diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index 599b224b63..cba09297f3 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -235,7 +235,7 @@ namespace Avalonia.Rendering { clipBounds = node.ClipBounds.Intersect(clipBounds); - if (!clipBounds.IsEmpty) + if (!clipBounds.IsEmpty && node.Opacity > 0) { node.BeginRender(context); @@ -353,7 +353,6 @@ namespace Avalonia.Rendering if (DrawFps) { - RenderFps(context, clientRect, true); RenderFps(context, clientRect, scene.Layers.Count); } } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs index b5ac7e4077..6c92ff022c 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs @@ -167,7 +167,7 @@ namespace Avalonia.Rendering.SceneGraph using (context.PushPostTransform(m)) using (context.PushTransformContainer()) { - var startLayer = opacity < 1 || visual.OpacityMask != null; + var startLayer = (visual as IAvaloniaObject)?.IsAnimating(Visual.OpacityProperty) ?? false; var clipBounds = bounds.TransformToAABB(contextImpl.Transform).Intersect(clip); forceRecurse = forceRecurse || diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs index dd5740e4a9..ea3b07ffe3 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs @@ -226,6 +226,11 @@ namespace Avalonia.Rendering.SceneGraph context.PushClip(ClipBounds); } + if (Opacity != 1) + { + context.PushOpacity(Opacity); + } + context.Transform = Transform; if (GeometryClip != null) @@ -242,6 +247,11 @@ namespace Avalonia.Rendering.SceneGraph context.PopGeometryClip(); } + if (Opacity != 1) + { + context.PopOpacity(); + } + if (ClipToBounds) { context.PopClip(); diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs index cec95d4807..9d90ae23af 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reactive.Subjects; using Avalonia.Controls; +using Avalonia.Data; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering; @@ -126,6 +128,94 @@ namespace Avalonia.Visuals.UnitTests.Rendering Assert.Equal(new List { root, decorator, border, canvas }, result); } + [Fact] + public void Should_Push_Opacity_For_Controls_With_Less_Than_1_Opacity() + { + var root = new TestRoot + { + Width = 100, + Height = 100, + Child = new Border + { + Background = Brushes.Red, + Opacity = 0.5, + } + }; + + root.Measure(Size.Infinity); + root.Arrange(new Rect(root.DesiredSize)); + + var rootLayer = CreateLayer(); + var borderLayer = CreateLayer(); + var renderTargetContext = Mock.Get(root.CreateRenderTarget().CreateDrawingContext(null)); + renderTargetContext.SetupSequence(x => x.CreateLayer(It.IsAny())) + .Returns(rootLayer) + .Returns(borderLayer); + + var loop = new Mock(); + var target = new DeferredRenderer( + root, + loop.Object, + dispatcher: new ImmediateDispatcher()); + root.Renderer = target; + + target.Start(); + RunFrame(loop); + + var context = Mock.Get(rootLayer.CreateDrawingContext(null)); + var animation = new BehaviorSubject(0.5); + + context.Verify(x => x.PushOpacity(0.5), Times.Once); + context.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); + context.Verify(x => x.PopOpacity(), Times.Once); + } + + [Fact] + public void Should_Not_Draw_Controls_With_0_Opacity() + { + var root = new TestRoot + { + Width = 100, + Height = 100, + Child = new Border + { + Background = Brushes.Red, + Opacity = 0, + Child = new Border + { + Background = Brushes.Green, + } + } + }; + + root.Measure(Size.Infinity); + root.Arrange(new Rect(root.DesiredSize)); + + var rootLayer = CreateLayer(); + var borderLayer = CreateLayer(); + var renderTargetContext = Mock.Get(root.CreateRenderTarget().CreateDrawingContext(null)); + renderTargetContext.SetupSequence(x => x.CreateLayer(It.IsAny())) + .Returns(rootLayer) + .Returns(borderLayer); + + var loop = new Mock(); + var target = new DeferredRenderer( + root, + loop.Object, + dispatcher: new ImmediateDispatcher()); + root.Renderer = target; + + target.Start(); + RunFrame(loop); + + var context = Mock.Get(rootLayer.CreateDrawingContext(null)); + var animation = new BehaviorSubject(0.5); + + context.Verify(x => x.PushOpacity(0.5), Times.Never); + context.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Never); + context.Verify(x => x.PopOpacity(), Times.Never); + } + [Fact] public void Frame_Should_Create_Layer_For_Root() { @@ -148,7 +238,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering root, loop.Object, sceneBuilder: sceneBuilder.Object, - //layerFactory: layers.Object, dispatcher: dispatcher); target.Start(); @@ -159,7 +248,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering } [Fact] - public void Should_Create_And_Delete_Layers_For_Transparent_Controls() + public void Should_Create_And_Delete_Layers_For_Controls_With_Animated_Opacity() { Border border; var root = new TestRoot @@ -198,6 +287,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering var rootContext = Mock.Get(rootLayer.CreateDrawingContext(null)); var borderContext = Mock.Get(borderLayer.CreateDrawingContext(null)); + var animation = new BehaviorSubject(0.5); rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); @@ -205,7 +295,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering rootContext.ResetCalls(); borderContext.ResetCalls(); - border.Opacity = 0.5; + border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); RunFrame(loop); rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); @@ -214,7 +304,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering rootContext.ResetCalls(); borderContext.ResetCalls(); - border.Opacity = 1; + animation.OnCompleted(); RunFrame(loop); Mock.Get(borderLayer).Verify(x => x.Dispose()); diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs index d0f7671956..44b9ebe8ce 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs @@ -9,6 +9,8 @@ using Xunit; using Avalonia.Layout; using Moq; using Avalonia.Platform; +using System.Reactive.Subjects; +using Avalonia.Data; namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph { @@ -620,13 +622,15 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph Margin = new Thickness(0, 10, 0, 0), Child = border = new Border { - Opacity = 0.5, Background = Brushes.Red, Child = canvas = new Canvas(), } } }; + var animation = new BehaviorSubject(0.5); + border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); + var scene = new Scene(tree); var sceneBuilder = new SceneBuilder(); sceneBuilder.UpdateAll(scene); diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs index ed74f12075..10372c7d8c 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs @@ -7,14 +7,15 @@ using Avalonia.UnitTests; using Avalonia.VisualTree; using Xunit; using Avalonia.Layout; -using Avalonia.Rendering; +using System.Reactive.Subjects; +using Avalonia.Data; namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph { public partial class SceneBuilderTests { [Fact] - public void Control_With_Transparency_Should_Start_New_Layer() + public void Control_With_Animated_Opacity_Should_Start_New_Layer() { using (TestApplication()) { @@ -31,7 +32,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph Padding = new Thickness(11), Child = border = new Border { - Opacity = 0.5, Background = Brushes.Red, Padding = new Thickness(12), Child = canvas = new Canvas(), @@ -42,6 +42,9 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph var layout = AvaloniaLocator.Current.GetService(); layout.ExecuteInitialLayoutPass(tree); + var animation = new BehaviorSubject(0.5); + border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); + var scene = new Scene(tree); var sceneBuilder = new SceneBuilder(); sceneBuilder.UpdateAll(scene); @@ -58,7 +61,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph Assert.Equal(2, scene.Layers.Count()); Assert.Empty(scene.Layers.Select(x => x.LayerRoot).Except(new IVisual[] { tree, border })); - border.Opacity = 1; + animation.OnCompleted(); scene = scene.Clone(); sceneBuilder.Update(scene, border); @@ -80,7 +83,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph } [Fact] - public void Control_With_OpacityMask_Should_Start_New_Layer() + public void Removing_Control_With_Animated_Opacity_Should_Remove_Layers() { using (TestApplication()) { @@ -97,10 +100,9 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph Padding = new Thickness(11), Child = border = new Border { - OpacityMask = Brushes.Red, Background = Brushes.Red, Padding = new Thickness(12), - Child = canvas = new Canvas(), + Child = canvas = new Canvas() } } }; @@ -108,74 +110,9 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph var layout = AvaloniaLocator.Current.GetService(); layout.ExecuteInitialLayoutPass(tree); - var scene = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(scene); - - var rootNode = (VisualNode)scene.Root; - var borderNode = (VisualNode)scene.FindNode(border); - var canvasNode = (VisualNode)scene.FindNode(canvas); - - Assert.Same(tree, rootNode.LayerRoot); - Assert.Same(border, borderNode.LayerRoot); - Assert.Same(border, canvasNode.LayerRoot); - Assert.Equal(Brushes.Red, scene.Layers[border].OpacityMask); - - Assert.Equal(2, scene.Layers.Count()); - Assert.Empty(scene.Layers.Select(x => x.LayerRoot).Except(new IVisual[] { tree, border })); - - border.OpacityMask = null; - scene = scene.Clone(); - - sceneBuilder.Update(scene, border); - - rootNode = (VisualNode)scene.Root; - borderNode = (VisualNode)scene.FindNode(border); - canvasNode = (VisualNode)scene.FindNode(canvas); - - Assert.Same(tree, rootNode.LayerRoot); - Assert.Same(tree, borderNode.LayerRoot); - Assert.Same(tree, canvasNode.LayerRoot); - Assert.Single(scene.Layers); - - var rootDirty = scene.Layers[tree].Dirty; - - Assert.Single(rootDirty); - Assert.Equal(new Rect(21, 21, 58, 78), rootDirty.Single()); - } - } - - [Fact] - public void Removing_Transparent_Control_Should_Remove_Layers() - { - using (TestApplication()) - { - Decorator decorator; - Border border; - Canvas canvas; - var tree = new TestRoot - { - Padding = new Thickness(10), - Width = 100, - Height = 120, - Child = decorator = new Decorator - { - Padding = new Thickness(11), - Child = border = new Border - { - Opacity = 0.5, - Background = Brushes.Red, - Padding = new Thickness(12), - Child = canvas = new Canvas - { - Opacity = 0.75, - }, - } - } - }; - - var layout = AvaloniaLocator.Current.GetService(); - layout.ExecuteInitialLayoutPass(tree); + var animation = new BehaviorSubject(0.5); + border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); + canvas.Bind(Canvas.OpacityProperty, animation, BindingPriority.Animation); var scene = new Scene(tree); var sceneBuilder = new SceneBuilder(); @@ -210,13 +147,9 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph Padding = new Thickness(11), Child = border = new Border { - Opacity = 0.5, Background = Brushes.Red, Padding = new Thickness(12), - Child = canvas = new Canvas - { - Opacity = 0.75, - }, + Child = canvas = new Canvas(), } } }; @@ -224,6 +157,10 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph var layout = AvaloniaLocator.Current.GetService(); layout.ExecuteInitialLayoutPass(tree); + var animation = new BehaviorSubject(0.5); + border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); + canvas.Bind(Canvas.OpacityProperty, animation, BindingPriority.Animation); + var scene = new Scene(tree); var sceneBuilder = new SceneBuilder(); sceneBuilder.UpdateAll(scene); @@ -263,6 +200,9 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph var layout = AvaloniaLocator.Current.GetService(); layout.ExecuteInitialLayoutPass(tree); + var animation = new BehaviorSubject(0.5); + border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); + var scene = new Scene(tree); var sceneBuilder = new SceneBuilder(); sceneBuilder.UpdateAll(scene);