88using Windows . UI ;
99using Windows . UI . Composition ;
1010using Windows . UI . Xaml ;
11+ using Windows . UI . Xaml . Hosting ;
1112
1213namespace 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