1
+ // ------------------------------------------------------
2
+ // Copyright (C) Microsoft. All rights reserved.
3
+ // ------------------------------------------------------
4
+
5
+ using Microsoft . Graphics . Canvas . Geometry ;
6
+ using System . Numerics ;
7
+ using Windows . Foundation ;
8
+ using Windows . Foundation . Metadata ;
9
+ using Windows . UI ;
10
+ using Windows . UI . Composition ;
11
+ using Windows . UI . Xaml ;
12
+
13
+ namespace Microsoft . Toolkit . Uwp . UI . Media . Shadows
14
+ {
15
+ public class AttachedCardShadow : AttachedShadowBase
16
+ {
17
+ private const float MaxBlurRadius = 72 ;
18
+ private static readonly TypedResourceKey < CompositionGeometricClip > ClipResourceKey = "Clip" ;
19
+ private static readonly bool SupportsCompositionVisualSurface ;
20
+ private static readonly bool SupportsCompositionGeometricClip ;
21
+
22
+ private static readonly TypedResourceKey < CompositionPathGeometry > PathGeometryResourceKey = "PathGeometry" ;
23
+ private static readonly TypedResourceKey < CompositionRoundedRectangleGeometry > RoundedRectangleGeometryResourceKey = "RoundedGeometry" ;
24
+ private static readonly TypedResourceKey < CompositionSpriteShape > ShapeResourceKey = "Shape" ;
25
+ private static readonly TypedResourceKey < ShapeVisual > ShapeVisualResourceKey = "ShapeVisual" ;
26
+ private static readonly TypedResourceKey < CompositionSurfaceBrush > SurfaceBrushResourceKey = "SurfaceBrush" ;
27
+ private static readonly TypedResourceKey < CompositionVisualSurface > VisualSurfaceResourceKey = "VisualSurface" ;
28
+
29
+ public static readonly DependencyProperty CornerRadiusProperty =
30
+ DependencyProperty . Register (
31
+ nameof ( CornerRadius ) ,
32
+ typeof ( double ) ,
33
+ typeof ( AttachedCardShadow ) ,
34
+ new PropertyMetadata ( 8d , OnDependencyPropertyChanged ) ) ;
35
+
36
+ static AttachedCardShadow ( )
37
+ {
38
+ SupportsCompositionGeometricClip = ApiInformation . IsTypePresent ( typeof ( CompositionGeometricClip ) . FullName ) ;
39
+ SupportsCompositionVisualSurface = ApiInformation . IsTypePresent ( typeof ( CompositionVisualSurface ) . FullName ) ; ;
40
+ }
41
+
42
+ /// <summary>
43
+ /// Gets or sets the roundness of the shadow's corners
44
+ /// </summary>
45
+ public double CornerRadius
46
+ {
47
+ get => ( double ) GetValue ( CornerRadiusProperty ) ;
48
+ set => SetValue ( CornerRadiusProperty , value ) ;
49
+ }
50
+
51
+ /// <summary>
52
+ /// <inheritdoc/>
53
+ /// </summary>
54
+ public override bool IsSupported => SupportsCompositionVisualSurface ;
55
+
56
+ /// <summary>
57
+ /// <inheritdoc/>
58
+ /// </summary>
59
+ public override bool SupportsOnSizeChangedEvent => true ;
60
+
61
+ protected override void OnElementContextUninitialized ( AttachedShadowElementContext context )
62
+ {
63
+ context . ClearAndDisposeResources ( ) ;
64
+ base . OnElementContextUninitialized ( context ) ;
65
+ }
66
+
67
+ protected override void OnPropertyChanged ( AttachedShadowElementContext context , DependencyProperty property , object oldValue , object newValue )
68
+ {
69
+ if ( property == CornerRadiusProperty )
70
+ {
71
+ var geometry = context . GetResource ( RoundedRectangleGeometryResourceKey ) ;
72
+ if ( geometry != null )
73
+ {
74
+ geometry . CornerRadius = new Vector2 ( ( float ) ( double ) newValue ) ;
75
+ }
76
+
77
+ UpdateShadowClip ( context ) ;
78
+ }
79
+ else
80
+ {
81
+ base . OnPropertyChanged ( context , property , oldValue , newValue ) ;
82
+ }
83
+ }
84
+
85
+ protected override CompositionBrush GetShadowMask ( AttachedShadowElementContext context )
86
+ {
87
+ if ( ! SupportsCompositionVisualSurface )
88
+ {
89
+ return null ;
90
+ }
91
+
92
+ // Create rounded rectangle geometry and add it to a shape
93
+ var geometry = context . GetResource ( RoundedRectangleGeometryResourceKey ) ?? context . AddResource (
94
+ RoundedRectangleGeometryResourceKey ,
95
+ context . Compositor . CreateRoundedRectangleGeometry ( ) ) ;
96
+ geometry . CornerRadius = new Vector2 ( ( float ) CornerRadius ) ;
97
+
98
+ var shape = context . GetResource ( ShapeResourceKey ) ?? context . AddResource ( ShapeResourceKey , context . Compositor . CreateSpriteShape ( geometry ) ) ;
99
+ shape . FillBrush = context . Compositor . CreateColorBrush ( Colors . Black ) ;
100
+
101
+ // Create a ShapeVisual so that our geometry can be rendered to a visual
102
+ var shapeVisual = context . GetResource ( ShapeVisualResourceKey ) ??
103
+ context . AddResource ( ShapeVisualResourceKey , context . Compositor . CreateShapeVisual ( ) ) ;
104
+ shapeVisual . Shapes . Add ( shape ) ;
105
+
106
+ // Create a CompositionVisualSurface, which renders our ShapeVisual to a texture
107
+ var visualSurface = context . GetResource ( VisualSurfaceResourceKey ) ??
108
+ context . AddResource ( VisualSurfaceResourceKey , context . Compositor . CreateVisualSurface ( ) ) ;
109
+ visualSurface . SourceVisual = shapeVisual ;
110
+
111
+ // Create a CompositionSurfaceBrush to render our CompositionVisualSurface to a brush.
112
+ // Now we have a rounded rectangle brush that can be used on as the mask for our shadow.
113
+ var surfaceBrush = context . GetResource ( SurfaceBrushResourceKey ) ?? context . AddResource (
114
+ SurfaceBrushResourceKey ,
115
+ context . Compositor . CreateSurfaceBrush ( visualSurface ) ) ;
116
+
117
+ geometry . Size = visualSurface . SourceSize = shapeVisual . Size = context . Element . RenderSize . ToVector2 ( ) ;
118
+
119
+ return surfaceBrush ;
120
+ }
121
+
122
+ protected override CompositionClip GetShadowClip ( AttachedShadowElementContext context )
123
+ {
124
+ var pathGeom = context . GetResource ( PathGeometryResourceKey ) ??
125
+ context . AddResource ( PathGeometryResourceKey , context . Compositor . CreatePathGeometry ( ) ) ;
126
+ var clip = context . GetResource ( ClipResourceKey ) ?? context . AddResource ( ClipResourceKey , context . Compositor . CreateGeometricClip ( pathGeom ) ) ;
127
+
128
+ // Create rounded rectangle geometry at a larger size that compensates for the size of the stroke,
129
+ // as we want the inside edge of the stroke to match the edges of the element.
130
+ // Additionally, the inside edge of the stroke will have a smaller radius than the radius we specified.
131
+ // Using "(StrokeThickness / 2) + Radius" as our rectangle's radius will give us an inside stroke radius that matches the radius we want.
132
+ var canvasRectangle = CanvasGeometry . CreateRoundedRectangle (
133
+ null ,
134
+ - MaxBlurRadius / 2 ,
135
+ - MaxBlurRadius / 2 ,
136
+ ( float ) context . Element . ActualWidth + MaxBlurRadius ,
137
+ ( float ) context . Element . ActualHeight + MaxBlurRadius ,
138
+ ( MaxBlurRadius / 2 ) + ( float ) CornerRadius ,
139
+ ( MaxBlurRadius / 2 ) + ( float ) CornerRadius ) ;
140
+
141
+ var canvasStroke = canvasRectangle . Stroke ( MaxBlurRadius ) ;
142
+
143
+ pathGeom . Path = new CompositionPath ( canvasStroke ) ;
144
+
145
+ return clip ;
146
+ }
147
+
148
+ protected override void OnSizeChanged ( AttachedShadowElementContext context , Size newSize , Size previousSize )
149
+ {
150
+ var sizeAsVec2 = newSize . ToVector2 ( ) ;
151
+
152
+ var geometry = context . GetResource ( RoundedRectangleGeometryResourceKey ) ;
153
+ if ( geometry != null )
154
+ {
155
+ geometry . Size = sizeAsVec2 ;
156
+ }
157
+
158
+ var visualSurface = context . GetResource ( VisualSurfaceResourceKey ) ;
159
+ if ( geometry != null )
160
+ {
161
+ visualSurface . SourceSize = sizeAsVec2 ;
162
+ }
163
+
164
+ var shapeVisual = context . GetResource ( ShapeVisualResourceKey ) ;
165
+ if ( geometry != null )
166
+ {
167
+ shapeVisual . Size = sizeAsVec2 ;
168
+ }
169
+
170
+ UpdateShadowClip ( context ) ;
171
+
172
+ base . OnSizeChanged ( context , newSize , previousSize ) ;
173
+ }
174
+ }
175
+ }
0 commit comments