diff --git a/samples/RenderTest/MainWindow.xaml.cs b/samples/RenderTest/MainWindow.xaml.cs
index feb121186f..8fe306df7d 100644
--- a/samples/RenderTest/MainWindow.xaml.cs
+++ b/samples/RenderTest/MainWindow.xaml.cs
@@ -22,7 +22,7 @@ namespace RenderTest
this.InitializeComponent();
this.CreateAnimations();
this.AttachDevTools();
- RendererMixin.DrawFpsCounter = true;
+ Renderer.DrawFps = true;
}
private void InitializeComponent()
diff --git a/src/Avalonia.Controls/Shapes/Shape.cs b/src/Avalonia.Controls/Shapes/Shape.cs
index 427749263a..688d507c8e 100644
--- a/src/Avalonia.Controls/Shapes/Shape.cs
+++ b/src/Avalonia.Controls/Shapes/Shape.cs
@@ -64,7 +64,7 @@ namespace Avalonia.Controls.Shapes
if (DefiningGeometry != null)
{
_renderedGeometry = DefiningGeometry.Clone();
- _renderedGeometry.Transform = new MatrixTransform(_transform);
+ ////_renderedGeometry.Transform = new MatrixTransform(_transform);
}
}
diff --git a/src/Avalonia.Visuals/Avalonia.Visuals.csproj b/src/Avalonia.Visuals/Avalonia.Visuals.csproj
index d9736e4c6e..aeffde81ed 100644
--- a/src/Avalonia.Visuals/Avalonia.Visuals.csproj
+++ b/src/Avalonia.Visuals/Avalonia.Visuals.csproj
@@ -100,15 +100,34 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -131,7 +150,6 @@
-
@@ -140,14 +158,8 @@
-
-
-
-
-
-
diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs
index ddaa681bab..9a5e83dbcb 100644
--- a/src/Avalonia.Visuals/Media/DrawingContext.cs
+++ b/src/Avalonia.Visuals/Media/DrawingContext.cs
@@ -1,16 +1,11 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using Avalonia.Media.Imaging;
namespace Avalonia.Media
{
public sealed class DrawingContext : IDisposable
{
- private readonly IDrawingContextImpl _impl;
private int _currentLevel;
//Internal tranformation that is applied but not exposed anywhere
//To be used for DPI scaling, etc
@@ -41,10 +36,11 @@ namespace Avalonia.Media
public DrawingContext(IDrawingContextImpl impl, Matrix? hiddenPostTransform = null)
{
- _impl = impl;
+ PlatformImpl = impl;
_hiddenPostTransform = hiddenPostTransform;
}
+ public IDrawingContextImpl PlatformImpl { get; }
private Matrix _currentTransform = Matrix.Identity;
@@ -62,7 +58,7 @@ namespace Avalonia.Media
var transform = _currentTransform*_currentContainerTransform;
if (_hiddenPostTransform.HasValue)
transform = transform*_hiddenPostTransform.Value;
- _impl.Transform = transform;
+ PlatformImpl.Transform = transform;
}
}
@@ -79,7 +75,7 @@ namespace Avalonia.Media
/// The rect in the image to draw.
/// The rect in the output to draw to.
public void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect)
- => _impl.DrawImage(source, opacity, sourceRect, destRect);
+ => PlatformImpl.DrawImage(source.PlatformImpl, opacity, sourceRect, destRect);
///
/// Draws a line.
@@ -87,7 +83,7 @@ namespace Avalonia.Media
/// The stroke pen.
/// The first point of the line.
/// The second point of the line.
- public void DrawLine(Pen pen, Point p1, Point p2) => _impl.DrawLine(pen, p1, p2);
+ public void DrawLine(Pen pen, Point p1, Point p2) => PlatformImpl.DrawLine(pen, p1, p2);
///
/// Draws a geometry.
@@ -95,7 +91,8 @@ namespace Avalonia.Media
/// The fill brush.
/// The stroke pen.
/// The geometry.
- public void DrawGeometry(IBrush brush, Pen pen, Geometry geometry) => _impl.DrawGeometry(brush, pen, geometry);
+ public void DrawGeometry(IBrush brush, Pen pen, Geometry geometry)
+ => PlatformImpl.DrawGeometry(brush, pen, geometry.PlatformImpl);
///
/// Draws the outline of a rectangle.
@@ -104,7 +101,7 @@ namespace Avalonia.Media
/// The rectangle bounds.
/// The corner radius.
public void DrawRectangle(Pen pen, Rect rect, float cornerRadius = 0.0f)
- => _impl.DrawRectangle(pen, rect, cornerRadius);
+ => PlatformImpl.DrawRectangle(pen, rect, cornerRadius);
///
/// Draws text.
@@ -113,7 +110,7 @@ namespace Avalonia.Media
/// The upper-left corner of the text.
/// The text.
public void DrawText(IBrush foreground, Point origin, FormattedText text)
- => _impl.DrawText(foreground, origin, text);
+ => PlatformImpl.DrawText(foreground, origin, text.PlatformImpl);
///
/// Draws a filled rectangle.
@@ -122,7 +119,7 @@ namespace Avalonia.Media
/// The rectangle bounds.
/// The corner radius.
public void FillRectangle(IBrush brush, Rect rect, float cornerRadius = 0.0f)
- => _impl.FillRectangle(brush, rect, cornerRadius);
+ => PlatformImpl.FillRectangle(brush, rect, cornerRadius);
public struct PushedState : IDisposable
{
@@ -162,13 +159,13 @@ namespace Avalonia.Media
if (_type == PushedStateType.Matrix)
_context.CurrentTransform = _matrix;
else if (_type == PushedStateType.Clip)
- _context._impl.PopClip();
+ _context.PlatformImpl.PopClip();
else if (_type == PushedStateType.Opacity)
- _context._impl.PopOpacity();
+ _context.PlatformImpl.PopOpacity();
else if (_type == PushedStateType.GeometryClip)
- _context._impl.PopGeometryClip();
+ _context.PlatformImpl.PopGeometryClip();
else if (_type == PushedStateType.OpacityMask)
- _context._impl.PopOpacityMask();
+ _context.PlatformImpl.PopOpacityMask();
else if (_type == PushedStateType.MatrixContainer)
{
var cont = _context._transformContainers.Pop();
@@ -186,7 +183,7 @@ namespace Avalonia.Media
/// A disposable used to undo the clip rectangle.
public PushedState PushClip(Rect clip)
{
- _impl.PushClip(clip);
+ PlatformImpl.PushClip(clip);
return new PushedState(this, PushedState.PushedStateType.Clip);
}
@@ -198,7 +195,7 @@ namespace Avalonia.Media
public PushedState PushGeometryClip(Geometry clip)
{
Contract.Requires(clip != null);
- _impl.PushGeometryClip(clip);
+ PlatformImpl.PushGeometryClip(clip);
return new PushedState(this, PushedState.PushedStateType.GeometryClip);
}
@@ -210,7 +207,7 @@ namespace Avalonia.Media
public PushedState PushOpacity(double opacity)
//TODO: Eliminate platform-specific push opacity call
{
- _impl.PushOpacity(opacity);
+ PlatformImpl.PushOpacity(opacity);
return new PushedState(this, PushedState.PushedStateType.Opacity);
}
@@ -224,7 +221,7 @@ namespace Avalonia.Media
/// A disposable to undo the opacity mask.
public PushedState PushOpacityMask(IBrush mask, Rect bounds)
{
- _impl.PushOpacityMask(mask, bounds);
+ PlatformImpl.PushOpacityMask(mask, bounds);
return new PushedState(this, PushedState.PushedStateType.OpacityMask);
}
@@ -278,7 +275,7 @@ namespace Avalonia.Media
_states = null;
TransformStackPool.Push(_transformContainers);
_transformContainers = null;
- _impl.Dispose();
+ PlatformImpl.Dispose();
}
}
}
diff --git a/src/Avalonia.Visuals/Media/IDrawingContext.cs b/src/Avalonia.Visuals/Media/IDrawingContextImpl.cs
similarity index 92%
rename from src/Avalonia.Visuals/Media/IDrawingContext.cs
rename to src/Avalonia.Visuals/Media/IDrawingContextImpl.cs
index c63abc160f..0fb57923dd 100644
--- a/src/Avalonia.Visuals/Media/IDrawingContext.cs
+++ b/src/Avalonia.Visuals/Media/IDrawingContextImpl.cs
@@ -3,6 +3,7 @@
using System;
using Avalonia.Media.Imaging;
+using Avalonia.Platform;
namespace Avalonia.Media
{
@@ -23,7 +24,7 @@ namespace Avalonia.Media
/// The opacity to draw with.
/// The rect in the image to draw.
/// The rect in the output to draw to.
- void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect);
+ void DrawImage(IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect);
///
/// Draws a line.
@@ -39,7 +40,7 @@ namespace Avalonia.Media
/// The fill brush.
/// The stroke pen.
/// The geometry.
- void DrawGeometry(IBrush brush, Pen pen, Geometry geometry);
+ void DrawGeometry(IBrush brush, Pen pen, IGeometryImpl geometry);
///
/// Draws the outline of a rectangle.
@@ -55,7 +56,7 @@ namespace Avalonia.Media
/// The foreground brush.
/// The upper-left corner of the text.
/// The text.
- void DrawText(IBrush foreground, Point origin, FormattedText text);
+ void DrawText(IBrush foreground, Point origin, IFormattedTextImpl text);
///
/// Draws a filled rectangle.
diff --git a/src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs b/src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs
index 4cc9c3fdf2..749d690d48 100644
--- a/src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs
+++ b/src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs
@@ -48,6 +48,6 @@ namespace Avalonia.Media.Imaging
return factory.CreateRenderTargetBitmap(width, height);
}
- public DrawingContext CreateDrawingContext() => PlatformImpl.CreateDrawingContext();
+ public IDrawingContextImpl CreateDrawingContext() => PlatformImpl.CreateDrawingContext();
}
}
diff --git a/src/Avalonia.Visuals/Platform/IFormattedTextImpl.cs b/src/Avalonia.Visuals/Platform/IFormattedTextImpl.cs
index 14f9e987bd..e3a44c437e 100644
--- a/src/Avalonia.Visuals/Platform/IFormattedTextImpl.cs
+++ b/src/Avalonia.Visuals/Platform/IFormattedTextImpl.cs
@@ -17,6 +17,11 @@ namespace Avalonia.Platform
///
Size Constraint { get; set; }
+ ///
+ /// Gets the text.
+ ///
+ string Text { get; }
+
///
/// Gets the lines in the text.
///
diff --git a/src/Avalonia.Visuals/Platform/IRenderTarget.cs b/src/Avalonia.Visuals/Platform/IRenderTarget.cs
index 75ba9d5a56..fcde194884 100644
--- a/src/Avalonia.Visuals/Platform/IRenderTarget.cs
+++ b/src/Avalonia.Visuals/Platform/IRenderTarget.cs
@@ -17,6 +17,6 @@ namespace Avalonia.Platform
///
/// Creates an for a rendering session.
///
- DrawingContext CreateDrawingContext();
+ IDrawingContextImpl CreateDrawingContext();
}
}
diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
new file mode 100644
index 0000000000..3721544234
--- /dev/null
+++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
@@ -0,0 +1,128 @@
+using System;
+using System.Diagnostics;
+using System.Linq;
+using Avalonia.Media;
+using Avalonia.Platform;
+using Avalonia.Rendering.SceneGraph;
+using Avalonia.Threading;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Rendering
+{
+ public class DeferredRenderer : IRenderer
+ {
+ private readonly IRenderLoop _renderLoop;
+ private readonly IRenderRoot _root;
+ private Scene _scene;
+ private IRenderTarget _renderTarget;
+ private bool _needsUpdate;
+ private bool _needsRender;
+
+ private readonly Stopwatch _stopwatch = Stopwatch.StartNew();
+ private int _totalFrames;
+ private int _framesThisSecond;
+ private int _fps;
+ private TimeSpan _lastFpsUpdate;
+
+ public DeferredRenderer(IRenderRoot root, IRenderLoop renderLoop)
+ {
+ Contract.Requires(root != null);
+
+ _root = root;
+ _scene = new Scene(root);
+ _renderLoop = renderLoop;
+ _renderLoop.Tick += OnRenderLoopTick;
+ }
+
+ public bool DrawFps { get; set; }
+
+ public void AddDirty(IVisual visual)
+ {
+ if (!_needsUpdate)
+ {
+ _needsUpdate = true;
+ Dispatcher.UIThread.InvokeAsync(UpdateScene, DispatcherPriority.Render);
+ }
+ }
+
+ public void Dispose()
+ {
+ _renderLoop.Tick -= OnRenderLoopTick;
+ }
+
+ public void Render(Rect rect)
+ {
+ if (_renderTarget == null)
+ {
+ _renderTarget = _root.CreateRenderTarget();
+ }
+
+ try
+ {
+ _totalFrames++;
+
+ using (var context = _renderTarget.CreateDrawingContext())
+ {
+ _scene.Root.Render(context);
+
+ if (DrawFps)
+ {
+ RenderFps(context);
+ }
+ }
+ }
+ catch (RenderTargetCorruptedException ex)
+ {
+ Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
+ _renderTarget.Dispose();
+ _renderTarget = null;
+ }
+ }
+
+ private void RenderFps(IDrawingContextImpl context)
+ {
+ var now = _stopwatch.Elapsed;
+ var elapsed = now - _lastFpsUpdate;
+
+ _framesThisSecond++;
+
+ if (elapsed.TotalSeconds > 1)
+ {
+ _fps = (int)(_framesThisSecond / elapsed.TotalSeconds);
+ _framesThisSecond = 0;
+ _lastFpsUpdate = now;
+ }
+
+ var pt = new Point(40, 40);
+ using (
+ var txt = new FormattedText("Frame #" + _totalFrames + " FPS: " + _fps, "Arial", 18,
+ FontStyle.Normal,
+ TextAlignment.Left,
+ FontWeight.Normal,
+ TextWrapping.NoWrap))
+ {
+ context.Transform = Matrix.Identity;
+ context.FillRectangle(Brushes.White, new Rect(pt, txt.Measure()));
+ context.DrawText(Brushes.Black, pt, txt.PlatformImpl);
+ }
+ }
+
+ private void UpdateScene()
+ {
+ Dispatcher.UIThread.VerifyAccess();
+
+ _scene = SceneBuilder.Update(_scene);
+ _needsUpdate = false;
+ _needsRender = true;
+ _root.Invalidate(new Rect(_root.ClientSize));
+ }
+
+ private void OnRenderLoopTick(object sender, EventArgs e)
+ {
+ //if (_needsRender)
+ //{
+ // _needsRender = false;
+ //}
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Rendering/IRenderer.cs b/src/Avalonia.Visuals/Rendering/IRenderer.cs
index c643662179..119bb4c8d1 100644
--- a/src/Avalonia.Visuals/Rendering/IRenderer.cs
+++ b/src/Avalonia.Visuals/Rendering/IRenderer.cs
@@ -8,8 +8,9 @@ namespace Avalonia.Rendering
{
public interface IRenderer : IDisposable
{
- void AddDirty(IVisual visual);
+ bool DrawFps { get; set; }
+ void AddDirty(IVisual visual);
void Render(Rect rect);
}
}
\ No newline at end of file
diff --git a/src/Avalonia.Visuals/Rendering/Renderer.cs b/src/Avalonia.Visuals/Rendering/Renderer.cs
index ed58d129af..3b2fe19be4 100644
--- a/src/Avalonia.Visuals/Rendering/Renderer.cs
+++ b/src/Avalonia.Visuals/Rendering/Renderer.cs
@@ -23,6 +23,8 @@ namespace Avalonia.Rendering
_renderLoop.Tick += OnRenderLoopTick;
}
+ public bool DrawFps { get; set; }
+
public void AddDirty(IVisual visual)
{
_dirty = true;
@@ -42,6 +44,7 @@ namespace Avalonia.Rendering
try
{
+ RendererMixin.DrawFpsCounter = DrawFps;
_renderTarget.Render(_root);
}
catch (RenderTargetCorruptedException ex)
diff --git a/src/Avalonia.Visuals/Rendering/RendererMixin.cs b/src/Avalonia.Visuals/Rendering/RendererMixin.cs
index 3d13501edd..3a24e1a22f 100644
--- a/src/Avalonia.Visuals/Rendering/RendererMixin.cs
+++ b/src/Avalonia.Visuals/Rendering/RendererMixin.cs
@@ -42,34 +42,34 @@ namespace Avalonia.Rendering
/// The visual to render.
public static void Render(this IRenderTarget renderTarget, IVisual visual)
{
- using (var ctx = renderTarget.CreateDrawingContext())
- {
- ctx.Render(visual);
- s_frameNum++;
- if (DrawFpsCounter)
- {
- s_currentFrames++;
- var now = s_stopwatch.Elapsed;
- var elapsed = now - s_lastMeasure;
- if (elapsed.TotalSeconds > 1)
- {
- s_fps = (int) (s_currentFrames/elapsed.TotalSeconds);
- s_currentFrames = 0;
- s_lastMeasure = now;
- }
- var pt = new Point(40, 40);
- using (
- var txt = new FormattedText("Frame #" + s_frameNum + " FPS: " + s_fps, "Arial", 18,
- FontStyle.Normal,
- TextAlignment.Left,
- FontWeight.Normal,
- TextWrapping.NoWrap))
- {
- ctx.FillRectangle(Brushes.White, new Rect(pt, txt.Measure()));
- ctx.DrawText(Brushes.Black, pt, txt);
- }
- }
- }
+ ////using (var ctx = renderTarget.CreateDrawingContext())
+ ////{
+ //// ctx.Render(visual);
+ //// s_frameNum++;
+ //// if (DrawFpsCounter)
+ //// {
+ //// s_currentFrames++;
+ //// var now = s_stopwatch.Elapsed;
+ //// var elapsed = now - s_lastMeasure;
+ //// if (elapsed.TotalSeconds > 1)
+ //// {
+ //// s_fps = (int) (s_currentFrames/elapsed.TotalSeconds);
+ //// s_currentFrames = 0;
+ //// s_lastMeasure = now;
+ //// }
+ //// var pt = new Point(40, 40);
+ //// using (
+ //// var txt = new FormattedText("Frame #" + s_frameNum + " FPS: " + s_fps, "Arial", 18,
+ //// FontStyle.Normal,
+ //// TextAlignment.Left,
+ //// FontWeight.Normal,
+ //// TextWrapping.NoWrap))
+ //// {
+ //// ctx.FillRectangle(Brushes.White, new Rect(pt, txt.Measure()));
+ //// ctx.DrawText(Brushes.Black, pt, txt);
+ //// }
+ //// }
+ ////}
}
///
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
new file mode 100644
index 0000000000..5717ccdf0e
--- /dev/null
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
@@ -0,0 +1,222 @@
+// 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.Reactive.Disposables;
+using Avalonia.Media;
+using Avalonia.Platform;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Rendering.SceneGraph
+{
+ public class DeferredDrawingContextImpl : IDrawingContextImpl
+ {
+ private Stack _stack = new Stack();
+
+ public Matrix Transform { get; set; }
+
+ private VisualNode Node => _stack.Peek().Node;
+
+ private int Index
+ {
+ get { return _stack.Peek().Index; }
+ set { _stack.Peek().Index = value; }
+ }
+
+ public IDisposable Begin(VisualNode node)
+ {
+ _stack.Push(new Frame(node));
+ return Disposable.Create(Pop);
+ }
+
+ public void Dispose()
+ {
+ }
+
+ public void AddChild(IVisualNode visualNode)
+ {
+ if (_stack.Count > 0)
+ {
+ var next = NextNodeAs();
+
+ if (next == null || next != visualNode)
+ {
+ Add(visualNode);
+ }
+ else
+ {
+ ++Index;
+ }
+ }
+ }
+
+ public void DrawGeometry(IBrush brush, Pen pen, IGeometryImpl geometry)
+ {
+ var next = NextNodeAs();
+
+ if (next == null || !next.Equals(Transform, brush, pen, geometry))
+ {
+ Add(new GeometryNode(Transform, brush, pen, geometry));
+ }
+ else
+ {
+ ++Index;
+ }
+ }
+
+ public void DrawImage(IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect)
+ {
+ var next = NextNodeAs();
+
+ if (next == null || !next.Equals(Transform, source, opacity, sourceRect, destRect))
+ {
+ Add(new ImageNode(Transform, source, opacity, sourceRect, destRect));
+ }
+ else
+ {
+ ++Index;
+ }
+ }
+
+ public void DrawLine(Pen pen, Point p1, Point p2)
+ {
+ var next = NextNodeAs();
+
+ if (next == null || !next.Equals(Transform, pen, p1, p2))
+ {
+ Add(new LineNode(Transform, pen, p1, p2));
+ }
+ else
+ {
+ ++Index;
+ }
+ }
+
+ public void DrawRectangle(Pen pen, Rect rect, float cornerRadius = 0)
+ {
+ var next = NextNodeAs();
+
+ if (next == null || !next.Equals(Transform, null, pen, rect, cornerRadius))
+ {
+ Add(new RectangleNode(Transform, null, pen, rect, cornerRadius));
+ }
+ else
+ {
+ ++Index;
+ }
+ }
+
+ public void DrawText(IBrush foreground, Point origin, IFormattedTextImpl text)
+ {
+ var next = NextNodeAs();
+
+ if (next == null || !next.Equals(Transform, foreground, origin, text))
+ {
+ Add(new TextNode(Transform, foreground, origin, text));
+ }
+ else
+ {
+ ++Index;
+ }
+ }
+
+ public void FillRectangle(IBrush brush, Rect rect, float cornerRadius = 0)
+ {
+ var next = NextNodeAs();
+
+ if (next == null || !next.Equals(Transform, brush, null, rect, cornerRadius))
+ {
+ Add(new RectangleNode(Transform, brush, null, rect, cornerRadius));
+ }
+ else
+ {
+ ++Index;
+ }
+ }
+
+ public void PopClip()
+ {
+ // TODO: Implement
+ }
+
+ public void PopGeometryClip()
+ {
+ // TODO: Implement
+ }
+
+ public void PopOpacity()
+ {
+ // TODO: Implement
+ }
+
+ public void PopOpacityMask()
+ {
+ // TODO: Implement
+ }
+
+ public void PushClip(Rect clip)
+ {
+ // TODO: Implement
+ }
+
+ public void PushGeometryClip(Geometry clip)
+ {
+ // TODO: Implement
+ }
+
+ public void PushOpacity(double opacity)
+ {
+ // TODO: Implement
+ }
+
+ public void PushOpacityMask(IBrush mask, Rect bounds)
+ {
+ // TODO: Implement
+ }
+
+ private void Add(ISceneNode node)
+ {
+ var index = Index;
+
+ if (index < Node.Children.Count)
+ {
+ Node.Children[index] = node;
+ }
+ else
+ {
+ Node.Children.Add(node);
+ }
+
+ ++Index;
+ }
+
+ private T NextNodeAs() where T : class, ISceneNode
+ {
+ return Index < Node.Children.Count ? Node.Children[Index] as T : null;
+ }
+
+ private void Pop()
+ {
+ var frame = _stack.Pop();
+ var children = frame.Node.Children;
+ var index = frame.Index;
+
+ if (children.Count > index)
+ {
+ children.RemoveRange(index, children.Count - index);
+ }
+ }
+
+ class Frame
+ {
+ public Frame(VisualNode node)
+ {
+ Node = node;
+ }
+
+ public VisualNode Node { get; }
+ public int Index { get; set; }
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs
new file mode 100644
index 0000000000..89850f0c0e
--- /dev/null
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs
@@ -0,0 +1,39 @@
+// 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.Media;
+using Avalonia.Platform;
+
+namespace Avalonia.Rendering.SceneGraph
+{
+ public class GeometryNode : ISceneNode
+ {
+ public GeometryNode(Matrix transform, IBrush brush, Pen pen, IGeometryImpl geometry)
+ {
+ Transform = transform;
+ Brush = brush;
+ Pen = pen;
+ Geometry = geometry;
+ }
+
+ public Matrix Transform { get; }
+ public IBrush Brush { get; }
+ public Pen Pen { get; }
+ public IGeometryImpl Geometry { get; }
+
+ public bool Equals(Matrix transform, IBrush brush, Pen pen, IGeometryImpl geometry)
+ {
+ return transform == Transform &&
+ Equals(brush, Brush) &&
+ pen == Pen &&
+ Equals(geometry, Geometry);
+ }
+
+ public void Render(IDrawingContextImpl context)
+ {
+ context.Transform = Transform;
+ context.DrawGeometry(Brush, Pen, Geometry);
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/ISceneNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/ISceneNode.cs
new file mode 100644
index 0000000000..073d9716d7
--- /dev/null
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/ISceneNode.cs
@@ -0,0 +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 System;
+using System.Collections.Generic;
+using Avalonia.Media;
+
+namespace Avalonia.Rendering.SceneGraph
+{
+ public interface ISceneNode
+ {
+ void Render(IDrawingContextImpl context);
+ }
+}
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
new file mode 100644
index 0000000000..17fcb7e219
--- /dev/null
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
@@ -0,0 +1,15 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Rendering.SceneGraph
+{
+ public interface IVisualNode : ISceneNode
+ {
+ IReadOnlyList Children { get; }
+ IVisual Visual { get; }
+ }
+}
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs
new file mode 100644
index 0000000000..f8aef2aed9
--- /dev/null
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs
@@ -0,0 +1,42 @@
+// 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.Media;
+using Avalonia.Platform;
+
+namespace Avalonia.Rendering.SceneGraph
+{
+ public class ImageNode : ISceneNode
+ {
+ public ImageNode(Matrix transform, IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect)
+ {
+ Transform = transform;
+ Source = source;
+ Opacity = opacity;
+ SourceRect = sourceRect;
+ DestRect = destRect;
+ }
+
+ public Matrix Transform { get; }
+ public IBitmapImpl Source { get; }
+ public double Opacity { get; }
+ public Rect SourceRect { get; }
+ public Rect DestRect { get; }
+
+ public bool Equals(Matrix transform, IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect)
+ {
+ return transform == Transform &&
+ Equals(source, Source) &&
+ opacity == Opacity &&
+ sourceRect == SourceRect &&
+ destRect == DestRect;
+ }
+
+ public void Render(IDrawingContextImpl context)
+ {
+ context.Transform = Transform;
+ context.DrawImage(Source, Opacity, SourceRect, DestRect);
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs
new file mode 100644
index 0000000000..9ff0fbde4e
--- /dev/null
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs
@@ -0,0 +1,35 @@
+// 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.Media;
+
+namespace Avalonia.Rendering.SceneGraph
+{
+ public class LineNode : ISceneNode
+ {
+ public LineNode(Matrix transform, Pen pen, Point p1, Point p2)
+ {
+ Transform = transform;
+ Pen = pen;
+ P1 = p1;
+ P2 = p2;
+ }
+
+ public Matrix Transform { get; }
+ public Pen Pen { get; }
+ public Point P1 { get; }
+ public Point P2 { get; }
+
+ public bool Equals(Matrix transform, Pen pen, Point p1, Point p2)
+ {
+ return transform == Transform && pen == Pen && p1 == P1 && p2 == P2;
+ }
+
+ public void Render(IDrawingContextImpl context)
+ {
+ context.Transform = Transform;
+ context.DrawLine(Pen, P1, P2);
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
new file mode 100644
index 0000000000..738cd189ad
--- /dev/null
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
@@ -0,0 +1,50 @@
+// 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.Media;
+
+namespace Avalonia.Rendering.SceneGraph
+{
+ public class RectangleNode : ISceneNode
+ {
+ public RectangleNode(Matrix transform, IBrush brush, Pen pen, Rect rect, float cornerRadius)
+ {
+ Transform = transform;
+ Brush = brush;
+ Pen = pen;
+ Rect = rect;
+ CornerRadius = cornerRadius;
+ }
+
+ public Matrix Transform { get; }
+ public IBrush Brush { get; }
+ public Pen Pen { get; }
+ public Rect Rect { get; }
+ public float CornerRadius { get; }
+
+ public bool Equals(Matrix transform, IBrush brush, Pen pen, Rect rect, float cornerRadius)
+ {
+ return transform == Transform &&
+ Equals(brush, Brush) &&
+ pen == Pen &&
+ rect == Rect &&
+ cornerRadius == CornerRadius;
+ }
+
+ public void Render(IDrawingContextImpl context)
+ {
+ context.Transform = Transform;
+
+ if (Brush != null)
+ {
+ context.FillRectangle(Brush, Rect, CornerRadius);
+ }
+
+ if (Pen != null)
+ {
+ context.DrawRectangle(Pen, Rect, CornerRadius);
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
new file mode 100644
index 0000000000..f0212d23c3
--- /dev/null
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
@@ -0,0 +1,72 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Rendering.SceneGraph
+{
+ public class Scene
+ {
+ private Dictionary _index;
+
+ public Scene(IVisual rootVisual)
+ : this(new VisualNode(rootVisual), new Dictionary())
+ {
+ }
+
+ internal Scene(VisualNode root, Dictionary index)
+ {
+ Contract.Requires(root != null);
+
+ _index = index;
+ Root = root;
+ }
+
+ public IVisualNode Root { get; }
+
+ public void Add(IVisualNode node)
+ {
+ _index.Add(node.Visual, node);
+ }
+
+ public IVisualNode FindNode(IVisual visual)
+ {
+ IVisualNode node;
+ _index.TryGetValue(visual, out node);
+ return node;
+ }
+
+ public Scene Clone()
+ {
+ var index = new Dictionary();
+ var root = (VisualNode)Clone((VisualNode)Root, null, index);
+ var result = new Scene(root, index);
+ return result;
+ }
+
+ private VisualNode Clone(VisualNode source, ISceneNode parent, Dictionary index)
+ {
+ var result = source.Clone();
+
+ index.Add(result.Visual, result);
+
+ foreach (var child in source.Children)
+ {
+ var visualNode = child as VisualNode;
+
+ if (visualNode != null)
+ {
+ result.Children.Add(Clone(visualNode, result, index));
+ }
+ else
+ {
+ result.Children.Add(child);
+ }
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
new file mode 100644
index 0000000000..b3ac55fe6a
--- /dev/null
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
@@ -0,0 +1,90 @@
+// 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.Media;
+using Avalonia.Threading;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Rendering.SceneGraph
+{
+ public static class SceneBuilder
+ {
+ public static Scene Update(Scene scene)
+ {
+ Dispatcher.UIThread.VerifyAccess();
+
+ scene = scene.Clone();
+
+ using (var impl = new DeferredDrawingContextImpl())
+ using (var context = new DrawingContext(impl))
+ {
+ Update(context, scene, scene.Root.Visual, null);
+ }
+
+ return scene;
+ }
+
+ private static void Update(DrawingContext context, Scene scene, IVisual visual, VisualNode parent)
+ {
+ var opacity = visual.Opacity;
+ var clipToBounds = visual.ClipToBounds;
+ var bounds = new Rect(visual.Bounds.Size);
+ var node = (VisualNode)scene.FindNode(visual) ?? CreateNode(visual, scene, parent);
+ var contextImpl = (DeferredDrawingContextImpl)context.PlatformImpl;
+
+ contextImpl.AddChild(node);
+
+ if (visual.IsVisible && opacity > 0)
+ {
+ var m = Matrix.CreateTranslation(visual.Bounds.Position);
+
+ var renderTransform = Matrix.Identity;
+
+ if (visual.RenderTransform != null)
+ {
+ var origin = visual.RenderTransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height));
+ var offset = Matrix.CreateTranslation(origin);
+ renderTransform = (-offset) * visual.RenderTransform.Value * (offset);
+ }
+
+ m = renderTransform * m;
+
+ using (contextImpl.Begin(node))
+ using (context.PushPostTransform(m))
+ using (context.PushTransformContainer())
+ {
+ node.Transform = contextImpl.Transform;
+ node.Bounds = bounds;
+ node.ClipToBounds = clipToBounds;
+ node.GeometryClip = visual.Clip;
+ node.Opacity = opacity;
+ node.OpacityMask = visual.OpacityMask;
+
+ visual.Render(context);
+
+#pragma warning disable 0618
+ var transformed = new TransformedBounds(bounds, new Rect(), context.CurrentContainerTransform);
+#pragma warning restore 0618
+
+ if (visual is Visual)
+ {
+ BoundsTracker.SetTransformedBounds((Visual)visual, transformed);
+ }
+
+ foreach (var child in visual.VisualChildren)
+ {
+ Update(context, scene, child, node);
+ }
+ }
+ }
+ }
+
+ private static VisualNode CreateNode(IVisual visual, Scene scene, VisualNode parent)
+ {
+ var node = new VisualNode(visual);
+ scene.Add(node);
+ return node;
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs
new file mode 100644
index 0000000000..3718f16af4
--- /dev/null
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs
@@ -0,0 +1,39 @@
+// 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.Media;
+using Avalonia.Platform;
+
+namespace Avalonia.Rendering.SceneGraph
+{
+ public class TextNode : ISceneNode
+ {
+ public TextNode(Matrix transform, IBrush foreground, Point origin, IFormattedTextImpl text)
+ {
+ Transform = transform;
+ Foreground = foreground;
+ Origin = origin;
+ Text = text;
+ }
+
+ public Matrix Transform { get; }
+ public IBrush Foreground { get; }
+ public Point Origin { get; }
+ public IFormattedTextImpl Text { get; }
+
+ public void Render(IDrawingContextImpl context)
+ {
+ context.Transform = Transform;
+ context.DrawText(Foreground, Origin, Text);
+ }
+
+ internal bool Equals(Matrix transform, IBrush foreground, Point origin, IFormattedTextImpl text)
+ {
+ return transform == Transform &&
+ Equals(foreground, Foreground) &&
+ origin == Origin &&
+ Equals(text, Text);
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
new file mode 100644
index 0000000000..3f4cea5c30
--- /dev/null
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
@@ -0,0 +1,65 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using Avalonia.Media;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Rendering.SceneGraph
+{
+ public class VisualNode : IVisualNode
+ {
+ public VisualNode(IVisual visual)
+ {
+ Children = new List();
+ Visual = visual;
+ }
+
+ public IVisual Visual { get; }
+ public Matrix Transform { get; set; }
+ public Rect Bounds { get; set; }
+ public bool ClipToBounds { get; set; }
+ public Geometry GeometryClip { get; set; }
+ public double Opacity { get; set; }
+ public IBrush OpacityMask { get; set; }
+ public List Children { get; }
+
+ IReadOnlyList IVisualNode.Children => Children;
+
+ public VisualNode Clone()
+ {
+ return new VisualNode(Visual);
+ }
+
+ public void Render(IDrawingContextImpl context)
+ {
+ context.Transform = Transform;
+
+ if (Opacity != 1)
+ {
+ context.PushOpacity(Opacity);
+ }
+
+ if (ClipToBounds)
+ {
+ context.PushClip(Bounds);
+ }
+
+ foreach (var child in Children)
+ {
+ child.Render(context);
+ }
+
+ if (ClipToBounds)
+ {
+ context.PopClip();
+ }
+
+ if (Opacity != 1)
+ {
+ context.PopOpacity();
+ }
+ }
+ }
+}
diff --git a/src/Gtk/Avalonia.Cairo/Avalonia.Cairo.v2.ncrunchproject b/src/Gtk/Avalonia.Cairo/Avalonia.Cairo.v2.ncrunchproject
index 30815b1937..1f00ab7c74 100644
--- a/src/Gtk/Avalonia.Cairo/Avalonia.Cairo.v2.ncrunchproject
+++ b/src/Gtk/Avalonia.Cairo/Avalonia.Cairo.v2.ncrunchproject
@@ -17,9 +17,9 @@
true
true
60000
-
-
-
+
+
+
AutoDetect
STA
x86
diff --git a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
index 9f9558ff76..eeae2bff1c 100644
--- a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
+++ b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
@@ -64,7 +64,7 @@
-
+
diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
index b43eef2fa9..2b465619ef 100644
--- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
+++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
@@ -29,9 +29,9 @@ namespace Avalonia.Direct2D1
private static readonly SharpDX.Direct2D1.Factory s_d2D1Factory =
#if DEBUG
- new SharpDX.Direct2D1.Factory(SharpDX.Direct2D1.FactoryType.SingleThreaded, SharpDX.Direct2D1.DebugLevel.Error);
+ new SharpDX.Direct2D1.Factory(SharpDX.Direct2D1.FactoryType.MultiThreaded, SharpDX.Direct2D1.DebugLevel.Error);
#else
- new SharpDX.Direct2D1.Factory(SharpDX.Direct2D1.FactoryType.SingleThreaded, SharpDX.Direct2D1.DebugLevel.None);
+ new SharpDX.Direct2D1.Factory(SharpDX.Direct2D1.FactoryType.MultiThreaded, SharpDX.Direct2D1.DebugLevel.None);
#endif
private static readonly SharpDX.DirectWrite.Factory s_dwfactory = new SharpDX.DirectWrite.Factory();
@@ -63,7 +63,7 @@ namespace Avalonia.Direct2D1
public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop)
{
- return new Renderer(root, renderLoop);
+ return new DeferredRenderer(root, renderLoop);
}
public IRenderTarget CreateRenderTarget(IPlatformHandle handle)
diff --git a/src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs b/src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs
index a1b9f85783..51b4eef62d 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs
@@ -11,14 +11,14 @@ namespace Avalonia.Direct2D1.Media
{
internal class AvaloniaTextRenderer : TextRenderer
{
- private readonly DrawingContext _context;
+ private readonly DrawingContextImpl _context;
private readonly SharpDX.Direct2D1.RenderTarget _renderTarget;
private readonly Brush _foreground;
public AvaloniaTextRenderer(
- DrawingContext context,
+ DrawingContextImpl context,
SharpDX.Direct2D1.RenderTarget target,
Brush foreground)
{
diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContext.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
similarity index 95%
rename from src/Windows/Avalonia.Direct2D1/Media/DrawingContext.cs
rename to src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
index 75a0f43d9f..2747ad7ca0 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContext.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using Avalonia.Media;
+using Avalonia.Platform;
using SharpDX;
using SharpDX.Direct2D1;
using SharpDX.Mathematics.Interop;
@@ -15,7 +16,7 @@ namespace Avalonia.Direct2D1.Media
///
/// Draws using Direct2D1.
///
- public class DrawingContext : IDrawingContextImpl, IDisposable
+ public class DrawingContextImpl : IDrawingContextImpl, IDisposable
{
///
/// The Direct2D1 render target.
@@ -28,11 +29,11 @@ namespace Avalonia.Direct2D1.Media
private SharpDX.DirectWrite.Factory _directWriteFactory;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The render target to draw to.
/// The DirectWrite factory.
- public DrawingContext(
+ public DrawingContextImpl(
SharpDX.Direct2D1.RenderTarget renderTarget,
SharpDX.DirectWrite.Factory directWriteFactory)
{
@@ -74,9 +75,9 @@ namespace Avalonia.Direct2D1.Media
/// The opacity to draw with.
/// The rect in the image to draw.
/// The rect in the output to draw to.
- public void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect)
+ public void DrawImage(IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect)
{
- BitmapImpl impl = (BitmapImpl)source.PlatformImpl;
+ BitmapImpl impl = (BitmapImpl)source;
Bitmap d2d = impl.GetDirect2DBitmap(_renderTarget);
_renderTarget.DrawBitmap(
d2d,
@@ -120,7 +121,7 @@ namespace Avalonia.Direct2D1.Media
/// The fill brush.
/// The stroke pen.
/// The geometry.
- public void DrawGeometry(IBrush brush, Pen pen, Avalonia.Media.Geometry geometry)
+ public void DrawGeometry(IBrush brush, Pen pen, IGeometryImpl geometry)
{
if (brush != null)
{
@@ -128,7 +129,7 @@ namespace Avalonia.Direct2D1.Media
{
if (d2dBrush.PlatformBrush != null)
{
- var impl = (GeometryImpl)geometry.PlatformImpl;
+ var impl = (GeometryImpl)geometry;
_renderTarget.FillGeometry(impl.Geometry, d2dBrush.PlatformBrush);
}
}
@@ -141,7 +142,7 @@ namespace Avalonia.Direct2D1.Media
{
if (d2dBrush.PlatformBrush != null)
{
- var impl = (GeometryImpl)geometry.PlatformImpl;
+ var impl = (GeometryImpl)geometry;
_renderTarget.DrawGeometry(impl.Geometry, d2dBrush.PlatformBrush, (float)pen.Thickness, d2dStroke);
}
}
@@ -187,11 +188,11 @@ namespace Avalonia.Direct2D1.Media
/// The foreground brush.
/// The upper-left corner of the text.
/// The text.
- public void DrawText(IBrush foreground, Point origin, FormattedText text)
+ public void DrawText(IBrush foreground, Point origin, IFormattedTextImpl text)
{
if (!string.IsNullOrEmpty(text.Text))
{
- var impl = (FormattedTextImpl)text.PlatformImpl;
+ var impl = (FormattedTextImpl)text;
using (var brush = CreateBrush(foreground, impl.Measure()))
using (var renderer = new AvaloniaTextRenderer(this, _renderTarget, brush.PlatformBrush))
diff --git a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs
index 45d06b5e27..b8e295e8ab 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs
@@ -23,6 +23,8 @@ namespace Avalonia.Direct2D1.Media
{
var factory = AvaloniaLocator.Current.GetService();
+ Text = text;
+
using (var format = new DWrite.TextFormat(
factory,
fontFamily,
@@ -58,6 +60,8 @@ namespace Avalonia.Direct2D1.Media
}
}
+ public string Text { get; }
+
public DWrite.TextLayout TextLayout { get; }
public void Dispose()
diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs
index eff832407e..219693ca23 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs
@@ -39,7 +39,7 @@ namespace Avalonia.Direct2D1.Media
base.Dispose();
}
- public Avalonia.Media.DrawingContext CreateDrawingContext() => new RenderTarget(_target).CreateDrawingContext();
+ public IDrawingContextImpl CreateDrawingContext() => new RenderTarget(_target).CreateDrawingContext();
}
}
diff --git a/src/Windows/Avalonia.Direct2D1/Media/TileBrushImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/TileBrushImpl.cs
index dd1aac3776..87f886008c 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/TileBrushImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/TileBrushImpl.cs
@@ -23,7 +23,7 @@ namespace Avalonia.Direct2D1.Media
using (var ctx = new RenderTarget(intermediate).CreateDrawingContext())
{
intermediate.Clear(null);
- helper.DrawIntermediate(ctx);
+ helper.DrawIntermediate(new DrawingContext(ctx));
}
PlatformBrush = new BitmapBrush(
diff --git a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs
index 180e1a7472..32dd4ac794 100644
--- a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs
+++ b/src/Windows/Avalonia.Direct2D1/RenderTarget.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 Avalonia.Direct2D1.Media;
+using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Win32.Interop;
using SharpDX;
@@ -80,7 +82,7 @@ namespace Avalonia.Direct2D1
/// Creates a drawing context for a rendering session.
///
/// An .
- public DrawingContext CreateDrawingContext()
+ public IDrawingContextImpl CreateDrawingContext()
{
var window = _renderTarget as WindowRenderTarget;
@@ -100,7 +102,7 @@ namespace Avalonia.Direct2D1
}
}
- return new DrawingContext(new Media.DrawingContext(_renderTarget, DirectWriteFactory));
+ return new DrawingContextImpl(_renderTarget, DirectWriteFactory);
}
public void Dispose()
diff --git a/tests/Avalonia.RenderTests/Avalonia.Direct2D1.RenderTests.v2.ncrunchproject b/tests/Avalonia.RenderTests/Avalonia.Direct2D1.RenderTests.v2.ncrunchproject
index 30815b1937..3f48b966ba 100644
--- a/tests/Avalonia.RenderTests/Avalonia.Direct2D1.RenderTests.v2.ncrunchproject
+++ b/tests/Avalonia.RenderTests/Avalonia.Direct2D1.RenderTests.v2.ncrunchproject
@@ -17,10 +17,13 @@
true
true
60000
-
-
-
+
+
+
AutoDetect
STA
x86
+
+
+
\ No newline at end of file
diff --git a/tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj b/tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj
index 9e7f739a1c..c885914279 100644
--- a/tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj
+++ b/tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj
@@ -81,6 +81,7 @@
+
diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
new file mode 100644
index 0000000000..1ccd8655bc
--- /dev/null
+++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
@@ -0,0 +1,130 @@
+using System;
+using Avalonia.Controls;
+using Avalonia.Media;
+using Avalonia.Rendering.SceneGraph;
+using Avalonia.UnitTests;
+using Xunit;
+
+namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
+{
+ public class SceneBuilderTests
+ {
+ [Fact]
+ public void Should_Build_Initial_Scene()
+ {
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
+ {
+ Border border;
+ TextBlock textBlock;
+ var tree = new TestRoot
+ {
+ Child = border = new Border
+ {
+ Background = Brushes.Red,
+ Child = textBlock = new TextBlock
+ {
+ Text = "Hello World",
+ }
+ }
+ };
+
+ var initial = new Scene(tree);
+ var result = SceneBuilder.Update(initial);
+
+ Assert.NotSame(initial, result);
+ Assert.Equal(1, result.Root.Children.Count);
+
+ var borderNode = (VisualNode)result.Root.Children[0];
+ Assert.Same(borderNode, result.FindNode(border));
+ Assert.Same(border, borderNode.Visual);
+ Assert.Equal(2, borderNode.Children.Count);
+
+ var backgroundNode = (RectangleNode)borderNode.Children[0];
+ Assert.Equal(Brushes.Red, backgroundNode.Brush);
+
+ var textBlockNode = (VisualNode)borderNode.Children[1];
+ Assert.Same(textBlockNode, result.FindNode(textBlock));
+ Assert.Same(textBlock, textBlockNode.Visual);
+ Assert.Equal(1, textBlockNode.Children.Count);
+
+ var textNode = (TextNode)textBlockNode.Children[0];
+ Assert.NotNull(textNode.Text);
+ }
+ }
+
+ [Fact]
+ public void Should_Update_Border_Background_Node()
+ {
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
+ {
+ Border border;
+ TextBlock textBlock;
+ var tree = new TestRoot
+ {
+ Child = border = new Border
+ {
+ Background = Brushes.Red,
+ Child = textBlock = new TextBlock
+ {
+ Text = "Hello World",
+ }
+ }
+ };
+
+ var initial = SceneBuilder.Update(new Scene(tree));
+ var initialBackgroundNode = initial.FindNode(border).Children[0];
+ var initialTextNode = initial.FindNode(textBlock).Children[0];
+
+ Assert.NotNull(initialBackgroundNode);
+ Assert.NotNull(initialTextNode);
+
+ border.Background = Brushes.Green;
+ var result = SceneBuilder.Update(initial);
+
+ Assert.NotSame(initial, result);
+ var borderNode = (VisualNode)result.Root.Children[0];
+ Assert.Same(border, borderNode.Visual);
+
+ var backgroundNode = (RectangleNode)borderNode.Children[0];
+ Assert.NotSame(initialBackgroundNode, backgroundNode);
+ Assert.Equal(Brushes.Green, backgroundNode.Brush);
+
+ var textBlockNode = (VisualNode)borderNode.Children[1];
+ Assert.Same(textBlock, textBlockNode.Visual);
+
+ var textNode = (TextNode)textBlockNode.Children[0];
+ Assert.Same(initialTextNode, textNode);
+ }
+ }
+
+ [Fact]
+ public void Should_Update_When_Control_Removed()
+ {
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
+ {
+ Border border;
+ TextBlock textBlock;
+ var tree = new TestRoot
+ {
+ Child = border = new Border
+ {
+ Background = Brushes.Red,
+ Child = textBlock = new TextBlock
+ {
+ Text = "Hello World",
+ }
+ }
+ };
+
+ var initial = SceneBuilder.Update(new Scene(tree));
+
+ border.Child = null;
+ var result = SceneBuilder.Update(initial);
+
+ Assert.NotSame(initial, result);
+ var borderNode = (VisualNode)result.Root.Children[0];
+ Assert.Equal(1, borderNode.Children.Count);
+ }
+ }
+ }
+}
diff --git a/tests/Avalonia.Visuals.UnitTests/TestRoot.cs b/tests/Avalonia.Visuals.UnitTests/TestRoot.cs
index bdcbae59ce..b291afb6d3 100644
--- a/tests/Avalonia.Visuals.UnitTests/TestRoot.cs
+++ b/tests/Avalonia.Visuals.UnitTests/TestRoot.cs
@@ -23,7 +23,7 @@ namespace Avalonia.Visuals.UnitTests
public IRenderer Renderer
{
- get { throw new NotImplementedException(); }
+ get { return AvaloniaLocator.Current.GetService(); }
}
public Point PointToClient(Point p)