Browse Source
Whole tree is currently being updated and rendered still though and lots doesn't even build.scenegraph-after-breakage
committed by
Steven Kirk
36 changed files with 1064 additions and 93 deletions
@ -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;
|
||||
|
//}
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue