1+ using System ;
2+ using System . Numerics ;
3+ using Windows . Foundation ;
4+ using Windows . UI ;
5+ using Windows . UI . Composition ;
6+ using Windows . UI . Xaml ;
7+ using Windows . UI . Xaml . Controls ;
8+ using Windows . UI . Xaml . Data ;
9+ using Windows . UI . Xaml . Hosting ;
10+ using Windows . UI . Xaml . Markup ;
11+ using Windows . UI . Xaml . Shapes ;
12+
13+ namespace Telerik . UI . Xaml . Controls . Primitives
14+ {
15+ /// <summary>
16+ /// Represents a control that enables a user to show a shadow around another view.
17+ /// </summary>
18+ [ TemplatePart ( Name = "PART_Shadow" , Type = typeof ( Canvas ) ) ]
19+ [ ContentProperty ( Name = nameof ( Content ) ) ]
20+ public class RadShadow : RadControl
21+ {
22+ /// <summary>
23+ /// Identifies the <see cref="Color"/> dependency property.
24+ /// </summary>
25+ public static readonly DependencyProperty ColorProperty =
26+ DependencyProperty . Register ( nameof ( Color ) , typeof ( Color ) , typeof ( RadShadow ) , new PropertyMetadata ( Colors . Black , new PropertyChangedCallback ( ( d , e ) => ( ( RadShadow ) d ) . OnColorPropertyChanged ( ) ) ) ) ;
27+
28+ /// <summary>
29+ /// Identifies the <see cref="OffsetX"/> dependency property.
30+ /// </summary>
31+ public static readonly DependencyProperty OffsetXProperty =
32+ DependencyProperty . Register ( nameof ( OffsetX ) , typeof ( double ) , typeof ( RadShadow ) , new PropertyMetadata ( 0.0 , new PropertyChangedCallback ( ( d , e ) => ( ( RadShadow ) d ) . OnOffsetPropertyChanged ( ) ) ) ) ;
33+
34+ /// <summary>
35+ /// Identifies the <see cref="OffsetY"/> dependency property.
36+ /// </summary>
37+ public static readonly DependencyProperty OffsetYProperty =
38+ DependencyProperty . Register ( nameof ( OffsetY ) , typeof ( double ) , typeof ( RadShadow ) , new PropertyMetadata ( 0.0 , new PropertyChangedCallback ( ( d , e ) => ( ( RadShadow ) d ) . OnOffsetPropertyChanged ( ) ) ) ) ;
39+
40+ /// <summary>
41+ /// Identifies the <see cref="BlurRadius"/> dependency property.
42+ /// </summary>
43+ public static readonly DependencyProperty BlurRadiusProperty =
44+ DependencyProperty . Register ( nameof ( BlurRadius ) , typeof ( double ) , typeof ( RadShadow ) , new PropertyMetadata ( 9.0 , new PropertyChangedCallback ( ( d , e ) => ( ( RadShadow ) d ) . OnBlurRadiusPropertyChanged ( ) ) ) ) ;
45+
46+ /// <summary>
47+ /// Identifies the <see cref="ShadowOpacity"/> dependency property.
48+ /// </summary>
49+ public static readonly DependencyProperty ShadowOpacityProperty =
50+ DependencyProperty . Register ( nameof ( ShadowOpacity ) , typeof ( double ) , typeof ( RadShadow ) , new PropertyMetadata ( 0.26 , new PropertyChangedCallback ( ( d , e ) => ( ( RadShadow ) d ) . OnShadowOpacityPropertyChanged ( ) ) ) ) ;
51+
52+ /// <summary>
53+ /// Identifies the <see cref="Content"/> dependency property.
54+ /// </summary>
55+ public static readonly DependencyProperty ContentProperty =
56+ DependencyProperty . Register ( nameof ( Content ) , typeof ( object ) , typeof ( RadShadow ) , new PropertyMetadata ( null , new PropertyChangedCallback ( ( d , e ) => ( ( RadShadow ) d ) . Invalidate ( ) ) ) ) ;
57+
58+ /// <summary>
59+ /// Identifies the <see cref="CornerRadius"/> dependency property.
60+ /// </summary>
61+ public static readonly DependencyProperty CornerRadiusProperty =
62+ DependencyProperty . Register ( nameof ( CornerRadius ) , typeof ( double ) , typeof ( RadShadow ) , new PropertyMetadata ( 0.0d , new PropertyChangedCallback ( ( d , e ) => ( ( RadShadow ) d ) . OnCornerRadiusPropertyChanged ( ) ) ) ) ;
63+
64+ private const string PartShadowName = "PART_Shadow" ;
65+
66+ private bool invalidateShadowMask ;
67+ private SpriteVisual shadowVisual ;
68+ private DropShadow dropShadow ;
69+ private Canvas shadowView ;
70+ private Rectangle radiusMask ;
71+
72+ /// <summary>
73+ /// Initializes a new instance of the <see cref="RadShadow"/> class.
74+ /// </summary>
75+ public RadShadow ( )
76+ {
77+ this . DefaultStyleKey = typeof ( RadShadow ) ;
78+ }
79+
80+ /// <summary>
81+ /// Gets or sets the color of the shadow.
82+ /// </summary>
83+ public Color Color
84+ {
85+ get { return ( Color ) this . GetValue ( ColorProperty ) ; }
86+ set { this . SetValue ( ColorProperty , value ) ; }
87+ }
88+
89+ /// <summary>
90+ /// Gets or sets the X offset of the shadow from its content.
91+ /// </summary>
92+ public double OffsetX
93+ {
94+ get { return ( double ) this . GetValue ( OffsetXProperty ) ; }
95+ set { this . SetValue ( OffsetXProperty , value ) ; }
96+ }
97+
98+ /// <summary>
99+ /// Gets or sets the Y offset of the shadow from its content.
100+ /// </summary>
101+ public double OffsetY
102+ {
103+ get { return ( double ) this . GetValue ( OffsetYProperty ) ; }
104+ set { this . SetValue ( OffsetYProperty , value ) ; }
105+ }
106+
107+ /// <summary>
108+ /// Gets or sets the blur radius of the shadow.
109+ /// </summary>
110+ public double BlurRadius
111+ {
112+ get { return ( double ) this . GetValue ( BlurRadiusProperty ) ; }
113+ set { this . SetValue ( BlurRadiusProperty , value ) ; }
114+ }
115+
116+ /// <summary>
117+ /// Gets or sets the opacity of the shadow.
118+ /// </summary>
119+ public double ShadowOpacity
120+ {
121+ get { return ( double ) this . GetValue ( ShadowOpacityProperty ) ; }
122+ set { this . SetValue ( ShadowOpacityProperty , value ) ; }
123+ }
124+
125+ /// <summary>
126+ /// Gets or sets the content of the RadShadow.
127+ /// </summary>
128+ public object Content
129+ {
130+ get { return ( object ) this . GetValue ( ContentProperty ) ; }
131+ set { this . SetValue ( ContentProperty , value ) ; }
132+ }
133+
134+ /// <summary>
135+ /// Gets or sets the corner radius of the shadow.
136+ /// </summary>
137+ public double CornerRadius
138+ {
139+ get { return ( double ) this . GetValue ( CornerRadiusProperty ) ; }
140+ set { this . SetValue ( CornerRadiusProperty , value ) ; }
141+ }
142+
143+ /// <summary>
144+ /// Use to get the shadow mask if the Content has such mask. Views like Shapes, TextBlock and Image provide masks - you can get them through the GetAlphaMask methods.
145+ /// </summary>
146+ /// <param name="content">The content of the shadow control.</param>
147+ /// <returns>The alpha mask applied to the shadow.</returns>
148+ public virtual CompositionBrush GetShadowMask ( FrameworkElement content )
149+ {
150+ var shape = content as Shape ;
151+ if ( shape != null )
152+ {
153+ return shape . GetAlphaMask ( ) ;
154+ }
155+
156+ var textBlock = content as TextBlock ;
157+ if ( textBlock != null )
158+ {
159+ return textBlock . GetAlphaMask ( ) ;
160+ }
161+
162+ var image = content as Image ;
163+ if ( image != null )
164+ {
165+ return image . GetAlphaMask ( ) ;
166+ }
167+
168+ return this . radiusMask ? . GetAlphaMask ( ) ;
169+ }
170+
171+ /// <inheritdoc />
172+ protected override Size ArrangeOverride ( Size finalSize )
173+ {
174+ var size = base . ArrangeOverride ( finalSize ) ;
175+
176+ var content = this . GetVisualContent ( ) ;
177+ if ( content != null )
178+ {
179+ var contentPosition = content . TransformToVisual ( this ) ;
180+ var offset = contentPosition . TransformPoint ( new Point ( 0 , 0 ) ) ;
181+
182+ var x = ( float ) offset . X - ( float ) this . BorderThickness . Left ;
183+ var y = ( float ) offset . Y - ( float ) this . BorderThickness . Top ;
184+ this . shadowVisual . Offset = new Vector3 ( x , y , 0 ) ;
185+
186+ var width = content . ActualWidth ;
187+ var height = content . ActualHeight ;
188+ this . shadowVisual . Size = new Vector2 ( ( float ) width , ( float ) height ) ;
189+
190+ if ( this . radiusMask != null )
191+ {
192+ this . radiusMask . Width = width ;
193+ this . radiusMask . Height = height ;
194+
195+ Canvas . SetTop ( this . radiusMask , y ) ;
196+ Canvas . SetLeft ( this . radiusMask , x ) ;
197+ }
198+
199+ this . ApplyShadowMaskIfNeeded ( content ) ;
200+ }
201+
202+ return size ;
203+ }
204+
205+ /// <inheritdoc />
206+ protected override bool ApplyTemplateCore ( )
207+ {
208+ bool applied = base . ApplyTemplateCore ( ) ;
209+
210+ this . shadowView = this . GetTemplatePartField < Canvas > ( PartShadowName ) ;
211+ applied = applied && this . shadowView != null ;
212+
213+ if ( applied )
214+ {
215+ this . InitializeDropShadow ( ) ;
216+ }
217+
218+ return applied ;
219+ }
220+
221+ private void InitializeDropShadow ( )
222+ {
223+ var compositor = ElementCompositionPreview . GetElementVisual ( this . shadowView ) . Compositor ;
224+ this . shadowVisual = compositor . CreateSpriteVisual ( ) ;
225+ this . dropShadow = compositor . CreateDropShadow ( ) ;
226+
227+ this . OnColorPropertyChanged ( ) ;
228+ this . OnOffsetPropertyChanged ( ) ;
229+ this . OnBlurRadiusPropertyChanged ( ) ;
230+ this . OnShadowOpacityPropertyChanged ( ) ;
231+ this . OnCornerRadiusPropertyChanged ( ) ;
232+
233+ this . shadowVisual . Shadow = this . dropShadow ;
234+
235+ ElementCompositionPreview . SetElementChildVisual ( this . shadowView , this . shadowVisual ) ;
236+ }
237+
238+ private void ApplyShadowMaskIfNeeded ( FrameworkElement content )
239+ {
240+ if ( ! this . invalidateShadowMask )
241+ {
242+ return ;
243+ }
244+
245+ this . invalidateShadowMask = false ;
246+
247+ this . dropShadow . Mask = this . GetShadowMask ( content ) ;
248+ }
249+
250+ private FrameworkElement GetVisualContent ( )
251+ {
252+ var content = this . Content ;
253+ if ( content == null )
254+ {
255+ return null ;
256+ }
257+
258+ FrameworkElement visualContent = this . Content as FrameworkElement ;
259+ if ( visualContent == null )
260+ {
261+ visualContent = ElementTreeHelper . FindVisualDescendant < TextBlock > ( this ) ;
262+ }
263+
264+ return visualContent ;
265+ }
266+
267+ private void OnColorPropertyChanged ( )
268+ {
269+ if ( this . dropShadow != null )
270+ {
271+ this . dropShadow . Color = this . Color ;
272+ }
273+ }
274+
275+ private void OnOffsetPropertyChanged ( )
276+ {
277+ if ( this . dropShadow != null )
278+ {
279+ this . dropShadow . Offset = new Vector3 ( ( float ) this . OffsetX , ( float ) this . OffsetY , 0f ) ;
280+ }
281+ }
282+
283+ private void OnBlurRadiusPropertyChanged ( )
284+ {
285+ if ( this . dropShadow != null )
286+ {
287+ this . dropShadow . BlurRadius = ( float ) this . BlurRadius ;
288+ }
289+ }
290+
291+ private void OnShadowOpacityPropertyChanged ( )
292+ {
293+ if ( this . dropShadow != null )
294+ {
295+ this . dropShadow . Opacity = ( float ) this . ShadowOpacity ;
296+ }
297+ }
298+
299+ private void OnCornerRadiusPropertyChanged ( )
300+ {
301+ if ( this . shadowView == null )
302+ {
303+ return ;
304+ }
305+
306+ var cornerRadius = this . CornerRadius ;
307+ if ( cornerRadius < 0 )
308+ {
309+ throw new ArgumentException ( $ "{ cornerRadius } is an invalid value for { nameof ( this . CornerRadius ) } ") ;
310+ }
311+
312+ if ( cornerRadius > 0 )
313+ {
314+ if ( this . radiusMask == null )
315+ {
316+ this . radiusMask = new Rectangle ( ) ;
317+ this . radiusMask . SetBinding ( Rectangle . FillProperty , new Binding ( ) { Path = new PropertyPath ( nameof ( this . Background ) ) , Source = this , Mode = BindingMode . TwoWay } ) ;
318+ this . shadowView . Children . Add ( this . radiusMask ) ;
319+ }
320+
321+ this . radiusMask . RadiusY = cornerRadius ;
322+ this . radiusMask . RadiusX = cornerRadius ;
323+ }
324+ else
325+ {
326+ if ( this . shadowView . Children . Contains ( this . radiusMask ) )
327+ {
328+ this . shadowView . Children . Remove ( this . radiusMask ) ;
329+ }
330+
331+ this . radiusMask ? . ClearValue ( Rectangle . FillProperty ) ;
332+ this . radiusMask = null ;
333+ }
334+
335+ this . Invalidate ( ) ;
336+ }
337+
338+ private void Invalidate ( )
339+ {
340+ this . invalidateShadowMask = true ;
341+ this . InvalidateArrange ( ) ;
342+ }
343+ }
344+ }
0 commit comments