using System; using System.Linq; using Avalonia.Controls; using Avalonia.Media; using Avalonia.Rendering.SceneGraph; using Avalonia.UnitTests; using Avalonia.VisualTree; using Xunit; using Avalonia.Layout; 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 { Width = 100, Height = 100, Background = Brushes.Red, Child = textBlock = new TextBlock { Text = "Hello World", } } }; tree.Measure(Size.Infinity); tree.Arrange(new Rect(tree.DesiredSize)); var result = new Scene(tree); SceneBuilder.UpdateAll(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_Respect_Margin_For_ClipBounds() { using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) { Canvas canvas; var tree = new TestRoot { Width = 200, Height = 300, Child = new Border { Margin = new Thickness(10, 20, 30, 40), Child = canvas = new Canvas(), } }; tree.Measure(Size.Infinity); tree.Arrange(new Rect(tree.DesiredSize)); var result = new Scene(tree); SceneBuilder.UpdateAll(result); var canvasNode = result.FindNode(canvas); Assert.Equal(new Rect(10, 20, 160, 240), canvasNode.ClipBounds); // Initial ClipBounds are correct, make sure they're still correct after updating canvas. result = result.Clone(); Assert.True(SceneBuilder.Update(result, canvas)); canvasNode = result.FindNode(canvas); Assert.Equal(new Rect(10, 20, 160, 240), canvasNode.ClipBounds); } } [Fact] public void Should_Respect_ZIndex() { using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) { Border front; Border back; var tree = new TestRoot { Child = new Panel { Children = { (front = new Border { ZIndex = 1, }), (back = new Border { ZIndex = 0, }), } } }; var result = new Scene(tree); SceneBuilder.UpdateAll(result); var panelNode = result.FindNode(tree.Child); var expected = new IVisual[] { back, front }; var actual = panelNode.Children.OfType().Select(x => x.Visual).ToArray(); Assert.Equal(expected, actual); } } [Fact] public void ClipBounds_Should_Be_In_Global_Coordinates() { using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) { Border target; var tree = new TestRoot { Child = new Decorator { Margin = new Thickness(24, 26), Child = target = new Border { Margin = new Thickness(26, 24), Width = 100, Height = 100, } } }; tree.Measure(Size.Infinity); tree.Arrange(new Rect(tree.DesiredSize)); var result = new Scene(tree); SceneBuilder.UpdateAll(result); var targetNode = result.FindNode(target); Assert.Equal(new Rect(50, 50, 100, 100), targetNode.ClipBounds); } } [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 { Width = 100, Height = 100, Background = Brushes.Red, Child = textBlock = new TextBlock { Foreground = Brushes.Green, Text = "Hello World", } } }; tree.Measure(Size.Infinity); tree.Arrange(new Rect(tree.DesiredSize)); var initial = new Scene(tree); SceneBuilder.UpdateAll(initial); 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 = initial.Clone(); SceneBuilder.Update(result, border); 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_Added() { using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) { Border border; var tree = new TestRoot { Width = 100, Height = 100, Child = border = new Border { Background = Brushes.Red, } }; Canvas canvas; var decorator = new Decorator { Child = canvas = new Canvas(), }; tree.Measure(Size.Infinity); tree.Arrange(new Rect(tree.DesiredSize)); var initial = new Scene(tree); SceneBuilder.UpdateAll(initial); border.Child = decorator; var result = initial.Clone(); Assert.True(SceneBuilder.Update(result, decorator)); // Updating canvas should result in no-op as it should have been updated along // with decorator as part of the add opeation. Assert.False(SceneBuilder.Update(result, canvas)); var borderNode = (VisualNode)result.Root.Children[0]; Assert.Equal(2, borderNode.Children.Count); var decoratorNode = (VisualNode)borderNode.Children[1]; Assert.Same(decorator, decoratorNode.Visual); Assert.Same(decoratorNode, result.FindNode(decorator)); var canvasNode = (VisualNode)decoratorNode.Children[0]; Assert.Same(canvas, canvasNode.Visual); Assert.Same(canvasNode, result.FindNode(canvas)); } } [Fact] public void Should_Update_When_Control_Removed() { using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) { Border border; Decorator decorator; Canvas canvas; var tree = new TestRoot { Width = 100, Height = 100, Child = border = new Border { Background = Brushes.Red, Child = decorator = new Decorator { Child = canvas = new Canvas() } } }; tree.Measure(Size.Infinity); tree.Arrange(new Rect(tree.DesiredSize)); var initial = new Scene(tree); SceneBuilder.UpdateAll(initial); border.Child = null; var result = initial.Clone(); Assert.True(SceneBuilder.Update(result, decorator)); Assert.False(SceneBuilder.Update(result, canvas)); var borderNode = (VisualNode)result.Root.Children[0]; Assert.Equal(1, borderNode.Children.Count); Assert.Null(result.FindNode(decorator)); } } [Fact] public void Should_Update_When_Control_Made_Invisible() { using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) { Decorator decorator; Border border; Canvas canvas; var tree = new TestRoot { Width = 100, Height = 100, Child = decorator = new Decorator { Child = border = new Border { Background = Brushes.Red, Child = canvas = new Canvas(), } } }; tree.Measure(Size.Infinity); tree.Arrange(new Rect(tree.DesiredSize)); var initial = new Scene(tree); SceneBuilder.UpdateAll(initial); border.IsVisible = false; var result = initial.Clone(); Assert.True(SceneBuilder.Update(result, border)); Assert.False(SceneBuilder.Update(result, canvas)); var decoratorNode = (VisualNode)result.Root.Children[0]; Assert.Equal(0, decoratorNode.Children.Count); Assert.Null(result.FindNode(border)); Assert.Null(result.FindNode(canvas)); } } [Fact] public void Should_Update_Descendent_Tranform_When_Margin_Changed() { using (TestApplication()) { Decorator decorator; Border border; Canvas canvas; var tree = new TestRoot { Width = 100, Height = 100, Child = decorator = new Decorator { Margin = new Thickness(0, 10, 0, 0), Child = border = new Border { Child = canvas = new Canvas(), } } }; var layout = AvaloniaLocator.Current.GetService(); layout.ExecuteInitialLayoutPass(tree); var scene = new Scene(tree); SceneBuilder.UpdateAll(scene); var borderNode = scene.FindNode(border); var canvasNode = scene.FindNode(canvas); Assert.Equal(Matrix.CreateTranslation(0, 10), borderNode.Transform); Assert.Equal(Matrix.CreateTranslation(0, 10), canvasNode.Transform); decorator.Margin = new Thickness(0, 20, 0, 0); layout.ExecuteLayoutPass(); scene = scene.Clone(); SceneBuilder.Update(scene, decorator); borderNode = scene.FindNode(border); canvasNode = scene.FindNode(canvas); Assert.Equal(Matrix.CreateTranslation(0, 20), borderNode.Transform); Assert.Equal(Matrix.CreateTranslation(0, 20), canvasNode.Transform); } } private IDisposable TestApplication() { return UnitTestApplication.Start( TestServices.MockPlatformRenderInterface.With( layoutManager: new LayoutManager())); } } }