Skip to content

Commit d90b89a

Browse files
Don't inherit from AttachedCardShadowBase
1 parent 095c315 commit d90b89a

File tree

2 files changed

+176
-37
lines changed

2 files changed

+176
-37
lines changed

Microsoft.Toolkit.Uwp.UI.Media/Enums/InnerContentClipMode.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
namespace Microsoft.Toolkit.Uwp.UI.Media
88
{
99
/// <summary>
10-
/// The method that shadows deriving from <see cref="AttachedCardShadowBase"/> use when clipping their inner content.
10+
/// The method that each instance of <see cref="AttachedCardShadow"/> uses when clipping its inner content.
1111
/// </summary>
1212
public enum InnerContentClipMode
1313
{

Microsoft.Toolkit.Uwp.UI.Media/Shadows/AttachedCardShadow.cs

Lines changed: 175 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Windows.UI;
99
using Windows.UI.Composition;
1010
using Windows.UI.Xaml;
11+
using Windows.UI.Xaml.Hosting;
1112

1213
namespace Microsoft.Toolkit.Uwp.UI.Media
1314
{
@@ -20,9 +21,18 @@ namespace Microsoft.Toolkit.Uwp.UI.Media
2021
public sealed class AttachedCardShadow : AttachedShadowBase
2122
{
2223
private const float MaxBlurRadius = 72;
23-
private static readonly TypedResourceKey<CompositionGeometricClip> ClipResourceKey = "Clip";
2424

25+
private static readonly TypedResourceKey<CompositionGeometricClip> ClipResourceKey = "Clip";
2526
private static readonly TypedResourceKey<CompositionPathGeometry> PathGeometryResourceKey = "PathGeometry";
27+
private static readonly TypedResourceKey<CompositionMaskBrush> OpacityMaskBrushResourceKey = "OpacityMask";
28+
private static readonly TypedResourceKey<ShapeVisual> OpacityMaskShapeVisualResourceKey = "OpacityMaskShapeVisual";
29+
private static readonly TypedResourceKey<CompositionRoundedRectangleGeometry> OpacityMaskGeometryResourceKey = "OpacityMaskGeometry";
30+
private static readonly TypedResourceKey<CompositionSpriteShape> OpacityMaskSpriteShapeResourceKey = "OpacityMaskSpriteShape";
31+
private static readonly TypedResourceKey<CompositionVisualSurface> OpacityMaskShapeVisualSurfaceResourceKey = "OpacityMaskShapeVisualSurface";
32+
private static readonly TypedResourceKey<CompositionSurfaceBrush> OpacityMaskShapeVisualSurfaceBrushResourceKey = "OpacityMaskShapeVisualSurfaceBrush";
33+
private static readonly TypedResourceKey<CompositionVisualSurface> OpacityMaskVisualSurfaceResourceKey = "OpacityMaskVisualSurface";
34+
private static readonly TypedResourceKey<CompositionSurfaceBrush> OpacityMaskSurfaceBrushResourceKey = "OpacityMaskSurfaceBrush";
35+
private static readonly TypedResourceKey<SpriteVisual> OpacityMaskVisualResourceKey = "OpacityMaskVisual";
2636
private static readonly TypedResourceKey<CompositionRoundedRectangleGeometry> RoundedRectangleGeometryResourceKey = "RoundedGeometry";
2737
private static readonly TypedResourceKey<CompositionSpriteShape> ShapeResourceKey = "Shape";
2838
private static readonly TypedResourceKey<ShapeVisual> ShapeVisualResourceKey = "ShapeVisual";
@@ -39,6 +49,16 @@ public sealed class AttachedCardShadow : AttachedShadowBase
3949
typeof(AttachedCardShadow),
4050
new PropertyMetadata(4d, OnDependencyPropertyChanged)); // Default WinUI ControlCornerRadius is 4
4151

52+
/// <summary>
53+
/// The <see cref="DependencyProperty"/> for <see cref="InnerContentClipMode"/>.
54+
/// </summary>
55+
public static readonly DependencyProperty InnerContentClipModeProperty =
56+
DependencyProperty.Register(
57+
nameof(InnerContentClipMode),
58+
typeof(InnerContentClipMode),
59+
typeof(AttachedCardShadowBase),
60+
new PropertyMetadata(InnerContentClipMode.CompositionGeometricClip, OnDependencyPropertyChanged));
61+
4262
/// <summary>
4363
/// Gets or sets the roundness of the shadow's corners.
4464
/// </summary>
@@ -48,72 +68,125 @@ public double CornerRadius
4868
set => SetValue(CornerRadiusProperty, value);
4969
}
5070

71+
/// <summary>
72+
/// Gets or sets the mode use to clip inner content from the shadow.
73+
/// </summary>
74+
public InnerContentClipMode InnerContentClipMode
75+
{
76+
get => (InnerContentClipMode)GetValue(InnerContentClipModeProperty);
77+
set => SetValue(InnerContentClipModeProperty, value);
78+
}
79+
5180
/// <inheritdoc/>
5281
public override bool IsSupported => SupportsCompositionVisualSurface;
5382

5483
/// <inheritdoc/>
5584
protected internal override bool SupportsOnSizeChangedEvent => true;
5685

5786
/// <inheritdoc/>
58-
protected override void OnPropertyChanged(AttachedShadowElementContext context, DependencyProperty property, object oldValue, object newValue)
87+
protected internal override void OnElementContextInitialized(AttachedShadowElementContext context)
5988
{
60-
if (property == CornerRadiusProperty)
89+
UpdateVisualOpacityMask(context);
90+
base.OnElementContextInitialized(context);
91+
}
92+
93+
/// <inheritdoc/>
94+
protected override void SetElementChildVisual(AttachedShadowElementContext context)
95+
{
96+
if (context.TryGetResource(OpacityMaskShapeVisualSurfaceBrushResourceKey, out var opacityMask))
6197
{
62-
var geometry = context.GetResource(RoundedRectangleGeometryResourceKey);
63-
if (geometry != null)
64-
{
65-
geometry.CornerRadius = new Vector2((float)(double)newValue);
66-
}
98+
var visualSurface = context.GetResource(OpacityMaskVisualSurfaceResourceKey) ??
99+
context.AddResource(OpacityMaskVisualSurfaceResourceKey, context.Compositor.CreateVisualSurface());
100+
visualSurface.SourceVisual = context.SpriteVisual;
101+
context.SpriteVisual.RelativeSizeAdjustment = Vector2.Zero;
102+
context.SpriteVisual.Size = new Vector2((float)context.Element.ActualWidth, (float)context.Element.ActualHeight);
103+
visualSurface.SourceOffset = new Vector2(-MaxBlurRadius);
104+
visualSurface.SourceSize = new Vector2((float)context.Element.ActualWidth, (float)context.Element.ActualHeight) + new Vector2(MaxBlurRadius * 2);
67105

68-
UpdateShadowClip(context);
106+
var surfaceBrush = context.GetResource(OpacityMaskSurfaceBrushResourceKey) ??
107+
context.AddResource(OpacityMaskSurfaceBrushResourceKey, context.Compositor.CreateSurfaceBrush());
108+
surfaceBrush.Surface = visualSurface;
109+
surfaceBrush.Stretch = CompositionStretch.None;
110+
111+
CompositionMaskBrush maskBrush = context.GetResource(OpacityMaskBrushResourceKey) ??
112+
context.AddResource(OpacityMaskBrushResourceKey, context.Compositor.CreateMaskBrush());
113+
maskBrush.Source = surfaceBrush;
114+
maskBrush.Mask = opacityMask;
115+
116+
var visual = context.GetResource(OpacityMaskVisualResourceKey) ??
117+
context.AddResource(OpacityMaskVisualResourceKey, context.Compositor.CreateSpriteVisual());
118+
visual.RelativeSizeAdjustment = Vector2.One;
119+
visual.Offset = new Vector3(-MaxBlurRadius, -MaxBlurRadius, 0);
120+
visual.Size = new Vector2(MaxBlurRadius * 2);
121+
visual.Brush = maskBrush;
122+
ElementCompositionPreview.SetElementChildVisual(context.Element, visual);
69123
}
70124
else
71125
{
72-
base.OnPropertyChanged(context, property, oldValue, newValue);
126+
base.SetElementChildVisual(context);
127+
context.RemoveAndDisposeResource(OpacityMaskVisualSurfaceResourceKey);
128+
context.RemoveAndDisposeResource(OpacityMaskSurfaceBrushResourceKey);
129+
context.RemoveAndDisposeResource(OpacityMaskVisualResourceKey);
130+
context.RemoveAndDisposeResource(OpacityMaskBrushResourceKey);
73131
}
74132
}
75133

76-
/// <inheritdoc/>
77-
protected override CompositionBrush GetShadowMask(AttachedShadowElementContext context)
134+
/// <summary>
135+
/// Updates the <see cref="CompositionBrush"/> used to mask <paramref name="context"/>.<see cref="AttachedShadowElementContext.SpriteVisual">SpriteVisual</see>.
136+
/// </summary>
137+
/// <param name="context">The <see cref="AttachedShadowElementContext"/> whose <see cref="SpriteVisual"/> will be masked.</param>
138+
private void UpdateVisualOpacityMask(AttachedShadowElementContext context)
78139
{
79-
if (!SupportsCompositionVisualSurface)
140+
if (InnerContentClipMode != InnerContentClipMode.CompositionMaskBrush)
80141
{
81-
return null;
142+
context.RemoveAndDisposeResource(OpacityMaskShapeVisualResourceKey);
143+
context.RemoveAndDisposeResource(OpacityMaskGeometryResourceKey);
144+
context.RemoveAndDisposeResource(OpacityMaskSpriteShapeResourceKey);
145+
context.RemoveAndDisposeResource(OpacityMaskShapeVisualSurfaceResourceKey);
146+
context.RemoveAndDisposeResource(OpacityMaskShapeVisualSurfaceBrushResourceKey);
147+
return;
82148
}
83149

84-
// Create rounded rectangle geometry and add it to a shape
85-
var geometry = context.GetResource(RoundedRectangleGeometryResourceKey) ?? context.AddResource(
86-
RoundedRectangleGeometryResourceKey,
87-
context.Compositor.CreateRoundedRectangleGeometry());
88-
geometry.CornerRadius = new Vector2((float)CornerRadius);
150+
var shapeVisual = context.GetResource(OpacityMaskShapeVisualResourceKey) ??
151+
context.AddResource(OpacityMaskShapeVisualResourceKey, context.Compositor.CreateShapeVisual());
89152

90-
var shape = context.GetResource(ShapeResourceKey) ?? context.AddResource(ShapeResourceKey, context.Compositor.CreateSpriteShape(geometry));
91-
shape.FillBrush = context.Compositor.CreateColorBrush(Colors.Black);
153+
CompositionRoundedRectangleGeometry geom = context.GetResource(OpacityMaskGeometryResourceKey) ??
154+
context.AddResource(OpacityMaskGeometryResourceKey, context.Compositor.CreateRoundedRectangleGeometry());
155+
CompositionSpriteShape shape = context.GetResource(OpacityMaskSpriteShapeResourceKey) ??
156+
context.AddResource(OpacityMaskSpriteShapeResourceKey, context.Compositor.CreateSpriteShape(geom));
92157

93-
// Create a ShapeVisual so that our geometry can be rendered to a visual
94-
var shapeVisual = context.GetResource(ShapeVisualResourceKey) ??
95-
context.AddResource(ShapeVisualResourceKey, context.Compositor.CreateShapeVisual());
96-
shapeVisual.Shapes.Add(shape);
158+
geom.Offset = new Vector2(MaxBlurRadius / 2);
159+
geom.CornerRadius = new Vector2((MaxBlurRadius / 2) + (float)CornerRadius);
160+
shape.StrokeThickness = MaxBlurRadius;
161+
shape.StrokeBrush = shape.StrokeBrush ?? context.Compositor.CreateColorBrush(Colors.Black);
97162

98-
// Create a CompositionVisualSurface, which renders our ShapeVisual to a texture
99-
var visualSurface = context.GetResource(VisualSurfaceResourceKey) ??
100-
context.AddResource(VisualSurfaceResourceKey, context.Compositor.CreateVisualSurface());
101-
visualSurface.SourceVisual = shapeVisual;
163+
if (!shapeVisual.Shapes.Contains(shape))
164+
{
165+
shapeVisual.Shapes.Add(shape);
166+
}
102167

103-
// Create a CompositionSurfaceBrush to render our CompositionVisualSurface to a brush.
104-
// Now we have a rounded rectangle brush that can be used on as the mask for our shadow.
105-
var surfaceBrush = context.GetResource(SurfaceBrushResourceKey) ?? context.AddResource(
106-
SurfaceBrushResourceKey,
107-
context.Compositor.CreateSurfaceBrush(visualSurface));
168+
var visualSurface = context.GetResource(OpacityMaskShapeVisualSurfaceResourceKey) ??
169+
context.AddResource(OpacityMaskShapeVisualSurfaceResourceKey, context.Compositor.CreateVisualSurface());
170+
visualSurface.SourceVisual = shapeVisual;
108171

109-
geometry.Size = visualSurface.SourceSize = shapeVisual.Size = context.Element.RenderSize.ToVector2();
172+
geom.Size = new Vector2((float)context.Element.ActualWidth, (float)context.Element.ActualHeight) + new Vector2(MaxBlurRadius);
173+
shapeVisual.Size = visualSurface.SourceSize = new Vector2((float)context.Element.ActualWidth, (float)context.Element.ActualHeight) + new Vector2(MaxBlurRadius * 2);
110174

111-
return surfaceBrush;
175+
var surfaceBrush = context.GetResource(OpacityMaskShapeVisualSurfaceBrushResourceKey) ??
176+
context.AddResource(OpacityMaskShapeVisualSurfaceBrushResourceKey, context.Compositor.CreateSurfaceBrush());
177+
surfaceBrush.Surface = visualSurface;
112178
}
113179

114180
/// <inheritdoc/>
115181
protected override CompositionClip GetShadowClip(AttachedShadowElementContext context)
116182
{
183+
if (InnerContentClipMode != InnerContentClipMode.CompositionGeometricClip)
184+
{
185+
context.RemoveAndDisposeResource(PathGeometryResourceKey);
186+
context.RemoveAndDisposeResource(ClipResourceKey);
187+
return null;
188+
}
189+
117190
// The way this shadow works without the need to project on another element is because
118191
// we're clipping the inner part of the shadow which would be cast on the element
119192
// itself away. This method is creating an outline so that we are only showing the
@@ -145,6 +218,72 @@ protected override CompositionClip GetShadowClip(AttachedShadowElementContext co
145218
}
146219

147220
/// <inheritdoc/>
221+
protected override CompositionBrush GetShadowMask(AttachedShadowElementContext context)
222+
{
223+
if (!SupportsCompositionVisualSurface)
224+
{
225+
return null;
226+
}
227+
228+
// Create rounded rectangle geometry and add it to a shape
229+
var geometry = context.GetResource(RoundedRectangleGeometryResourceKey) ?? context.AddResource(
230+
RoundedRectangleGeometryResourceKey,
231+
context.Compositor.CreateRoundedRectangleGeometry());
232+
geometry.CornerRadius = new Vector2((float)CornerRadius);
233+
234+
var shape = context.GetResource(ShapeResourceKey) ?? context.AddResource(ShapeResourceKey, context.Compositor.CreateSpriteShape(geometry));
235+
shape.FillBrush = context.Compositor.CreateColorBrush(Colors.Black);
236+
237+
// Create a ShapeVisual so that our geometry can be rendered to a visual
238+
var shapeVisual = context.GetResource(ShapeVisualResourceKey) ??
239+
context.AddResource(ShapeVisualResourceKey, context.Compositor.CreateShapeVisual());
240+
shapeVisual.Shapes.Add(shape);
241+
242+
// Create a CompositionVisualSurface, which renders our ShapeVisual to a texture
243+
var visualSurface = context.GetResource(VisualSurfaceResourceKey) ??
244+
context.AddResource(VisualSurfaceResourceKey, context.Compositor.CreateVisualSurface());
245+
visualSurface.SourceVisual = shapeVisual;
246+
247+
// Create a CompositionSurfaceBrush to render our CompositionVisualSurface to a brush.
248+
// Now we have a rounded rectangle brush that can be used on as the mask for our shadow.
249+
var surfaceBrush = context.GetResource(SurfaceBrushResourceKey) ?? context.AddResource(
250+
SurfaceBrushResourceKey,
251+
context.Compositor.CreateSurfaceBrush(visualSurface));
252+
253+
geometry.Size = visualSurface.SourceSize = shapeVisual.Size = context.Element.RenderSize.ToVector2();
254+
255+
return surfaceBrush;
256+
}
257+
258+
/// <inheritdoc/>
259+
protected override void OnPropertyChanged(AttachedShadowElementContext context, DependencyProperty property, object oldValue, object newValue)
260+
{
261+
if (property == CornerRadiusProperty)
262+
{
263+
UpdateShadowClip(context);
264+
UpdateVisualOpacityMask(context);
265+
266+
var geometry = context.GetResource(RoundedRectangleGeometryResourceKey);
267+
if (geometry != null)
268+
{
269+
geometry.CornerRadius = new Vector2((float)(double)newValue);
270+
}
271+
}
272+
else if (property == InnerContentClipModeProperty)
273+
{
274+
UpdateShadowClip(context);
275+
UpdateVisualOpacityMask(context);
276+
SetElementChildVisual(context);
277+
}
278+
else
279+
{
280+
base.OnPropertyChanged(context, property, oldValue, newValue);
281+
}
282+
283+
base.OnPropertyChanged(context, property, oldValue, newValue);
284+
}
285+
286+
/// <inheritdoc />
148287
protected internal override void OnSizeChanged(AttachedShadowElementContext context, Size newSize, Size previousSize)
149288
{
150289
var sizeAsVec2 = newSize.ToVector2();

0 commit comments

Comments
 (0)