Browse Source

Introduced DrawingContext class responsible for matrix transformation and push/pop order validation

Nikita Tsukanov 10 years ago
parent
commit
3fc62506df

+ 45 - 29
src/Gtk/Perspex.Cairo/Media/DrawingContext.cs

@@ -2,6 +2,7 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
+using System.Collections.Generic;
 using System.Linq;
 using System.Reactive.Disposables;
 using System.Runtime.InteropServices;
@@ -16,7 +17,7 @@ namespace Perspex.Cairo.Media
     /// <summary>
     /// Draws using Direct2D1.
     /// </summary>
-    public class DrawingContext : IDrawingContext, IDisposable
+    public class DrawingContext : IDrawingContextImpl, IDisposable
     {
         /// <summary>
         /// The cairo context.
@@ -30,7 +31,6 @@ namespace Perspex.Cairo.Media
         public DrawingContext(Cairo.Surface surface)
         {
             _context = new Cairo.Context(surface);
-            CurrentTransform = Matrix.Identity;
         }
 
         /// <summary>
@@ -40,15 +40,23 @@ namespace Perspex.Cairo.Media
         public DrawingContext(Gdk.Drawable drawable)
         {
             _context = Gdk.CairoHelper.Create(drawable);
-            CurrentTransform = Matrix.Identity;
         }
 
+
+        private Matrix _transform = Matrix.Identity;
         /// <summary>
         /// Gets the current transform of the drawing context.
         /// </summary>
-        public Matrix CurrentTransform
+        public Matrix Transform
         {
-            get; }
+            get { return _transform; }
+            set
+            {
+                _transform = value;
+                _context.Matrix = value.ToCairo();
+                
+            }
+        }
 
         /// <summary>
         /// Ends a draw operation.
@@ -131,29 +139,31 @@ namespace Perspex.Cairo.Media
         {
             var impl = geometry.PlatformImpl as StreamGeometryImpl;
 
-            using (var pop = PushTransform(impl.Transform))
+            var oldMatrix = Transform;
+            Transform *= impl.Transform;
+
+            
+
+            if (brush != null)
             {
                 _context.AppendPath(impl.Path);
-
-                if (brush != null)
+                using (var b = SetBrush(brush, geometry.Bounds.Size))
                 {
-					using (var b = SetBrush(brush, geometry.Bounds.Size)) 
-					{
-						if (pen != null)
-							_context.FillPreserve();
-						else
-							_context.Fill();
-					}
+                    if (pen != null)
+                        _context.FillPreserve();
+                    else
+                        _context.Fill();
                 }
             }
-
             if (pen != null)
             {
-				using (var p = SetPen(pen, geometry.Bounds.Size)) 
-				{
-					_context.Stroke();
-				}
+                _context.AppendPath(impl.Path);
+                using (var p = SetPen(pen, geometry.Bounds.Size))
+                {
+                    _context.Stroke();
+                }
             }
+            Transform = oldMatrix;
         }
 
         /// <summary>
@@ -208,31 +218,37 @@ namespace Perspex.Cairo.Media
         /// </summary>
         /// <param name="clip">The clip rectangle.</param>
         /// <returns>A disposable used to undo the clip rectangle.</returns>
-        public IDisposable PushClip(Rect clip)
+        public void PushClip(Rect clip)
         {
             _context.Save();
             _context.Rectangle(clip.ToCairo());
             _context.Clip();
+        }
 
-            return Disposable.Create(() => _context.Restore());
+        public void PopClip()
+        {
+            _context.Restore();
         }
 
+        readonly Stack<double> _opacityStack = new Stack<double>();
+
         /// <summary>
         /// Pushes an opacity value.
         /// </summary>
         /// <param name="opacity">The opacity.</param>
         /// <returns>A disposable used to undo the opacity.</returns>
-        public IDisposable PushOpacity(double opacity)
+        public void PushOpacity(double opacity)
         {
-            var tmp = opacityOverride;
+            _opacityStack.Push(opacityOverride);
 
             if (opacity < 1.0f)
-                opacityOverride = opacity;
+                opacityOverride *= opacity;
 
-            return Disposable.Create(() =>
-            {
-                opacityOverride = tmp;
-            });
+        }
+
+        public void PopOpacity()
+        {
+            opacityOverride = _opacityStack.Pop();
         }
 
         /// <summary>

+ 1 - 1
src/Gtk/Perspex.Cairo/Media/Imaging/RenderTargetBitmapImpl.cs

@@ -39,7 +39,7 @@ namespace Perspex.Cairo.Media.Imaging
             Surface.WriteToPng(fileName);
         }
 
-        public IDrawingContext CreateDrawingContext()
+        public Perspex.Media.DrawingContext CreateDrawingContext()
         {
             return _renderTarget.CreateDrawingContext();
         }

+ 7 - 5
src/Gtk/Perspex.Cairo/RenderTarget.cs

@@ -7,6 +7,7 @@ using Perspex.Cairo.Media;
 using Perspex.Media;
 using Perspex.Platform;
 using Perspex.Rendering;
+using DrawingContext = Perspex.Media.DrawingContext;
 
 namespace Perspex.Cairo
 {
@@ -50,12 +51,13 @@ namespace Perspex.Cairo
         /// <summary>
         /// Creates a cairo surface that targets a platform-specific resource.
         /// </summary>
-        /// <returns>A surface wrapped in an <see cref="IDrawingContext"/>.</returns>
-        public IDrawingContext CreateDrawingContext()
+        /// <returns>A surface wrapped in an <see cref="Perspex.Media.DrawingContext"/>.</returns>
+        public DrawingContext CreateDrawingContext()
         {
-            if(_surface != null)
-                return new DrawingContext(_surface);
-            return new DrawingContext(_window.GdkWindow);
+            var ctx = _surface != null
+                ? new Media.DrawingContext(_surface)
+                : new Media.DrawingContext(_window.GdkWindow);
+            return new DrawingContext(ctx);
         }
         
         public void Dispose() => _surface?.Dispose();

+ 1 - 1
src/Perspex.Application/Application.cs

@@ -177,7 +177,7 @@ namespace Perspex
         /// <param name="platformID">The value of Environment.OSVersion.Platform.</param>
         protected void InitializeSubsystems(int platformID)
         {
-            if (platformID == 4 || platformID == 6)
+            if (true)//platformID == 4 || platformID == 6)
             {
                 InitializeSubsystem("Perspex.Cairo");
                 InitializeSubsystem("Perspex.Gtk");

+ 1 - 1
src/Perspex.Controls/Border.cs

@@ -83,7 +83,7 @@ namespace Perspex.Controls
         /// Renders the control.
         /// </summary>
         /// <param name="context">The drawing context.</param>
-        public override void Render(IDrawingContext context)
+        public override void Render(DrawingContext context)
         {
             var background = Background;
             var borderBrush = BorderBrush;

+ 1 - 1
src/Perspex.Controls/Image.cs

@@ -46,7 +46,7 @@ namespace Perspex.Controls
         /// Renders the control.
         /// </summary>
         /// <param name="context">The drawing context.</param>
-        public override void Render(IDrawingContext context)
+        public override void Render(DrawingContext context)
         {
             Bitmap source = Source;
 

+ 2 - 2
src/Perspex.Controls/Panel.cs

@@ -172,10 +172,10 @@ namespace Perspex.Controls
         }
 
         /// <summary>
-        /// Renders the visual to a <see cref="IDrawingContext"/>.
+        /// Renders the visual to a <see cref="DrawingContext"/>.
         /// </summary>
         /// <param name="context">The drawing context.</param>
-        public override void Render(IDrawingContext context)
+        public override void Render(DrawingContext context)
         {
             Brush background = Background;
             if (background != null)

+ 1 - 1
src/Perspex.Controls/Presenters/TextPresenter.cs

@@ -69,7 +69,7 @@ namespace Perspex.Controls.Presenters
             return hit.TextPosition + (hit.IsTrailing ? 1 : 0);
         }
 
-        public override void Render(IDrawingContext context)
+        public override void Render(DrawingContext context)
         {
             var selectionStart = SelectionStart;
             var selectionEnd = SelectionEnd;

+ 1 - 1
src/Perspex.Controls/Primitives/AccessText.cs

@@ -62,7 +62,7 @@ namespace Perspex.Controls.Primitives
         /// Renders the <see cref="AccessText"/> to a drawing context.
         /// </summary>
         /// <param name="context">The drawing context.</param>
-        public override void Render(IDrawingContext context)
+        public override void Render(DrawingContext context)
         {
             base.Render(context);
 

+ 1 - 1
src/Perspex.Controls/Shapes/Shape.cs

@@ -90,7 +90,7 @@ namespace Perspex.Controls.Shapes
             set { SetValue(StrokeThicknessProperty, value); }
         }
 
-        public override void Render(IDrawingContext context)
+        public override void Render(DrawingContext context)
         {
             var geometry = RenderedGeometry;
 

+ 1 - 1
src/Perspex.Controls/TextBlock.cs

@@ -214,7 +214,7 @@ namespace Perspex.Controls
         /// Renders the <see cref="TextBlock"/> to a drawing context.
         /// </summary>
         /// <param name="context">The drawing context.</param>
-        public override void Render(IDrawingContext context)
+        public override void Render(DrawingContext context)
         {
             Brush background = Background;
 

+ 2 - 2
src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs

@@ -32,7 +32,7 @@ namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters
         /// <summary>
         /// The wrapped Perspex graphics object
         /// </summary>
-        private readonly IDrawingContext _g;
+        private readonly DrawingContext _g;
 
         /// <summary>
         /// if to release the graphics object on dispose
@@ -51,7 +51,7 @@ namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters
         /// <param name="g">the Perspex graphics object to use</param>
         /// <param name="initialClip">the initial clip of the graphics</param>
         /// <param name="releaseGraphics">optional: if to release the graphics object on dispose (default - false)</param>
-        public GraphicsAdapter(IDrawingContext g, RRect initialClip, bool releaseGraphics = false)
+        public GraphicsAdapter(DrawingContext g, RRect initialClip, bool releaseGraphics = false)
             : base(PerspexAdapter.Instance, initialClip)
         {
             ArgChecker.AssertArgNotNull(g, "g");

+ 1 - 1
src/Perspex.HtmlRenderer/HtmlContainer.cs

@@ -360,7 +360,7 @@ namespace TheArtOfDev.HtmlRenderer.Perspex
         /// </summary>
         /// <param name="g">the device to use to render</param>
         /// <param name="clip">the clip rectangle of the html container</param>
-        public void PerformPaint(IDrawingContext g, Rect clip)
+        public void PerformPaint(DrawingContext g, Rect clip)
         {
             ArgChecker.AssertArgNotNull(g, "g");
 

+ 1 - 1
src/Perspex.HtmlRenderer/HtmlControl.cs

@@ -327,7 +327,7 @@ namespace Perspex.Controls.Html
         private Size RenderSize => new Size(Bounds.Width, Bounds.Height);
 
         
-        public override void Render(IDrawingContext context)
+        public override void Render(DrawingContext context)
         {
             context.FillRectangle(Background,  new Rect(RenderSize));
 

+ 2 - 2
src/Perspex.SceneGraph/IVisual.cs

@@ -76,10 +76,10 @@ namespace Perspex
         int ZIndex { get; set; }
 
         /// <summary>
-        /// Renders the scene graph node to a <see cref="IDrawingContext"/>.
+        /// Renders the scene graph node to a <see cref="DrawingContext"/>.
         /// </summary>
         /// <param name="context">The context.</param>
-        void Render(IDrawingContext context);
+        void Render(DrawingContext context);
 
         /// <summary>
         /// Returns a transform that transforms the visual's coordinates into the coordinates

+ 160 - 0
src/Perspex.SceneGraph/Media/DrawingContext.cs

@@ -0,0 +1,160 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Perspex.Media.Imaging;
+
+namespace Perspex.Media
+{
+    public sealed class DrawingContext : IDisposable
+    {
+        private readonly IDrawingContextImpl _impl;
+        private int _currentLevel;
+
+        public DrawingContext(IDrawingContextImpl impl)
+        {
+            _impl = impl;
+        }
+
+        /// <summary>
+        /// Gets the current transform of the drawing context.
+        /// </summary>
+        public Matrix CurrentTransform => _impl.Transform;
+
+        /// <summary>
+        /// Draws a bitmap image.
+        /// </summary>
+        /// <param name="source">The bitmap image.</param>
+        /// <param name="opacity">The opacity to draw with.</param>
+        /// <param name="sourceRect">The rect in the image to draw.</param>
+        /// <param name="destRect">The rect in the output to draw to.</param>
+        public void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect)
+            => _impl.DrawImage(source, opacity, sourceRect, destRect);
+
+        /// <summary>
+        /// Draws a line.
+        /// </summary>
+        /// <param name="pen">The stroke pen.</param>
+        /// <param name="p1">The first point of the line.</param>
+        /// <param name="p2">The second point of the line.</param>
+        public void DrawLine(Pen pen, Point p1, Point p2) => _impl.DrawLine(pen, p1, p2);
+
+        /// <summary>
+        /// Draws a geometry.
+        /// </summary>
+        /// <param name="brush">The fill brush.</param>
+        /// <param name="pen">The stroke pen.</param>
+        /// <param name="geometry">The geometry.</param>
+        public void DrawGeometry(Brush brush, Pen pen, Geometry geometry) => _impl.DrawGeometry(brush, pen, geometry);
+
+        /// <summary>
+        /// Draws the outline of a rectangle.
+        /// </summary>
+        /// <param name="pen">The pen.</param>
+        /// <param name="rect">The rectangle bounds.</param>
+        /// <param name="cornerRadius">The corner radius.</param>
+        public void DrawRectangle(Pen pen, Rect rect, float cornerRadius = 0.0f)
+            => _impl.DrawRectangle(pen, rect, cornerRadius);
+
+        /// <summary>
+        /// Draws text.
+        /// </summary>
+        /// <param name="foreground">The foreground brush.</param>
+        /// <param name="origin">The upper-left corner of the text.</param>
+        /// <param name="text">The text.</param>
+        public void DrawText(Brush foreground, Point origin, FormattedText text)
+            => _impl.DrawText(foreground, origin, text);
+
+        /// <summary>
+        /// Draws a filled rectangle.
+        /// </summary>
+        /// <param name="brush">The brush.</param>
+        /// <param name="rect">The rectangle bounds.</param>
+        /// <param name="cornerRadius">The corner radius.</param>
+        public void FillRectangle(Brush brush, Rect rect, float cornerRadius = 0.0f)
+            => _impl.FillRectangle(brush, rect, cornerRadius);
+
+        public struct PushedState : IDisposable
+        {
+            private readonly int _level;
+            private readonly DrawingContext _context;
+            private readonly Matrix _matrix;
+            private readonly PushedStateType _type;
+
+            public enum PushedStateType
+            {
+                None,
+                Matrix,
+                Opacity,
+                Clip
+            }
+
+            public PushedState(DrawingContext context, PushedStateType type, Matrix matrix = default(Matrix))
+            {
+                _level = context._currentLevel += 1;
+                _context = context;
+                _type = type;
+                _matrix = matrix;
+
+            }
+
+            public void Dispose()
+            {
+                if(_type == PushedStateType.None)
+                    return;
+                if (_context._currentLevel != _level)
+                    throw new InvalidOperationException("Wrong Push/Pop state order");
+                _context._currentLevel--;
+                if (_type == PushedStateType.Matrix)
+                    _context._impl.Transform = _matrix;
+                else if(_type == PushedStateType.Clip)
+                    _context._impl.PopClip();
+
+                else if(_type == PushedStateType.Opacity)
+                    _context._impl.PopOpacity();
+            }
+
+
+        }
+
+
+        /// <summary>
+        /// Pushes a clip rectange.
+        /// </summary>
+        /// <param name="clip">The clip rectangle.</param>
+        /// <returns>A disposable used to undo the clip rectangle.</returns>
+        public PushedState PushClip(Rect clip)
+        {
+            _impl.PushClip(clip);
+            return new PushedState(this, PushedState.PushedStateType.Clip);
+        }
+
+        /// <summary>
+        /// Pushes an opacity value.
+        /// </summary>
+        /// <param name="opacity">The opacity.</param>
+        /// <returns>A disposable used to undo the opacity.</returns>
+        public PushedState PushOpacity(double opacity)
+            //TODO: Elimintate platform-specific push opacity call
+        {
+            _impl.PushOpacity(opacity);
+            return new PushedState(this, PushedState.PushedStateType.Opacity);
+        }
+
+        /// <summary>
+        /// Pushes a matrix transformation.
+        /// </summary>
+        /// <param name="matrix">The matrix</param>
+        /// <returns>A disposable used to undo the transformation.</returns>
+        public PushedState PushTransform(Matrix matrix)
+        {
+            var oldMatrix = CurrentTransform;
+            matrix = oldMatrix*matrix;
+            _impl.Transform = matrix;
+            return new PushedState(this, PushedState.PushedStateType.Matrix, oldMatrix);
+        }
+
+        public void Dispose() => _impl.Dispose();
+    }
+}

+ 8 - 11
src/Perspex.SceneGraph/Media/IDrawingContext.cs

@@ -9,12 +9,12 @@ namespace Perspex.Media
     /// <summary>
     /// Defines the interface through which drawing occurs.
     /// </summary>
-    public interface IDrawingContext : IDisposable
+    public interface IDrawingContextImpl : IDisposable
     {
         /// <summary>
-        /// Gets the current transform of the drawing context.
+        /// Gets or sets the current transform of the drawing context.
         /// </summary>
-        Matrix CurrentTransform { get; }
+        Matrix Transform { get; set; }
 
         /// <summary>
         /// Draws a bitmap image.
@@ -70,20 +70,17 @@ namespace Perspex.Media
         /// </summary>
         /// <param name="clip">The clip rectangle.</param>
         /// <returns>A disposable used to undo the clip rectangle.</returns>
-        IDisposable PushClip(Rect clip);
+        void PushClip(Rect clip);
+
+        void PopClip();
 
         /// <summary>
         /// Pushes an opacity value.
         /// </summary>
         /// <param name="opacity">The opacity.</param>
         /// <returns>A disposable used to undo the opacity.</returns>
-        IDisposable PushOpacity(double opacity);
+        void PushOpacity(double opacity);
 
-        /// <summary>
-        /// Pushes a matrix transformation.
-        /// </summary>
-        /// <param name="matrix">The matrix</param>
-        /// <returns>A disposable used to undo the transformation.</returns>
-        IDisposable PushTransform(Matrix matrix);
+        void PopOpacity();
     }
 }

+ 1 - 1
src/Perspex.SceneGraph/Media/Imaging/RenderTargetBitmap.cs

@@ -47,7 +47,7 @@ namespace Perspex.Media.Imaging
             return factory.CreateRenderTargetBitmap(width, height);
         }
 
-        public IDrawingContext CreateDrawingContext() => PlatformImpl.CreateDrawingContext();
+        public DrawingContext CreateDrawingContext() => PlatformImpl.CreateDrawingContext();
 
         void IRenderTarget.Resize(int width, int height)
         {

+ 0 - 76
src/Perspex.SceneGraph/Media/ValidatingDrawingContext.cs

@@ -1,76 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.Reactive.Disposables;
-using Perspex.Media.Imaging;
-
-namespace Perspex.Media
-{
-    public class ValidatingDrawingContext : IDrawingContext
-    {
-        private readonly IDrawingContext _base;
-
-        public ValidatingDrawingContext(IDrawingContext @base)
-        {
-            _base = @base;
-        }
-
-        public void Dispose()
-        {
-            _base.Dispose();
-        }
-
-        public Matrix CurrentTransform => _base.CurrentTransform;
-        public void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect)
-        {
-            _base.DrawImage(source, opacity, sourceRect, destRect);
-        }
-
-        public void DrawLine(Pen pen, Point p1, Point p2)
-        {
-            _base.DrawLine(pen, p1, p2);
-        }
-
-        public void DrawGeometry(Brush brush, Pen pen, Geometry geometry)
-        {
-            _base.DrawGeometry(brush, pen, geometry);
-        }
-
-        public void DrawRectangle(Pen pen, Rect rect, float cornerRadius = 0)
-        {
-            _base.DrawRectangle(pen, rect, cornerRadius);
-        }
-
-        public void DrawText(Brush foreground, Point origin, FormattedText text)
-        {
-            _base.DrawText(foreground, origin, text);
-        }
-
-        public void FillRectangle(Brush brush, Rect rect, float cornerRadius = 0)
-        {
-            _base.FillRectangle(brush, rect, cornerRadius);
-        }
-
-
-        Stack<IDisposable> _stateStack = new Stack<IDisposable>();
-
-        IDisposable Transform(IDisposable disposable)
-        {
-            _stateStack.Push(disposable);
-            return Disposable.Create(() =>
-            {
-                var current = _stateStack.Peek();
-                if (current != disposable)
-                    throw new InvalidOperationException("Invalid push/pop order");
-                current.Dispose();
-                _stateStack.Pop();
-            });
-        }
-
-        public IDisposable PushClip(Rect clip) => Transform(_base.PushClip(clip));
-
-        public IDisposable PushOpacity(double opacity) => Transform(_base.PushOpacity(opacity));
-
-        public IDisposable PushTransform(Matrix matrix) => Transform(_base.PushTransform(matrix));
-    }
-}

+ 1 - 1
src/Perspex.SceneGraph/Perspex.SceneGraph.csproj

@@ -64,6 +64,7 @@
     <Compile Include="Media\Color.cs" />
     <Compile Include="Media\Colors.cs" />
     <Compile Include="Media\DashStyle.cs" />
+    <Compile Include="Media\DrawingContext.cs" />
     <Compile Include="Media\GradientBrush.cs" />
     <Compile Include="Media\GradientSpreadMethod.cs" />
     <Compile Include="Media\GradientStop.cs" />
@@ -99,7 +100,6 @@
     <Compile Include="Media\Transform.cs" />
     <Compile Include="Media\TileBrush.cs" />
     <Compile Include="Media\ImageBush.cs" />
-    <Compile Include="Media\ValidatingDrawingContext.cs" />
     <Compile Include="Media\VisualBrush.cs" />
     <Compile Include="RelativePoint.cs" />
     <Compile Include="Platform\IFormattedTextImpl.cs" />

+ 2 - 2
src/Perspex.SceneGraph/Platform/IRenderTarget.cs

@@ -15,9 +15,9 @@ namespace Perspex.Platform
     public interface IRenderTarget : IDisposable
     {
         /// <summary>
-        /// Creates an <see cref="IDrawingContext"/> for a rendering session.
+        /// Creates an <see cref="DrawingContext"/> for a rendering session.
         /// </summary>
-        IDrawingContext CreateDrawingContext();
+        DrawingContext CreateDrawingContext();
 
         /// <summary>
         /// Resizes the rendered viewport.

+ 2 - 2
src/Perspex.SceneGraph/Rendering/RendererBase.cs

@@ -35,7 +35,7 @@ namespace Perspex.Rendering
         /// <param name="visual">The visual to render.</param>
         /// 
         /// <param name="context">The drawing context.</param>
-        public static void Render(this IDrawingContext context, IVisual visual)
+        public static void Render(this DrawingContext context, IVisual visual)
         {
             var opacity = visual.Opacity;
             if (visual.IsVisible && opacity > 0)
@@ -54,7 +54,7 @@ namespace Perspex.Rendering
 
                 using (context.PushTransform(m))
                 using (context.PushOpacity(opacity))
-                using (visual.ClipToBounds ? context.PushClip(new Rect(visual.Bounds.Size)) : null)
+                using (visual.ClipToBounds ? context.PushClip(new Rect(visual.Bounds.Size)) : default(DrawingContext.PushedState))
                 {
                     visual.Render(context);
                     foreach (var child in visual.VisualChildren.OrderBy(x => x.ZIndex))

+ 2 - 2
src/Perspex.SceneGraph/Visual.cs

@@ -229,10 +229,10 @@ namespace Perspex
         }
 
         /// <summary>
-        /// Renders the visual to a <see cref="IDrawingContext"/>.
+        /// Renders the visual to a <see cref="DrawingContext"/>.
         /// </summary>
         /// <param name="context">The drawing context.</param>
-        public virtual void Render(IDrawingContext context)
+        public virtual void Render(DrawingContext context)
         {
             Contract.Requires<ArgumentNullException>(context != null);
         }

+ 25 - 34
src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs

@@ -2,6 +2,8 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
+using System.Collections;
+using System.Collections.Generic;
 using System.Reactive.Disposables;
 using Perspex.Media;
 using SharpDX;
@@ -13,7 +15,7 @@ namespace Perspex.Direct2D1.Media
     /// <summary>
     /// Draws using Direct2D1.
     /// </summary>
-    public class DrawingContext : IDrawingContext, IDisposable
+    public class DrawingContext : IDrawingContextImpl, IDisposable
     {
         /// <summary>
         /// The Direct2D1 render target.
@@ -42,10 +44,10 @@ namespace Perspex.Direct2D1.Media
         /// <summary>
         /// Gets the current transform of the drawing context.
         /// </summary>
-        public Matrix CurrentTransform
+        public Matrix Transform
         {
             get { return _renderTarget.Transform.ToPerspex(); }
-            private set { _renderTarget.Transform = value.ToDirect2D(); }
+            set { _renderTarget.Transform = value.ToDirect2D(); }
         }
 
         /// <summary>
@@ -53,6 +55,8 @@ namespace Perspex.Direct2D1.Media
         /// </summary>
         public void Dispose()
         {
+            foreach (var layer in _layerPool)
+                layer.Dispose();
             _renderTarget.EndDraw();
         }
 
@@ -233,22 +237,24 @@ namespace Perspex.Direct2D1.Media
         /// </summary>
         /// <param name="clip">The clip rectangle.</param>
         /// <returns>A disposable used to undo the clip rectangle.</returns>
-        public IDisposable PushClip(Rect clip)
+        public void PushClip(Rect clip)
         {
             _renderTarget.PushAxisAlignedClip(clip.ToSharpDX(), AntialiasMode.PerPrimitive);
+        }
 
-            return Disposable.Create(() =>
-            {
-                _renderTarget.PopAxisAlignedClip();
-            });
+        public void PopClip()
+        {
+            _renderTarget.PopAxisAlignedClip();
         }
 
+        Stack<Layer> _layers = new Stack<Layer>();
+        private readonly Stack<Layer> _layerPool = new Stack<Layer>();
         /// <summary>
         /// Pushes an opacity value.
         /// </summary>
         /// <param name="opacity">The opacity.</param>
         /// <returns>A disposable used to undo the opacity.</returns>
-        public IDisposable PushOpacity(double opacity)
+        public void PushOpacity(double opacity)
         {
             if (opacity < 1)
             {
@@ -256,41 +262,26 @@ namespace Perspex.Direct2D1.Media
                 {
                     ContentBounds = RectangleF.Infinite,
                     MaskTransform = Matrix3x2.Identity,
-                    Opacity = (float)opacity,
+                    Opacity = (float) opacity,
                 };
 
-                var layer = new Layer(_renderTarget);
-
+                var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_renderTarget);
                 _renderTarget.PushLayer(ref parameters, layer);
 
-                return Disposable.Create(() =>
-                {
-                    _renderTarget.PopLayer();
-                    layer.Dispose();
-                });
+                _layers.Push(layer);
             }
             else
-            {
-                return Disposable.Empty;
-            }
+                _layers.Push(null);
         }
 
-        /// <summary>
-        /// Pushes a matrix transformation.
-        /// </summary>
-        /// <param name="matrix">The matrix</param>
-        /// <returns>A disposable used to undo the transformation.</returns>
-        public IDisposable PushTransform(Matrix matrix)
+        public void PopOpacity()
         {
-            Matrix3x2 m3x2 = matrix.ToDirect2D();
-            Matrix3x2 transform = _renderTarget.Transform * m3x2;
-            _renderTarget.Transform = transform;
-
-            return Disposable.Create(() =>
+            var layer = _layers.Pop();
+            if (layer != null)
             {
-                m3x2.Invert();
-                _renderTarget.Transform = transform * m3x2;
-            });
+                _renderTarget.PopLayer();
+                _layerPool.Push(layer);
+            }
         }
 
         /// <summary>

+ 1 - 1
src/Windows/Perspex.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs

@@ -38,7 +38,7 @@ namespace Perspex.Direct2D1.Media
             // TODO:
         }
 
-        public IDrawingContext CreateDrawingContext() => new RenderTarget(_target).CreateDrawingContext();
+        public Perspex.Media.DrawingContext CreateDrawingContext() => new RenderTarget(_target).CreateDrawingContext();
 
         void IRenderTarget.Resize(int width, int height)
         {

+ 4 - 15
src/Windows/Perspex.Direct2D1/RenderTarget.cs

@@ -8,6 +8,7 @@ using Perspex.Platform;
 using Perspex.Rendering;
 using SharpDX;
 using SharpDX.Direct2D1;
+using DrawingContext = Perspex.Media.DrawingContext;
 using DwFactory = SharpDX.DirectWrite.Factory;
 
 namespace Perspex.Direct2D1
@@ -91,24 +92,12 @@ namespace Perspex.Direct2D1
             window.Resize(new Size2(width, height));
         }
 
-        IDrawingContext Wrap(IDrawingContext ctx)
-        {
-#if DEBUG
-            return new ValidatingDrawingContext(ctx);
-#endif
-#pragma warning disable 162
-            return ctx;
-#pragma warning restore 162
-        }
-
         /// <summary>
         /// Creates a drawing context for a rendering session.
         /// </summary>
-        /// <returns>An <see cref="IDrawingContext"/>.</returns>
-        public IDrawingContext CreateDrawingContext()
-        {
-            return Wrap(new DrawingContext(_renderTarget, DirectWriteFactory));
-        }
+        /// <returns>An <see cref="Perspex.Media.DrawingContext"/>.</returns>
+        public DrawingContext CreateDrawingContext() 
+            => new DrawingContext(new Media.DrawingContext(_renderTarget, DirectWriteFactory));
 
         public void Dispose()
         {