diff --git a/samples/ControlCatalog/Pages/CanvasPage.xaml b/samples/ControlCatalog/Pages/CanvasPage.xaml index 10a38895a2..d6c138a4f7 100644 --- a/samples/ControlCatalog/Pages/CanvasPage.xaml +++ b/samples/ControlCatalog/Pages/CanvasPage.xaml @@ -11,7 +11,8 @@ - + + diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs index e640925b3a..10c8d36dc8 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs @@ -30,19 +30,20 @@ namespace Avalonia.Rendering.SceneGraph Matrix Transform { get; } /// - /// Gets the bounds for the node's geometry in global coordinates. + /// Gets the bounds of the node's geometry in global coordinates. /// Rect Bounds { get; } /// /// Gets the clip bounds for the node in global coordinates. /// - /// - /// This clip does not take into account parent clips, to find the absolute clip bounds - /// it is necessary to traverse the tree. - /// Rect ClipBounds { get; } + /// + /// Gets the layout bounds for the node in global coordinates. + /// + Rect LayoutBounds { get; } + /// /// Whether the node is clipped to . /// diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs index 799380cb85..2fcea8c205 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs @@ -167,8 +167,9 @@ namespace Avalonia.Rendering.SceneGraph using (context.PushPostTransform(m)) using (context.PushTransformContainer()) { + var globalBounds = bounds.TransformToAABB(contextImpl.Transform); var clipBounds = clipToBounds ? - bounds.TransformToAABB(contextImpl.Transform).Intersect(clip) : + globalBounds.Intersect(clip) : clip; forceRecurse = forceRecurse || @@ -179,6 +180,7 @@ namespace Avalonia.Rendering.SceneGraph node.Transform = contextImpl.Transform; node.ClipBounds = clipBounds; node.ClipToBounds = clipToBounds; + node.LayoutBounds = globalBounds; node.GeometryClip = visual.Clip?.PlatformImpl; node.Opacity = opacity; diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs index 760cedcbf0..8000d413ea 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs @@ -58,6 +58,9 @@ namespace Avalonia.Rendering.SceneGraph /// public Rect ClipBounds { get; set; } + /// + public Rect LayoutBounds { get; set; } + /// public bool ClipToBounds { get; set; } @@ -266,7 +269,7 @@ namespace Avalonia.Rendering.SceneGraph if (OpacityMask != null) { - context.PushOpacityMask(OpacityMask, ClipBounds); + context.PushOpacityMask(OpacityMask, LayoutBounds); } } diff --git a/tests/Avalonia.RenderTests/OpacityMaskTests.cs b/tests/Avalonia.RenderTests/OpacityMaskTests.cs index 1934654c26..4edf4daa13 100644 --- a/tests/Avalonia.RenderTests/OpacityMaskTests.cs +++ b/tests/Avalonia.RenderTests/OpacityMaskTests.cs @@ -55,5 +55,42 @@ namespace Avalonia.Direct2D1.RenderTests await RenderToFile(target); CompareImages(); } + + [Fact] + public async Task RenderTansform_Applies_To_Opacity_Mask() + { + var target = new Canvas + { + OpacityMask = new LinearGradientBrush + { + StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative), + EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative), + GradientStops = new List + { + new GradientStop(Color.FromUInt32(0xffffffff), 0), + new GradientStop(Color.FromUInt32(0x00ffffff), 1) + } + }, + RenderTransform = new RotateTransform(90), + Width = 76, + Height = 76, + Children = + { + new Path + { + Width = 32, + Height = 40, + [Canvas.LeftProperty] = 23, + [Canvas.TopProperty] = 18, + Stretch = Stretch.Fill, + Fill = Brushes.Red, + Data = StreamGeometry.Parse("F1 M 27,18L 23,26L 33,30L 24,38L 33,46L 23,50L 27,58L 45,58L 55,38L 45,18L 27,18 Z") + } + } + }; + + await RenderToFile(target); + CompareImages(); + } } } diff --git a/tests/TestFiles/Direct2D1/OpacityMask/RenderTansform_Applies_To_Opacity_Mask.expected.png b/tests/TestFiles/Direct2D1/OpacityMask/RenderTansform_Applies_To_Opacity_Mask.expected.png new file mode 100644 index 0000000000..bf9775ba0b Binary files /dev/null and b/tests/TestFiles/Direct2D1/OpacityMask/RenderTansform_Applies_To_Opacity_Mask.expected.png differ diff --git a/tests/TestFiles/Skia/OpacityMask/RenderTansform_Applies_To_Opacity_Mask.expected.png b/tests/TestFiles/Skia/OpacityMask/RenderTansform_Applies_To_Opacity_Mask.expected.png new file mode 100644 index 0000000000..d7ea7d206f Binary files /dev/null and b/tests/TestFiles/Skia/OpacityMask/RenderTansform_Applies_To_Opacity_Mask.expected.png differ