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