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,
+ }
}
}
};