Browse Source

Initial implementation of low-level scene graph.

Whole tree is currently being updated and rendered still though and lots doesn't even build.
scenegraph-after-breakage
Steven Kirk 10 years ago
committed by Steven Kirk
parent
commit
ada15eba00
  1. 2
      samples/RenderTest/MainWindow.xaml.cs
  2. 2
      src/Avalonia.Controls/Shapes/Shape.cs
  3. 28
      src/Avalonia.Visuals/Avalonia.Visuals.csproj
  4. 41
      src/Avalonia.Visuals/Media/DrawingContext.cs
  5. 7
      src/Avalonia.Visuals/Media/IDrawingContextImpl.cs
  6. 2
      src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs
  7. 5
      src/Avalonia.Visuals/Platform/IFormattedTextImpl.cs
  8. 2
      src/Avalonia.Visuals/Platform/IRenderTarget.cs
  9. 128
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  10. 3
      src/Avalonia.Visuals/Rendering/IRenderer.cs
  11. 3
      src/Avalonia.Visuals/Rendering/Renderer.cs
  12. 56
      src/Avalonia.Visuals/Rendering/RendererMixin.cs
  13. 222
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  14. 39
      src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs
  15. 14
      src/Avalonia.Visuals/Rendering/SceneGraph/ISceneNode.cs
  16. 15
      src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
  17. 42
      src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs
  18. 35
      src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs
  19. 50
      src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
  20. 72
      src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
  21. 90
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  22. 39
      src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs
  23. 65
      src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
  24. 6
      src/Gtk/Avalonia.Cairo/Avalonia.Cairo.v2.ncrunchproject
  25. 2
      src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
  26. 6
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  27. 4
      src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs
  28. 21
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  29. 4
      src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs
  30. 2
      src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs
  31. 2
      src/Windows/Avalonia.Direct2D1/Media/TileBrushImpl.cs
  32. 6
      src/Windows/Avalonia.Direct2D1/RenderTarget.cs
  33. 9
      tests/Avalonia.RenderTests/Avalonia.Direct2D1.RenderTests.v2.ncrunchproject
  34. 1
      tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj
  35. 130
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
  36. 2
      tests/Avalonia.Visuals.UnitTests/TestRoot.cs

2
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()

2
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);
}
}

28
src/Avalonia.Visuals/Avalonia.Visuals.csproj

@ -100,15 +100,34 @@
<Compile Include="Media\FormattedTextLine.cs" />
<Compile Include="Media\FormattedText.cs" />
<Compile Include="Media\Geometry.cs" />
<Compile Include="Media\IDrawingContext.cs" />
<Compile Include="Media\IDrawingContextImpl.cs" />
<Compile Include="Platform\ExportRenderingSubsystemAttribute.cs" />
<Compile Include="Point.cs" />
<Compile Include="Rect.cs" />
<Compile Include="RelativePoint.cs" />
<Compile Include="RelativeRect.cs" />
<Compile Include="Rendering\DeferredRenderer.cs" />
<Compile Include="Rendering\IRenderer.cs" />
<Compile Include="Rendering\IRendererFactory.cs" />
<Compile Include="Rendering\IRenderLoop.cs" />
<Compile Include="Rendering\Renderer.cs" />
<Compile Include="Rendering\RendererMixin.cs" />
<Compile Include="Rendering\DefaultRenderLoop.cs" />
<Compile Include="Rendering\SceneGraph\DeferredDrawingContextImpl.cs" />
<Compile Include="Rendering\SceneGraph\GeometryNode.cs" />
<Compile Include="Rendering\SceneGraph\ImageNode.cs" />
<Compile Include="Rendering\SceneGraph\ISceneNode.cs" />
<Compile Include="Rendering\SceneGraph\IVisualNode.cs" />
<Compile Include="Rendering\SceneGraph\LineNode.cs" />
<Compile Include="Rendering\SceneGraph\RectangleNode.cs" />
<Compile Include="Rendering\SceneGraph\Scene.cs" />
<Compile Include="Rendering\SceneGraph\SceneBuilder.cs" />
<Compile Include="Rendering\SceneGraph\TextNode.cs" />
<Compile Include="Rendering\SceneGraph\VisualNode.cs" />
<Compile Include="RenderTargetCorruptedException.cs" />
<Compile Include="Size.cs" />
<Compile Include="Thickness.cs" />
<Compile Include="Vector.cs" />
<Compile Include="VisualTree\IVisual.cs" />
<Compile Include="Media\Imaging\Bitmap.cs" />
<Compile Include="Media\Imaging\IBitmap.cs" />
@ -131,7 +150,6 @@
<Compile Include="Media\ImageBush.cs" />
<Compile Include="Media\VisualBrush.cs" />
<Compile Include="Platform\IPlatformSettings.cs" />
<Compile Include="RelativePoint.cs" />
<Compile Include="Platform\IFormattedTextImpl.cs" />
<Compile Include="Platform\IBitmapImpl.cs" />
<Compile Include="Platform\IGeometryImpl.cs" />
@ -140,14 +158,8 @@
<Compile Include="Platform\IRenderTargetBitmapImpl.cs" />
<Compile Include="Platform\IStreamGeometryContextImpl.cs" />
<Compile Include="Platform\IStreamGeometryImpl.cs" />
<Compile Include="Point.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RelativeRect.cs" />
<Compile Include="Rect.cs" />
<Compile Include="Rendering\IRenderRoot.cs" />
<Compile Include="Size.cs" />
<Compile Include="Thickness.cs" />
<Compile Include="Vector.cs" />
<Compile Include="VisualExtensions.cs" />
<Compile Include="Visual.cs" />
<Compile Include="VisualTreeAttachmentEventArgs.cs" />

41
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
/// <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);
=> PlatformImpl.DrawImage(source.PlatformImpl, opacity, sourceRect, destRect);
/// <summary>
/// Draws a line.
@ -87,7 +83,7 @@ namespace Avalonia.Media
/// <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);
public void DrawLine(Pen pen, Point p1, Point p2) => PlatformImpl.DrawLine(pen, p1, p2);
/// <summary>
/// Draws a geometry.
@ -95,7 +91,8 @@ namespace Avalonia.Media
/// <param name="brush">The fill brush.</param>
/// <param name="pen">The stroke pen.</param>
/// <param name="geometry">The geometry.</param>
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);
/// <summary>
/// Draws the outline of a rectangle.
@ -104,7 +101,7 @@ namespace Avalonia.Media
/// <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);
=> PlatformImpl.DrawRectangle(pen, rect, cornerRadius);
/// <summary>
/// Draws text.
@ -113,7 +110,7 @@ namespace Avalonia.Media
/// <param name="origin">The upper-left corner of the text.</param>
/// <param name="text">The text.</param>
public void DrawText(IBrush foreground, Point origin, FormattedText text)
=> _impl.DrawText(foreground, origin, text);
=> PlatformImpl.DrawText(foreground, origin, text.PlatformImpl);
/// <summary>
/// Draws a filled rectangle.
@ -122,7 +119,7 @@ namespace Avalonia.Media
/// <param name="rect">The rectangle bounds.</param>
/// <param name="cornerRadius">The corner radius.</param>
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
/// <returns>A disposable used to undo the clip rectangle.</returns>
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<ArgumentNullException>(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
/// <returns>A disposable to undo the opacity mask.</returns>
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();
}
}
}

7
src/Avalonia.Visuals/Media/IDrawingContext.cs → 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
/// <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>
void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect);
void DrawImage(IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect);
/// <summary>
/// Draws a line.
@ -39,7 +40,7 @@ namespace Avalonia.Media
/// <param name="brush">The fill brush.</param>
/// <param name="pen">The stroke pen.</param>
/// <param name="geometry">The geometry.</param>
void DrawGeometry(IBrush brush, Pen pen, Geometry geometry);
void DrawGeometry(IBrush brush, Pen pen, IGeometryImpl geometry);
/// <summary>
/// Draws the outline of a rectangle.
@ -55,7 +56,7 @@ namespace Avalonia.Media
/// <param name="foreground">The foreground brush.</param>
/// <param name="origin">The upper-left corner of the text.</param>
/// <param name="text">The text.</param>
void DrawText(IBrush foreground, Point origin, FormattedText text);
void DrawText(IBrush foreground, Point origin, IFormattedTextImpl text);
/// <summary>
/// Draws a filled rectangle.

2
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();
}
}

5
src/Avalonia.Visuals/Platform/IFormattedTextImpl.cs

@ -17,6 +17,11 @@ namespace Avalonia.Platform
/// </summary>
Size Constraint { get; set; }
/// <summary>
/// Gets the text.
/// </summary>
string Text { get; }
/// <summary>
/// Gets the lines in the text.
/// </summary>

2
src/Avalonia.Visuals/Platform/IRenderTarget.cs

@ -17,6 +17,6 @@ namespace Avalonia.Platform
/// <summary>
/// Creates an <see cref="DrawingContext"/> for a rendering session.
/// </summary>
DrawingContext CreateDrawingContext();
IDrawingContextImpl CreateDrawingContext();
}
}

128
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<ArgumentNullException>(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;
//}
}
}
}

3
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);
}
}

3
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)

56
src/Avalonia.Visuals/Rendering/RendererMixin.cs

@ -42,34 +42,34 @@ namespace Avalonia.Rendering
/// <param name="visual">The visual to render.</param>
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);
//// }
//// }
////}
}
/// <summary>

222
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<Frame> _stack = new Stack<Frame>();
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<VisualNode>();
if (next == null || next != visualNode)
{
Add(visualNode);
}
else
{
++Index;
}
}
}
public void DrawGeometry(IBrush brush, Pen pen, IGeometryImpl geometry)
{
var next = NextNodeAs<GeometryNode>();
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<ImageNode>();
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<LineNode>();
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<RectangleNode>();
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<TextNode>();
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<RectangleNode>();
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<T>() 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; }
}
}
}

39
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);
}
}
}

14
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);
}
}

15
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<ISceneNode> Children { get; }
IVisual Visual { get; }
}
}

42
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);
}
}
}

35
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);
}
}
}

50
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);
}
}
}
}

72
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<IVisual, IVisualNode> _index;
public Scene(IVisual rootVisual)
: this(new VisualNode(rootVisual), new Dictionary<IVisual, IVisualNode>())
{
}
internal Scene(VisualNode root, Dictionary<IVisual, IVisualNode> index)
{
Contract.Requires<ArgumentNullException>(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<IVisual, IVisualNode>();
var root = (VisualNode)Clone((VisualNode)Root, null, index);
var result = new Scene(root, index);
return result;
}
private VisualNode Clone(VisualNode source, ISceneNode parent, Dictionary<IVisual, IVisualNode> 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;
}
}
}

90
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;
}
}
}

39
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);
}
}
}

65
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<ISceneNode>();
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<ISceneNode> Children { get; }
IReadOnlyList<ISceneNode> 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();
}
}
}
}

6
src/Gtk/Avalonia.Cairo/Avalonia.Cairo.v2.ncrunchproject

@ -17,9 +17,9 @@
<DetectStackOverflow>true</DetectStackOverflow>
<IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
<DefaultTestTimeout>60000</DefaultTestTimeout>
<UseBuildConfiguration />
<UseBuildPlatform />
<ProxyProcessPath />
<UseBuildConfiguration></UseBuildConfiguration>
<UseBuildPlatform></UseBuildPlatform>
<ProxyProcessPath></ProxyProcessPath>
<UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
<MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
<BuildProcessArchitecture>x86</BuildProcessArchitecture>

2
src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj

@ -64,7 +64,7 @@
<Compile Include="Disposable.cs" />
<Compile Include="Media\BrushImpl.cs" />
<Compile Include="Media\BrushWrapper.cs" />
<Compile Include="Media\DrawingContext.cs" />
<Compile Include="Media\DrawingContextImpl.cs" />
<Compile Include="Media\Imaging\RenderTargetBitmapImpl.cs" />
<Compile Include="Media\Imaging\BitmapImpl.cs" />
<Compile Include="Media\RadialGradientBrushImpl.cs" />

6
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)

4
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)
{

21
src/Windows/Avalonia.Direct2D1/Media/DrawingContext.cs → 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
/// <summary>
/// Draws using Direct2D1.
/// </summary>
public class DrawingContext : IDrawingContextImpl, IDisposable
public class DrawingContextImpl : IDrawingContextImpl, IDisposable
{
/// <summary>
/// The Direct2D1 render target.
@ -28,11 +29,11 @@ namespace Avalonia.Direct2D1.Media
private SharpDX.DirectWrite.Factory _directWriteFactory;
/// <summary>
/// Initializes a new instance of the <see cref="DrawingContext"/> class.
/// Initializes a new instance of the <see cref="DrawingContextImpl"/> class.
/// </summary>
/// <param name="renderTarget">The render target to draw to.</param>
/// <param name="directWriteFactory">The DirectWrite factory.</param>
public DrawingContext(
public DrawingContextImpl(
SharpDX.Direct2D1.RenderTarget renderTarget,
SharpDX.DirectWrite.Factory directWriteFactory)
{
@ -74,9 +75,9 @@ namespace Avalonia.Direct2D1.Media
/// <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)
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
/// <param name="brush">The fill brush.</param>
/// <param name="pen">The stroke pen.</param>
/// <param name="geometry">The geometry.</param>
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
/// <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(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))

4
src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs

@ -23,6 +23,8 @@ namespace Avalonia.Direct2D1.Media
{
var factory = AvaloniaLocator.Current.GetService<DWrite.Factory>();
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()

2
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();
}
}

2
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(

6
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.
/// </summary>
/// <returns>An <see cref="Avalonia.Media.DrawingContext"/>.</returns>
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()

9
tests/Avalonia.RenderTests/Avalonia.Direct2D1.RenderTests.v2.ncrunchproject

@ -17,10 +17,13 @@
<DetectStackOverflow>true</DetectStackOverflow>
<IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
<DefaultTestTimeout>60000</DefaultTestTimeout>
<UseBuildConfiguration />
<UseBuildPlatform />
<ProxyProcessPath />
<UseBuildConfiguration></UseBuildConfiguration>
<UseBuildPlatform></UseBuildPlatform>
<ProxyProcessPath></ProxyProcessPath>
<UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
<MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
<BuildProcessArchitecture>x86</BuildProcessArchitecture>
<IgnoredTests>
<AllTestsSelector />
</IgnoredTests>
</ProjectConfiguration>

1
tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj

@ -81,6 +81,7 @@
<Compile Include="Media\PathMarkupParserTests.cs" />
<Compile Include="RelativeRectComparer.cs" />
<Compile Include="RelativeRectTests.cs" />
<Compile Include="Rendering\SceneGraph\SceneBuilderTests.cs" />
<Compile Include="ThicknessTests.cs" />
<Compile Include="Media\BrushTests.cs" />
<Compile Include="Media\ColorTests.cs" />

130
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);
}
}
}
}

2
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<IRenderer>(); }
}
public Point PointToClient(Point p)

Loading…
Cancel
Save