From 79422fc45bee242e6da7e839c30a6128a1b0e61a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 3 Sep 2015 02:07:27 +0200 Subject: [PATCH] Started implementing VisualBrush. --- Perspex.sln | 2 +- src/Perspex.SceneGraph/Media/Brush.cs | 2 +- src/Perspex.SceneGraph/Media/VisualBrush.cs | 29 ++++++++ .../Perspex.SceneGraph.csproj | 1 + src/Windows/Perspex.Direct2D1/Disposable.cs | 39 ++++++++++ .../Perspex.Direct2D1/Media/DrawingContext.cs | 74 +++++++++++++++++-- .../Media/PerspexTextRenderer.cs | 7 +- .../Perspex.Direct2D1.csproj | 5 ++ .../Perspex.Direct2D1/PrimitiveExtensions.cs | 21 ------ .../Brushes/VisualBrushTests.cs | 58 +++++++++++++++ 10 files changed, 207 insertions(+), 31 deletions(-) create mode 100644 src/Perspex.SceneGraph/Media/VisualBrush.cs create mode 100644 src/Windows/Perspex.Direct2D1/Disposable.cs create mode 100644 tests/Perspex.RenderTests/Brushes/VisualBrushTests.cs diff --git a/Perspex.sln b/Perspex.sln index 3c1892f48d..4952c2b257 100644 --- a/Perspex.sln +++ b/Perspex.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.22823.1 +VisualStudioVersion = 14.0.23107.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Win32", "src\Windows\Perspex.Win32\Perspex.Win32.csproj", "{811A76CF-1CF6-440F-963B-BBE31BD72A82}" EndProject diff --git a/src/Perspex.SceneGraph/Media/Brush.cs b/src/Perspex.SceneGraph/Media/Brush.cs index f86a7d2560..5a3b6c3493 100644 --- a/src/Perspex.SceneGraph/Media/Brush.cs +++ b/src/Perspex.SceneGraph/Media/Brush.cs @@ -9,7 +9,7 @@ namespace Perspex.Media /// /// Describes how an area is painted. /// - public abstract class Brush + public abstract class Brush : PerspexObject { } } diff --git a/src/Perspex.SceneGraph/Media/VisualBrush.cs b/src/Perspex.SceneGraph/Media/VisualBrush.cs new file mode 100644 index 0000000000..73feade478 --- /dev/null +++ b/src/Perspex.SceneGraph/Media/VisualBrush.cs @@ -0,0 +1,29 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2015 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Media +{ + public class VisualBrush : Brush + { + public static readonly PerspexProperty VisualProperty = + PerspexProperty.Register("Visual"); + + public VisualBrush() + { + } + + public VisualBrush(IVisual visual) + { + this.Visual = visual; + } + + public IVisual Visual + { + get { return this.GetValue(VisualProperty); } + set { this.SetValue(VisualProperty, value); } + } + } +} diff --git a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj index 3b5280b26a..765915b16a 100644 --- a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj +++ b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj @@ -86,6 +86,7 @@ + diff --git a/src/Windows/Perspex.Direct2D1/Disposable.cs b/src/Windows/Perspex.Direct2D1/Disposable.cs new file mode 100644 index 0000000000..f42a044686 --- /dev/null +++ b/src/Windows/Perspex.Direct2D1/Disposable.cs @@ -0,0 +1,39 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Direct2D1 +{ + using System; + + public class Disposable : IDisposable where T : IDisposable + { + private IDisposable extra; + + public Disposable(T inner) + { + this.Inner = inner; + } + + public Disposable(T inner, IDisposable extra) + { + this.Inner = inner; + this.extra = extra; + } + + public T Inner { get; } + + public static implicit operator T(Disposable i) + { + return i.Inner; + } + + public void Dispose() + { + this.Inner.Dispose(); + this.extra?.Dispose(); + } + } +} diff --git a/src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs b/src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs index fce32ace29..f695e8335e 100644 --- a/src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs +++ b/src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs @@ -8,6 +8,7 @@ namespace Perspex.Direct2D1.Media { using System; using System.Reactive.Disposables; + using Layout; using Perspex.Media; using SharpDX; using SharpDX.Direct2D1; @@ -89,7 +90,7 @@ namespace Perspex.Direct2D1.Media { if (pen != null) { - using (var d2dBrush = pen.Brush.ToDirect2D(this.renderTarget)) + using (var d2dBrush = this.CreateBrush(pen.Brush)) using (var d2dStroke = pen.ToDirect2DStrokeStyle(this.renderTarget)) { this.renderTarget.DrawLine( @@ -112,7 +113,7 @@ namespace Perspex.Direct2D1.Media { if (brush != null) { - using (var d2dBrush = brush.ToDirect2D(this.renderTarget)) + using (var d2dBrush = this.CreateBrush(brush)) { GeometryImpl impl = (GeometryImpl)geometry.PlatformImpl; this.renderTarget.FillGeometry(impl.Geometry, d2dBrush); @@ -121,7 +122,7 @@ namespace Perspex.Direct2D1.Media if (pen != null) { - using (var d2dBrush = pen.Brush.ToDirect2D(this.renderTarget)) + using (var d2dBrush = this.CreateBrush(pen.Brush)) using (var d2dStroke = pen.ToDirect2DStrokeStyle(this.renderTarget)) { GeometryImpl impl = (GeometryImpl)geometry.PlatformImpl; @@ -138,7 +139,7 @@ namespace Perspex.Direct2D1.Media /// The corner radius. public void DrawRectange(Pen pen, Rect rect, float cornerRadius) { - using (var brush = pen.Brush.ToDirect2D(this.renderTarget)) + using (var brush = this.CreateBrush(pen.Brush)) using (var d2dStroke = pen.ToDirect2DStrokeStyle(this.renderTarget)) { this.renderTarget.DrawRoundedRectangle( @@ -161,7 +162,8 @@ namespace Perspex.Direct2D1.Media { var impl = (FormattedTextImpl)text.PlatformImpl; - using (var renderer = new PerspexTextRenderer(this.renderTarget, foreground.ToDirect2D(this.renderTarget))) + using (var brush = this.CreateBrush(foreground)) + using (var renderer = new PerspexTextRenderer(this, this.renderTarget, brush)) { impl.TextLayout.Draw(renderer, (float)origin.X, (float)origin.Y); } @@ -176,7 +178,7 @@ namespace Perspex.Direct2D1.Media /// The corner radius. public void FillRectange(Perspex.Media.Brush brush, Rect rect, float cornerRadius) { - using (var b = brush.ToDirect2D(this.renderTarget)) + using (var b = this.CreateBrush(brush)) { this.renderTarget.FillRoundedRectangle( new RoundedRectangle @@ -256,5 +258,65 @@ namespace Perspex.Direct2D1.Media this.renderTarget.Transform = transform * m3x2; }); } + + /// + /// Creates a Direct2D brush from a Perspex brush. + /// + /// The perspex brush. + /// The Direct2D brush. + public Disposable CreateBrush(Perspex.Media.Brush brush) + { + var solidColorBrush = brush as Perspex.Media.SolidColorBrush; + var visualBrush = brush as Perspex.Media.VisualBrush; + + if (solidColorBrush != null) + { + return new Disposable( + new SharpDX.Direct2D1.SolidColorBrush( + this.renderTarget, + solidColorBrush.Color.ToDirect2D())); + } + else if (visualBrush != null) + { + return this.CreateBrush(visualBrush); + } + else + { + // TODO: Implement other brushes. + return new Disposable( + new SharpDX.Direct2D1.SolidColorBrush(this.renderTarget, new Color4())); + } + } + + /// + /// Creates a Direct2D from a Perspex . + /// + /// The perspex brush. + /// The Direct2D brush. + private Disposable CreateBrush(VisualBrush brush) + { + var visual = brush.Visual; + var layoutable = visual as ILayoutable; + + if (layoutable?.IsArrangeValid == false) + { + layoutable.Measure(Size.Infinity); + layoutable.Arrange(new Rect(layoutable.DesiredSize)); + } + + using (var target = new BitmapRenderTarget( + this.renderTarget, + CompatibleRenderTargetOptions.None, + visual.Bounds.Size.ToSharpDX())) + { + var renderer = new Renderer(target); + renderer.Render(visual, null); + + var result = new BitmapBrush(this.renderTarget, target.Bitmap); + result.ExtendModeX = ExtendMode.Wrap; + result.ExtendModeY = ExtendMode.Wrap; + return new Disposable(result, target); + } + } } } diff --git a/src/Windows/Perspex.Direct2D1/Media/PerspexTextRenderer.cs b/src/Windows/Perspex.Direct2D1/Media/PerspexTextRenderer.cs index 3e6888fb64..5566c2ff60 100644 --- a/src/Windows/Perspex.Direct2D1/Media/PerspexTextRenderer.cs +++ b/src/Windows/Perspex.Direct2D1/Media/PerspexTextRenderer.cs @@ -13,14 +13,18 @@ namespace Perspex.Direct2D1.Media internal class PerspexTextRenderer : TextRenderer { + private DrawingContext context; + private RenderTarget renderTarget; private Brush foreground; public PerspexTextRenderer( + DrawingContext context, RenderTarget target, Brush foreground) { + this.context = context; this.renderTarget = target; this.foreground = foreground; } @@ -33,7 +37,6 @@ namespace Perspex.Direct2D1.Media public void Dispose() { - this.foreground.Dispose(); } public Result DrawGlyphRun( @@ -48,7 +51,7 @@ namespace Perspex.Direct2D1.Media var wrapper = clientDrawingEffect as BrushWrapper; var brush = (wrapper == null) ? this.foreground : - wrapper.Brush.ToDirect2D(this.renderTarget); + this.context.CreateBrush(wrapper.Brush); this.renderTarget.DrawGlyphRun( new Vector2(baselineOriginX, baselineOriginY), diff --git a/src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj b/src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj index 73ce0904a4..99e3b2e643 100644 --- a/src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj +++ b/src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj @@ -74,6 +74,7 @@ Properties\SharedAssemblyInfo.cs + @@ -101,6 +102,10 @@ {B09B78D8-9B26-48B0-9149-D64A2F120F3F} Perspex.Base + + {42472427-4774-4c81-8aff-9f27b8e31721} + Perspex.Layout + {EB582467-6ABB-43A1-B052-E981BA910E3A} Perspex.SceneGraph diff --git a/src/Windows/Perspex.Direct2D1/PrimitiveExtensions.cs b/src/Windows/Perspex.Direct2D1/PrimitiveExtensions.cs index ef44de77db..54c0da9c06 100644 --- a/src/Windows/Perspex.Direct2D1/PrimitiveExtensions.cs +++ b/src/Windows/Perspex.Direct2D1/PrimitiveExtensions.cs @@ -34,27 +34,6 @@ namespace Perspex.Direct2D1 return new Size2F((float)p.Width, (float)p.Height); } - /// - /// Converts a brush to Direct2D. - /// - /// The brush to convert. - /// The render target. - /// The Direct2D brush. - public static SharpDX.Direct2D1.Brush ToDirect2D(this Perspex.Media.Brush brush, RenderTarget target) - { - Perspex.Media.SolidColorBrush solidColorBrush = brush as Perspex.Media.SolidColorBrush; - - if (solidColorBrush != null) - { - return new SharpDX.Direct2D1.SolidColorBrush(target, solidColorBrush.Color.ToDirect2D()); - } - else - { - // TODO: Implement other brushes. - return new SharpDX.Direct2D1.SolidColorBrush(target, new Color4()); - } - } - /// /// Converts a pen to a Direct2D stroke style. /// diff --git a/tests/Perspex.RenderTests/Brushes/VisualBrushTests.cs b/tests/Perspex.RenderTests/Brushes/VisualBrushTests.cs new file mode 100644 index 0000000000..772d3a4917 --- /dev/null +++ b/tests/Perspex.RenderTests/Brushes/VisualBrushTests.cs @@ -0,0 +1,58 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Direct2D1.RenderTests.Controls +{ + using Perspex.Controls; + using Perspex.Controls.Shapes; + using Perspex.Layout; + using Perspex.Media; + using Xunit; + + public class VisualBrushTests : TestBase + { + public VisualBrushTests() + : base(@"Brushes\VisualBrush") + { + } + + [Fact] + public void VisualBrush_Should_Draw_Visual() + { + Decorator target = new Decorator + { + Padding = new Thickness(8), + Width = 200, + Height = 200, + Child = new Rectangle + { + Fill = new VisualBrush + { + Visual = new Border + { + Width = 92, + Height = 92, + Background = Brushes.Red, + BorderBrush = Brushes.Black, + BorderThickness = 2, + Child = new TextBlock + { + Text = "Perspex", + FontSize = 12, + FontFamily = "Arial", + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + } + } + } + } + }; + + this.RenderToFile(target); + this.CompareImages(); + } + } +}