8
8
using Windows . UI ;
9
9
using Windows . UI . Composition ;
10
10
using Windows . UI . Xaml ;
11
+ using Windows . UI . Xaml . Hosting ;
11
12
12
13
namespace Microsoft . Toolkit . Uwp . UI . Media
13
14
{
@@ -20,9 +21,18 @@ namespace Microsoft.Toolkit.Uwp.UI.Media
20
21
public sealed class AttachedCardShadow : AttachedShadowBase
21
22
{
22
23
private const float MaxBlurRadius = 72 ;
23
- private static readonly TypedResourceKey < CompositionGeometricClip > ClipResourceKey = "Clip" ;
24
24
25
+ private static readonly TypedResourceKey < CompositionGeometricClip > ClipResourceKey = "Clip" ;
25
26
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" ;
26
36
private static readonly TypedResourceKey < CompositionRoundedRectangleGeometry > RoundedRectangleGeometryResourceKey = "RoundedGeometry" ;
27
37
private static readonly TypedResourceKey < CompositionSpriteShape > ShapeResourceKey = "Shape" ;
28
38
private static readonly TypedResourceKey < ShapeVisual > ShapeVisualResourceKey = "ShapeVisual" ;
@@ -39,6 +49,16 @@ public sealed class AttachedCardShadow : AttachedShadowBase
39
49
typeof ( AttachedCardShadow ) ,
40
50
new PropertyMetadata ( 4d , OnDependencyPropertyChanged ) ) ; // Default WinUI ControlCornerRadius is 4
41
51
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
+
42
62
/// <summary>
43
63
/// Gets or sets the roundness of the shadow's corners.
44
64
/// </summary>
@@ -48,72 +68,125 @@ public double CornerRadius
48
68
set => SetValue ( CornerRadiusProperty , value ) ;
49
69
}
50
70
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
+
51
80
/// <inheritdoc/>
52
81
public override bool IsSupported => SupportsCompositionVisualSurface ;
53
82
54
83
/// <inheritdoc/>
55
84
protected internal override bool SupportsOnSizeChangedEvent => true ;
56
85
57
86
/// <inheritdoc/>
58
- protected override void OnPropertyChanged ( AttachedShadowElementContext context , DependencyProperty property , object oldValue , object newValue )
87
+ protected internal override void OnElementContextInitialized ( AttachedShadowElementContext context )
59
88
{
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 ) )
61
97
{
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 ) ;
67
105
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 ) ;
69
123
}
70
124
else
71
125
{
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 ) ;
73
131
}
74
132
}
75
133
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 )
78
139
{
79
- if ( ! SupportsCompositionVisualSurface )
140
+ if ( InnerContentClipMode != InnerContentClipMode . CompositionMaskBrush )
80
141
{
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 ;
82
148
}
83
149
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 ( ) ) ;
89
152
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 ) ) ;
92
157
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 ) ;
97
162
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
+ }
102
167
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 ;
108
171
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 ) ;
110
174
111
- return surfaceBrush ;
175
+ var surfaceBrush = context . GetResource ( OpacityMaskShapeVisualSurfaceBrushResourceKey ) ??
176
+ context . AddResource ( OpacityMaskShapeVisualSurfaceBrushResourceKey , context . Compositor . CreateSurfaceBrush ( ) ) ;
177
+ surfaceBrush . Surface = visualSurface ;
112
178
}
113
179
114
180
/// <inheritdoc/>
115
181
protected override CompositionClip GetShadowClip ( AttachedShadowElementContext context )
116
182
{
183
+ if ( InnerContentClipMode != InnerContentClipMode . CompositionGeometricClip )
184
+ {
185
+ context . RemoveAndDisposeResource ( PathGeometryResourceKey ) ;
186
+ context . RemoveAndDisposeResource ( ClipResourceKey ) ;
187
+ return null ;
188
+ }
189
+
117
190
// The way this shadow works without the need to project on another element is because
118
191
// we're clipping the inner part of the shadow which would be cast on the element
119
192
// 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
145
218
}
146
219
147
220
/// <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 />
148
287
protected internal override void OnSizeChanged ( AttachedShadowElementContext context , Size newSize , Size previousSize )
149
288
{
150
289
var sizeAsVec2 = newSize . ToVector2 ( ) ;
0 commit comments