diff --git a/src/Avalonia.Visuals/Avalonia.Visuals.csproj b/src/Avalonia.Visuals/Avalonia.Visuals.csproj index 83c65c34a5..24dde24907 100644 --- a/src/Avalonia.Visuals/Avalonia.Visuals.csproj +++ b/src/Avalonia.Visuals/Avalonia.Visuals.csproj @@ -110,6 +110,7 @@ + diff --git a/src/Avalonia.Visuals/Rendering/DirtyRects.cs b/src/Avalonia.Visuals/Rendering/DirtyRects.cs new file mode 100644 index 0000000000..4101dd2d8b --- /dev/null +++ b/src/Avalonia.Visuals/Rendering/DirtyRects.cs @@ -0,0 +1,36 @@ +// 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; +using System.Collections.Generic; + +namespace Avalonia.Rendering +{ + public class DirtyRects + { + private List _rects = new List(); + + public void Add(Rect rect) + { + for (var i = 0; i < _rects.Count; ++i) + { + var intersection = _rects[i].Intersect(rect); + + if (intersection != Rect.Empty) + { + _rects[i] = intersection; + return; + } + } + + _rects.Add(rect); + } + + public IList Coalesce() + { + // TODO: Final coalesce + return _rects; + } + } +} diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index 7e4dee81a4..2907832f0b 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -3,11 +3,9 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Reactive.Disposables; using Avalonia.Media; using Avalonia.Platform; -using Avalonia.VisualTree; namespace Avalonia.Rendering.SceneGraph { @@ -15,10 +13,22 @@ namespace Avalonia.Rendering.SceneGraph { private Stack _stack = new Stack(); + public DeferredDrawingContextImpl() + : this(new DirtyRects()) + { + } + + public DeferredDrawingContextImpl(DirtyRects dirty) + { + Dirty = dirty; + } + public Matrix Transform { get; set; } private VisualNode Node => _stack.Peek().Node; + public DirtyRects Dirty { get; } + private int Index { get { return _stack.Peek().Index; } @@ -206,7 +216,20 @@ namespace Avalonia.Rendering.SceneGraph return Index < Node.Children.Count ? Node.Children[Index] as T : null; } - private void Pop() => _stack.Pop(); + private void Pop() + { + foreach (var child in Node.Children) + { + var geometry = child as IGeometryNode; + + if (geometry != null) + { + Dirty.Add(geometry.Bounds); + } + } + + _stack.Pop(); + } class Frame { diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs index dedb1736e2..2371392880 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs @@ -11,12 +11,14 @@ namespace Avalonia.Rendering.SceneGraph { public GeometryNode(Matrix transform, IBrush brush, Pen pen, IGeometryImpl geometry) { + Bounds = geometry.Bounds * transform; Transform = transform; Brush = brush; Pen = pen; Geometry = geometry; } + public Rect Bounds { get; } public Matrix Transform { get; } public IBrush Brush { get; } public Pen Pen { get; } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/IGeometryNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/IGeometryNode.cs index 4a9daf0ec9..ee8c1aa81e 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/IGeometryNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/IGeometryNode.cs @@ -10,6 +10,11 @@ namespace Avalonia.Rendering.SceneGraph /// public interface IGeometryNode : ISceneNode { + /// + /// Gets the bounds of the node in global coordinates. + /// + Rect Bounds { get; } + /// /// Hit test the geometry in this node. /// diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs index 557476636d..86523c9a94 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs @@ -11,6 +11,7 @@ namespace Avalonia.Rendering.SceneGraph { public ImageNode(Matrix transform, IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect) { + Bounds = destRect * transform; Transform = transform; Source = source; Opacity = opacity; @@ -18,6 +19,7 @@ namespace Avalonia.Rendering.SceneGraph DestRect = destRect; } + public Rect Bounds { get; } public Matrix Transform { get; } public IBitmapImpl Source { get; } public double Opacity { get; } @@ -39,9 +41,6 @@ namespace Avalonia.Rendering.SceneGraph context.DrawImage(Source, Opacity, SourceRect, DestRect); } - public bool HitTest(Point p) - { - return (DestRect * Transform).Contains(p); - } + public bool HitTest(Point p) => Bounds.Contains(p); } } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs index d49b75c370..346c740199 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs @@ -10,12 +10,14 @@ namespace Avalonia.Rendering.SceneGraph { public LineNode(Matrix transform, Pen pen, Point p1, Point p2) { + Bounds = new Rect(P1, P2); Transform = transform; Pen = pen; P1 = p1; P2 = p2; } + public Rect Bounds { get; } public Matrix Transform { get; } public Pen Pen { get; } public Point P1 { get; } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs index 6c37c30f9c..72845f6d3c 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs @@ -10,6 +10,7 @@ namespace Avalonia.Rendering.SceneGraph { public RectangleNode(Matrix transform, IBrush brush, Pen pen, Rect rect, float cornerRadius) { + Bounds = rect * transform; Transform = transform; Brush = brush; Pen = pen; @@ -17,6 +18,7 @@ namespace Avalonia.Rendering.SceneGraph CornerRadius = cornerRadius; } + public Rect Bounds { get; } public Matrix Transform { get; } public IBrush Brush { get; } public Pen Pen { get; } @@ -47,10 +49,6 @@ namespace Avalonia.Rendering.SceneGraph } } - public bool HitTest(Point p) - { - // TODO: Only test interior when Brush != null. - return (Rect * Transform).Contains(p); - } + public bool HitTest(Point p) => Bounds.Contains(p); } } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs index 46ea25626f..b6fddd869d 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs @@ -6,6 +6,7 @@ using System.Linq; using Avalonia.Media; using Avalonia.Threading; using Avalonia.VisualTree; +using System.Collections.Generic; namespace Avalonia.Rendering.SceneGraph { @@ -13,6 +14,7 @@ namespace Avalonia.Rendering.SceneGraph { public static void UpdateAll(Scene scene) { + Contract.Requires(scene != null); Dispatcher.UIThread.VerifyAccess(); using (var impl = new DeferredDrawingContextImpl()) @@ -24,6 +26,15 @@ namespace Avalonia.Rendering.SceneGraph public static bool Update(Scene scene, IVisual visual) { + var dirty = new DirtyRects(); + return Update(scene, visual, dirty); + } + + public static bool Update(Scene scene, IVisual visual, DirtyRects dirty) + { + Contract.Requires(scene != null); + Contract.Requires(visual != null); + Contract.Requires(dirty != null); Dispatcher.UIThread.VerifyAccess(); var node = (VisualNode)scene.FindNode(visual); @@ -44,7 +55,7 @@ namespace Avalonia.Rendering.SceneGraph // descendents too. var recurse = node.Visual != visual; - using (var impl = new DeferredDrawingContextImpl()) + using (var impl = new DeferredDrawingContextImpl(dirty)) using (var context = new DrawingContext(impl)) { if (node.Parent != null) @@ -65,7 +76,7 @@ namespace Avalonia.Rendering.SceneGraph // The control has been removed so remove it from its parent and deindex the // node and its descendents. ((VisualNode)node.Parent)?.Children.Remove(node); - Deindex(scene, node); + Deindex(scene, node, dirty); return true; } } @@ -76,7 +87,7 @@ namespace Avalonia.Rendering.SceneGraph // node and its descendents. var trim = FindFirstDeadAncestor(scene, node); ((VisualNode)trim.Parent).Children.Remove(trim); - Deindex(scene, trim); + Deindex(scene, trim, dirty); return true; } @@ -170,18 +181,31 @@ namespace Avalonia.Rendering.SceneGraph return node; } - private static void Deindex(Scene scene, VisualNode node) + private static IList Deindex(Scene scene, VisualNode node) + { + var dirty = new DirtyRects(); + Deindex(scene, node, dirty); + return dirty.Coalesce(); + } + + private static void Deindex(Scene scene, VisualNode node, DirtyRects dirty) { scene.Remove(node); node.SubTreeUpdated = true; foreach (var child in node.Children) { - var visualChild = child as VisualNode; + var geometry = child as IGeometryNode; + var visual = child as VisualNode; + + if (geometry != null) + { + dirty.Add(geometry.Bounds); + } - if (visualChild != null) + if (visual != null) { - Deindex(scene, visualChild); + Deindex(scene, visual, dirty); } } } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs index fbe9bb7176..749b44eb9d 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs @@ -11,12 +11,14 @@ namespace Avalonia.Rendering.SceneGraph { public TextNode(Matrix transform, IBrush foreground, Point origin, IFormattedTextImpl text) { + Bounds = new Rect(origin, text.Measure()) * transform; Transform = transform; Foreground = foreground; Origin = origin; Text = text; } + public Rect Bounds { get; } public Matrix Transform { get; } public IBrush Foreground { get; } public Point Origin { get; } @@ -36,9 +38,6 @@ namespace Avalonia.Rendering.SceneGraph Equals(text, Text); } - public bool HitTest(Point p) - { - return (new Rect(Origin, Text.Measure()) * Transform).Contains(p); - } + public bool HitTest(Point p) => Bounds.Contains(p); } } diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs index f52efc10bb..b62be07dd4 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs @@ -72,7 +72,10 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph Child = new Border { Margin = new Thickness(10, 20, 30, 40), - Child = canvas = new Canvas(), + Child = canvas = new Canvas + { + Background = Brushes.AliceBlue, + } } }; @@ -282,7 +285,10 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph Background = Brushes.Red, Child = decorator = new Decorator { - Child = canvas = new Canvas() + Child = canvas = new Canvas + { + Background = Brushes.AliceBlue, + } } } };