提交 b1a787bf 编写于 作者: D David

fix(composition): Use ShapeVisual as visual for UIELement, use its Shapes to...

fix(composition): Use ShapeVisual as visual for UIELement, use its Shapes to render "local content" instead of adding sub-visual, and its ViewBox to properly clip transformed content.
上级 1c45fe37
......@@ -5,12 +5,11 @@ namespace Windows.UI.Composition
public partial class ShapeVisual : ContainerVisual
{
private CompositionViewBox? _viewBox;
private CompositionShapeCollection? _shapes;
public ShapeVisual(Compositor compositor)
: base(compositor)
{
Shapes = new CompositionShapeCollection(compositor, this);
// Add this as context for the shape collection so we get
// notified about changes in the shapes object graph.
OnCompositionPropertyChanged(null, Shapes, nameof(Shapes));
......@@ -22,6 +21,7 @@ namespace Windows.UI.Composition
set => SetProperty(ref _viewBox, value);
}
public CompositionShapeCollection Shapes { get; }
// This ia lazy as we are using the `ShapeVisual` for UIElement, but lot of them are not creating shapes, reduce memory pressure.
public CompositionShapeCollection Shapes => _shapes ??= new CompositionShapeCollection(Compositor, this);
}
}
#nullable enable
using System.Numerics;
using Windows.Foundation;
using SkiaSharp;
using Uno.Extensions;
namespace Windows.UI.Composition
namespace Windows.UI.Composition;
public partial class ShapeVisual
{
public partial class ShapeVisual
internal override void Render(SKSurface surface)
{
internal override void Render(SKSurface surface)
// First we render the shapes (a.k.a. the "local content")
// For UIElement, those are background and border or shape's content
if (_shapes is { Count: not 0 } shapes)
{
foreach (var shape in Shapes)
var viewBox = ViewBox;
if (viewBox is not null)
{
var shapeTransform = shape.TransformMatrix;
if (shape.Scale != Vector2.One)
// First we go back to the parent coordinates system.
surface.Canvas.Restore();
surface.Canvas.Save();
// Set the position of the visual on the canvas (i.e. change coordinates system to the "XAML element" one)
surface.Canvas.Translate(Offset.X + AnchorPoint.X, Offset.Y + AnchorPoint.Y);
// We apply the transformed viewbox clipping
var transform = this.GetTransform();
var shape = new SKPath();
shape.AddRect(new SKRect(0, 0, viewBox.Offset.X + viewBox.Size.X, viewBox.Offset.Y + viewBox.Size.Y));
shape.Transform(transform.ToSKMatrix());
surface.Canvas.ClipPath(shape, antialias: true);
// Applied rending transformation matrix (i.e. change coordinates system to the "rendering" one)
if (!transform.IsIdentity)
{
shapeTransform *= Matrix3x2.CreateScale(shape.Scale, shape.CenterPoint);
var skTransform = transform.ToSKMatrix();
surface.Canvas.Concat(ref skTransform);
}
if (shape.RotationAngle is not 0)
Clip?.Apply(surface);
}
for (var i = 0; i < shapes.Count; i++)
{
var shape = shapes[i];
var shapeTransform = shape.GetTransform();
if (shapeTransform.IsIdentity)
{
shapeTransform *= Matrix3x2.CreateRotation(shape.RotationAngle, shape.CenterPoint);
shape.Render(surface);
}
if (!shapeTransform.IsIdentity)
else
{
var shapeTransformMatrix = shapeTransform.ToSKMatrix44().Matrix;
var shapeTransformMatrix = shapeTransform.ToSKMatrix();
surface.Canvas.Save();
surface.Canvas.Concat(ref shapeTransformMatrix);
......@@ -33,11 +62,32 @@ namespace Windows.UI.Composition
surface.Canvas.Restore();
}
else
}
if (viewBox is not null)
{
surface.Canvas.Restore();
surface.Canvas.Save();
surface.Canvas.Translate(Offset.X + AnchorPoint.X, Offset.Y + AnchorPoint.Y);
// Applied rending transformation matrix (i.e. change coordinates system to the "rendering" one)
if (this.GetTransform() is { IsIdentity: false } transform)
{
shape.Render(surface);
var skTransform = transform.ToSKMatrix();
surface.Canvas.Concat(ref skTransform);
}
Clip?.Apply(surface);
}
}
// Second we render the children (without clipping so they are able to draw some content out of the clip bounds - in case or RenderTransform)
if (ViewBox is { } vb)
{
surface.Canvas.ClipRect(new SKRect(vb.Offset.X, vb.Offset.Y, vb.Offset.X + vb.Size.X, vb.Offset.Y + vb.Size.Y));
}
base.Render(surface);
}
}
......@@ -90,20 +90,16 @@ namespace Windows.UI.Xaml.Shapes
return Disposable.Empty;
}
var parent = owner.Visual;
var compositor = parent.Compositor;
var visual = owner.Visual;
var compositor = visual.Compositor;
var background = state.Background;
var borderThickness = owner.LayoutRound(state.BorderThickness);
var borderBrush = state.BorderBrush;
var cornerRadius = state.CornerRadius;
var disposables = new CompositeDisposable();
var sublayers = new List<Visual>();
var shapes = new List<CompositionShape>();
var heightOffset = ((float)borderThickness.Top / 2) + ((float)borderThickness.Bottom / 2);
var widthOffset = ((float)borderThickness.Left / 2) + ((float)borderThickness.Right / 2);
var halfWidth = (float)area.Width / 2;
var halfHeight = (float)area.Height / 2;
var adjustedArea = state.BackgroundSizing == BackgroundSizing.InnerBorderEdge
? area.DeflateBy(borderThickness)
: area;
......@@ -114,8 +110,6 @@ namespace Windows.UI.Xaml.Shapes
{
var borderShape = compositor.CreateSpriteShape();
var backgroundShape = compositor.CreateSpriteShape();
var outerShape = compositor.CreateSpriteShape();
var clipShape = compositor.CreateSpriteShape();
// Border brush
Brush.AssignAndObserveBrush(borderBrush, compositor, brush => borderShape.FillBrush = brush)
......@@ -141,10 +135,12 @@ namespace Windows.UI.Xaml.Shapes
GetRoundedPath(adjustedArea.ToSKRect(), _innerRadiiStore) :
GetRoundedPath(area.ToSKRect(), _outerRadiiStore);
backgroundShape.Geometry = compositor.CreatePathGeometry(backgroundPath);
var backgroundVisual = compositor.CreateShapeVisual();
backgroundVisual.Shapes.Add(backgroundShape);
sublayers.Add(backgroundVisual);
parent.Children.InsertAtBottom(backgroundVisual);
#if DEBUG
backgroundShape.Comment = "#background";
#endif
visual.Shapes.Add(backgroundShape);
shapes.Add(backgroundShape);
// Border shape (if any)
if (borderThickness != Thickness.Empty)
......@@ -152,31 +148,26 @@ namespace Windows.UI.Xaml.Shapes
var borderPath = GetBorderPath(_outerRadiiStore, _innerRadiiStore, area, adjustedArea);
borderShape.Geometry = compositor.CreatePathGeometry(borderPath);
var borderVisual = compositor.CreateShapeVisual();
borderVisual.Shapes.Add(borderShape);
sublayers.Add(borderVisual);
parent.Children.InsertAtTop(borderVisual);
visual.Shapes.Add(borderShape);
shapes.Add(borderShape);
#if DEBUG
backgroundShape.Comment = "#border";
#endif
}
owner.ClippingIsSetByCornerRadius = true;
parent.Clip = compositor.CreateRectangleClip(
visual.Clip = compositor.CreateRectangleClip(
0, 0, (float)area.Width, (float)area.Height,
fullCornerRadius.Outer.TopLeft, fullCornerRadius.Outer.TopRight, fullCornerRadius.Outer.BottomRight, fullCornerRadius.Outer.BottomLeft);
}
else
{
var shapeVisual = compositor.CreateShapeVisual();
var backgroundShape = compositor.CreateSpriteShape();
var backgroundArea = area;
// Background brush
if (background is ImageBrush imgBackground)
{
backgroundArea = CreateImageLayer(compositor, disposables, borderThickness, adjustedArea, backgroundShape, backgroundArea, imgBackground);
CreateImageLayer(compositor, disposables, borderThickness, adjustedArea, backgroundShape, area, imgBackground);
}
else
{
......@@ -195,13 +186,17 @@ namespace Windows.UI.Xaml.Shapes
geometry.AddRect(adjustedArea.ToSKRect());
backgroundShape.Geometry = compositor.CreatePathGeometry(new CompositionPath(geometrySource));
#if DEBUG
backgroundShape.Comment = "#background";
#endif
shapeVisual.Shapes.Add(backgroundShape);
visual.Shapes.Add(backgroundShape);
shapes.Add(backgroundShape);
// Border shape (if any)
if (borderThickness != Thickness.Empty)
{
void CreateLayer(Action<CompositionSpriteShape, SKPath> builder)
void CreateLayer(Action<CompositionSpriteShape, SKPath> builder, string name)
{
var spriteShape = compositor.CreateSpriteShape();
var geometry = new SkiaGeometrySource2D();
......@@ -212,8 +207,12 @@ namespace Windows.UI.Xaml.Shapes
builder(spriteShape, geometry.Geometry);
spriteShape.Geometry = compositor.CreatePathGeometry(new CompositionPath(geometry));
#if DEBUG
spriteShape.Comment = name;
#endif
shapeVisual.Shapes.Add(spriteShape);
visual.Shapes.Add(spriteShape);
shapes.Add(spriteShape);
}
if (borderThickness.Top != 0)
......@@ -225,7 +224,7 @@ namespace Windows.UI.Xaml.Shapes
path.MoveTo((float)(area.X + borderThickness.Left), (float)(area.Y + StrokeThicknessAdjust));
path.LineTo((float)(area.X + area.Width - borderThickness.Right), (float)(area.Y + StrokeThicknessAdjust));
path.Close();
});
}, "#border-top");
}
if (borderThickness.Bottom != 0)
......@@ -237,7 +236,7 @@ namespace Windows.UI.Xaml.Shapes
path.MoveTo((float)(area.X + (float)borderThickness.Left), (float)(area.Y + area.Height - StrokeThicknessAdjust));
path.LineTo((float)(area.X + area.Width - (float)borderThickness.Right), (float)(area.Y + area.Height - StrokeThicknessAdjust));
path.Close();
});
}, "#border-bottom");
}
if (borderThickness.Left != 0)
......@@ -249,7 +248,7 @@ namespace Windows.UI.Xaml.Shapes
path.MoveTo((float)(area.X + StrokeThicknessAdjust), (float)area.Y);
path.LineTo((float)(area.X + StrokeThicknessAdjust), (float)(area.Y + area.Height));
path.Close();
});
}, "#border-left");
}
if (borderThickness.Right != 0)
......@@ -261,25 +260,19 @@ namespace Windows.UI.Xaml.Shapes
path.MoveTo((float)(area.X + area.Width - StrokeThicknessAdjust), (float)area.Y);
path.LineTo((float)(area.X + area.Width - StrokeThicknessAdjust), (float)(area.Y + area.Height));
path.Close();
});
}, "#border-right");
}
}
sublayers.Add(shapeVisual);
// Must be inserted below the other subviews, which may happen when
// the current view has subviews.
parent.Children.InsertAtBottom(shapeVisual);
}
disposables.Add(() =>
{
owner.ClippingIsSetByCornerRadius = false;
foreach (var sv in sublayers)
foreach (var shape in shapes)
{
parent.Children.Remove(sv);
sv.Dispose();
visual.Shapes.Remove(shape);
shape.Dispose();
}
}
);
......@@ -315,7 +308,7 @@ namespace Windows.UI.Xaml.Shapes
if (imgBackground.Transform != null)
{
matrix *= imgBackground.Transform.ToMatrix(new Point());
matrix *= imgBackground.Transform.MatrixCore;
}
surfaceBrush.TransformMatrix = matrix;
......@@ -334,7 +327,6 @@ namespace Windows.UI.Xaml.Shapes
return backgroundArea;
}
private static CompositionPath GetRoundedPath(SKRect area, SKPoint[] radii, SkiaGeometrySource2D geometrySource = null)
{
geometrySource ??= new SkiaGeometrySource2D();
......
......@@ -8,8 +8,6 @@ namespace Windows.UI.Xaml.Shapes
{
partial class Ellipse : Shape
{
private ShapeVisual _rectangleVisual;
static Ellipse()
{
StretchProperty.OverrideMetadata(typeof(Ellipse), new FrameworkPropertyMetadata(defaultValue: Media.Stretch.Fill));
......@@ -17,8 +15,6 @@ namespace Windows.UI.Xaml.Shapes
public Ellipse()
{
_rectangleVisual = Visual.Compositor.CreateShapeVisual();
Visual.Children.InsertAtBottom(_rectangleVisual);
}
protected override Size MeasureOverride(Size availableSize)
......
using System;
using System.Collections.Generic;
using System.Text;
using Windows.UI.Xaml.Media;
using Uno.Extensions;
using System.Linq;
using Uno.Disposables;
using Uno.UI.Extensions;
using Uno.UI;
#nullable enable
using System;
using Windows.UI.Composition;
using Windows.Foundation;
using Windows.Graphics;
using System.Numerics;
namespace Windows.UI.Xaml.Shapes
......@@ -33,17 +26,9 @@ namespace Windows.UI.Xaml.Shapes
protected override Size ArrangeOverride(Size finalSize)
{
var (shapeSize, renderingArea) = ArrangeRelativeShape(finalSize);
SkiaGeometrySource2D path;
if (renderingArea.Width > 0 && renderingArea.Height > 0)
{
path = GetGeometry(renderingArea);
}
else
{
path = null;
}
var path = renderingArea.Width > 0 && renderingArea.Height > 0
? GetGeometry(renderingArea)
: null;
Render(path);
......@@ -52,33 +37,17 @@ namespace Windows.UI.Xaml.Shapes
private SkiaGeometrySource2D GetGeometry(Rect finalRect)
{
var strokeThickness = StrokeThickness;
var radiusX = RadiusX;
var radiusY = RadiusY;
var offset = new Vector2((float)(finalRect.Left), (float)(finalRect.Top));
var offset = new Vector2((float)finalRect.Left, (float)finalRect.Top);
var size = new Vector2((float)finalRect.Width, (float)finalRect.Height);
SkiaGeometrySource2D geometry;
if (radiusX == 0 || radiusY == 0)
{
// Simple rectangle
geometry = new SkiaGeometrySource2D(
CompositionGeometry.BuildRectangleGeometry(
offset,
size));
}
else
{
// Complex rectangle
geometry = new SkiaGeometrySource2D(
CompositionGeometry.BuildRoundedRectangleGeometry(
offset,
size,
new Vector2((float)radiusX, (float)radiusY)));
}
var geometry = radiusX is 0 || radiusY is 0
? CompositionGeometry.BuildRectangleGeometry(offset, size)
: CompositionGeometry.BuildRoundedRectangleGeometry(offset, size, new Vector2((float)radiusX, (float)radiusY));
return geometry;
return new SkiaGeometrySource2D(geometry);
}
}
}
using System;
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.Specialized;
......@@ -17,20 +19,25 @@ using System.Numerics;
namespace Windows.UI.Xaml.Shapes
{
[Markup.ContentProperty(Name = "SvgChildren")]
partial class Shape
{
private ShapeVisual _rectangleVisual;
private SerialDisposable _fillSubscription = new SerialDisposable();
private SerialDisposable _strokeSubscription = new SerialDisposable();
private CompositionSpriteShape _pathSpriteShape;
private readonly SerialDisposable _fillSubscription = new();
private readonly SerialDisposable _strokeSubscription = new();
private readonly CompositionSpriteShape _shape;
private readonly CompositionPathGeometry _geometry;
public Shape()
{
_rectangleVisual = Visual.Compositor.CreateShapeVisual();
Visual.Children.InsertAtBottom(_rectangleVisual);
var visual = Visual;
var compositor = visual.Compositor;
_geometry = compositor.CreatePathGeometry();
_shape = compositor.CreateSpriteShape(_geometry);
#if DEBUG
_shape.Comment = "#path";
#endif
InitCommonShapeProperties();
visual.Shapes.Add(_shape);
}
private Rect GetPathBoundingBox(SkiaGeometrySource2D path)
......@@ -38,29 +45,19 @@ namespace Windows.UI.Xaml.Shapes
private bool IsFinite(double value) => !double.IsInfinity(value);
private void InitCommonShapeProperties() { }
private protected void Render(Windows.UI.Composition.SkiaGeometrySource2D path, double? scaleX = null, double? scaleY = null, double? renderOriginX = null, double? renderOriginY = null)
private protected void Render(Windows.UI.Composition.SkiaGeometrySource2D? path, double? scaleX = null, double? scaleY = null, double? renderOriginX = null, double? renderOriginY = null)
{
var compositionPath = new CompositionPath(path);
var pathGeometry = Visual.Compositor.CreatePathGeometry();
pathGeometry.Path = compositionPath;
_pathSpriteShape = Visual.Compositor.CreateSpriteShape(pathGeometry);
if (scaleX != null && scaleY != null)
{
_pathSpriteShape.Scale = new Vector2((float)scaleX.Value, (float)scaleY.Value);
}
else
if (path is null)
{
_pathSpriteShape.Scale = new Vector2(1, 1);
_geometry.Path = null;
return;
}
_pathSpriteShape.Offset = LayoutRound(new Vector2((float)(renderOriginX ?? 0), (float)(renderOriginY ?? 0)));
_rectangleVisual.Shapes.Clear();
_rectangleVisual.Shapes.Add(_pathSpriteShape);
_geometry.Path = new CompositionPath(path);
_shape.Scale = scaleX != null && scaleY != null
? new Vector2((float)scaleX.Value, (float)scaleY.Value)
: Vector2.One;
_shape.Offset = LayoutRound(new Vector2((float)(renderOriginX ?? 0), (float)(renderOriginY ?? 0)));
UpdateRender();
}
......@@ -74,36 +71,25 @@ namespace Windows.UI.Xaml.Shapes
private void UpdateFill()
{
if (_pathSpriteShape != null)
{
_fillSubscription.Disposable = null;
_pathSpriteShape.FillBrush = null;
_fillSubscription.Disposable = null;
_shape.FillBrush = null;
_fillSubscription.Disposable =
Brush.AssignAndObserveBrush(Fill, Visual.Compositor, compositionBrush => _pathSpriteShape.FillBrush = compositionBrush);
}
_fillSubscription.Disposable = Brush.AssignAndObserveBrush(Fill, Visual.Compositor, compositionBrush => _shape.FillBrush = compositionBrush);
}
private void UpdateStrokeThickness()
{
if (_pathSpriteShape != null)
{
_pathSpriteShape.StrokeThickness = (float)ActualStrokeThickness;
}
_shape.StrokeThickness = (float)ActualStrokeThickness;
}
private void UpdateStroke()
{
if (_pathSpriteShape != null)
{
_strokeSubscription.Disposable = null;
_strokeSubscription.Disposable = null;
_shape.StrokeBrush = null;
_pathSpriteShape.StrokeBrush = null;
_strokeSubscription.Disposable =
Brush.AssignAndObserveBrush(Stroke, Visual.Compositor, compositionBrush => _pathSpriteShape.StrokeBrush = compositionBrush);
}
_strokeSubscription.Disposable = Brush.AssignAndObserveBrush(Stroke, Visual.Compositor, compositionBrush => _shape.StrokeBrush = compositionBrush);
}
}
}
......@@ -25,7 +25,7 @@ namespace Windows.UI.Xaml
{
public partial class UIElement : DependencyObject
{
private ContainerVisual _visual;
private ShapeVisual _visual;
private Rect _currentFinalRect;
private Rect? _currentClippedFrame;
......@@ -72,14 +72,14 @@ namespace Windows.UI.Xaml
Visual.Opacity = Visibility == Visibility.Visible ? (float)Opacity : 0;
}
internal ContainerVisual Visual
internal ShapeVisual Visual
{
get
{
if (_visual == null)
if (_visual is null)
{
_visual = Window.Current.Compositor.CreateContainerVisual();
_visual = Window.Current.Compositor.CreateShapeVisual();
#if ENABLE_CONTAINER_VISUAL_TRACKING
_visual.Comment = $"Owner: {this.GetDebugDepth():D2}-{this.GetDebugName()}";
#endif
......@@ -306,7 +306,20 @@ namespace Windows.UI.Xaml
visual.Size = new Vector2((float)roundedRect.Width, (float)roundedRect.Height);
visual.CenterPoint = new Vector3((float)RenderTransformOrigin.X, (float)RenderTransformOrigin.Y, 0);
ApplyNativeClip(clip ?? Rect.Empty);
// The clipping applied by our parent due to layout constraints are pushed to the visual through the ViewBox property
// This allows special handling of this clipping by the compositor (cf. ShapeVisual.Render).
if (clip is null)
{
visual.ViewBox = null;
}
else
{
var viewBox = visual.Compositor.CreateViewBox();
viewBox.Offset = clip.Value.Location.ToVector2();
viewBox.Size = clip.Value.Size.ToVector2();
visual.ViewBox = viewBox;
}
}
partial void ApplyNativeClip(Rect rect)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册