-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
AttachedShadowBase.cs
290 lines (254 loc) · 11.5 KB
/
AttachedShadowBase.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using Windows.Foundation;
using Windows.Foundation.Metadata;
using Windows.UI;
using Windows.UI.Composition;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Hosting;
namespace Microsoft.Toolkit.Uwp.UI
{
/// <summary>
/// The base class for attached shadows.
/// </summary>
public abstract class AttachedShadowBase : DependencyObject, IAttachedShadow
{
/// <summary>
/// Gets a value indicating whether or not Composition's VisualSurface is supported.
/// </summary>
protected static readonly bool SupportsCompositionVisualSurface = ApiInformation.IsTypePresent(typeof(CompositionVisualSurface).FullName);
/// <summary>
/// The <see cref="DependencyProperty"/> for <see cref="BlurRadius"/>.
/// </summary>
public static readonly DependencyProperty BlurRadiusProperty =
DependencyProperty.Register(nameof(BlurRadius), typeof(double), typeof(AttachedShadowBase), new PropertyMetadata(12d, OnDependencyPropertyChanged));
/// <summary>
/// The <see cref="DependencyProperty"/> for <see cref="Color"/>.
/// </summary>
public static readonly DependencyProperty ColorProperty =
DependencyProperty.Register(nameof(Color), typeof(Color), typeof(AttachedShadowBase), new PropertyMetadata(Colors.Black, OnDependencyPropertyChanged));
/// <summary>
/// The <see cref="DependencyProperty"/> for <see cref="Opacity"/>.
/// </summary>
public static readonly DependencyProperty OffsetProperty =
DependencyProperty.Register(
nameof(Offset),
typeof(string), // Needs to be string as we can't convert in XAML natively from Vector3, see https://github.com/microsoft/microsoft-ui-xaml/issues/3896
typeof(AttachedShadowBase),
new PropertyMetadata(string.Empty, OnDependencyPropertyChanged));
/// <summary>
/// The <see cref="DependencyProperty"/> for <see cref="Opacity"/>
/// </summary>
public static readonly DependencyProperty OpacityProperty =
DependencyProperty.Register(nameof(Opacity), typeof(double), typeof(AttachedShadowBase), new PropertyMetadata(1d, OnDependencyPropertyChanged));
/// <summary>
/// Gets a value indicating whether or not this <see cref="AttachedShadowBase"/> implementation is supported on the current platform.
/// </summary>
public abstract bool IsSupported { get; }
/// <summary>
/// Gets or sets the collection of <see cref="AttachedShadowElementContext"/> for each element this <see cref="AttachedShadowBase"/> is connected to.
/// </summary>
private ConditionalWeakTable<FrameworkElement, AttachedShadowElementContext> ShadowElementContextTable { get; set; }
/// <inheritdoc/>
public double BlurRadius
{
get => (double)GetValue(BlurRadiusProperty);
set => SetValue(BlurRadiusProperty, value);
}
/// <inheritdoc/>
public double Opacity
{
get => (double)GetValue(OpacityProperty);
set => SetValue(OpacityProperty, value);
}
/// <inheritdoc/>
public string Offset
{
get => (string)GetValue(OffsetProperty);
set => SetValue(OffsetProperty, value);
}
/// <inheritdoc/>
public Color Color
{
get => (Color)GetValue(ColorProperty);
set => SetValue(ColorProperty, value);
}
/// <summary>
/// Gets a value indicating whether or not OnSizeChanged should be called when <see cref="FrameworkElement.SizeChanged"/> is fired.
/// </summary>
protected internal abstract bool SupportsOnSizeChangedEvent { get; }
/// <summary>
/// Use this method as the <see cref="PropertyChangedCallback"/> for <see cref="DependencyProperty">DependencyProperties</see> in derived classes.
/// </summary>
protected static void OnDependencyPropertyChanged(object sender, DependencyPropertyChangedEventArgs args)
{
(sender as AttachedShadowBase)?.CallPropertyChangedForEachElement(args.Property, args.OldValue, args.NewValue);
}
internal void ConnectElement(FrameworkElement element)
{
if (!IsSupported)
{
return;
}
ShadowElementContextTable = ShadowElementContextTable ?? new ConditionalWeakTable<FrameworkElement, AttachedShadowElementContext>();
if (ShadowElementContextTable.TryGetValue(element, out var context))
{
return;
}
context = new AttachedShadowElementContext();
context.ConnectToElement(this, element);
ShadowElementContextTable.Add(element, context);
}
internal void DisconnectElement(FrameworkElement element)
{
if (ShadowElementContextTable == null)
{
return;
}
if (ShadowElementContextTable.TryGetValue(element, out var context))
{
context.DisconnectFromElement();
ShadowElementContextTable.Remove(element);
}
}
/// <summary>
/// Override to handle when the <see cref="AttachedShadowElementContext"/> for an element is being initialized.
/// </summary>
/// <param name="context">The <see cref="AttachedShadowElementContext"/> that is being initialized.</param>
protected internal virtual void OnElementContextInitialized(AttachedShadowElementContext context)
{
OnPropertyChanged(context, OpacityProperty, Opacity, Opacity);
OnPropertyChanged(context, BlurRadiusProperty, BlurRadius, BlurRadius);
OnPropertyChanged(context, ColorProperty, Color, Color);
OnPropertyChanged(context, OffsetProperty, Offset, Offset);
UpdateShadowClip(context);
UpdateShadowMask(context);
SetElementChildVisual(context);
}
/// <summary>
/// Override to handle when the <see cref="AttachedShadowElementContext"/> for an element is being uninitialized.
/// </summary>
/// <param name="context">The <see cref="AttachedShadowElementContext"/> that is being uninitialized.</param>
protected internal virtual void OnElementContextUninitialized(AttachedShadowElementContext context)
{
context.ClearAndDisposeResources();
ElementCompositionPreview.SetElementChildVisual(context.Element, null);
}
/// <inheritdoc/>
public AttachedShadowElementContext GetElementContext(FrameworkElement element)
{
if (ShadowElementContextTable != null && ShadowElementContextTable.TryGetValue(element, out var context))
{
return context;
}
return null;
}
/// <inheritdoc/>
public IEnumerable<AttachedShadowElementContext> EnumerateElementContexts()
{
foreach (var kvp in ShadowElementContextTable)
{
yield return kvp.Value;
}
}
/// <summary>
/// Sets <see cref="AttachedShadowElementContext.SpriteVisual"/> as a child visual on <see cref="AttachedShadowElementContext.Element"/>
/// </summary>
/// <param name="context">The <see cref="AttachedShadowElementContext"/> this operaiton will be performed on.</param>
protected virtual void SetElementChildVisual(AttachedShadowElementContext context)
{
ElementCompositionPreview.SetElementChildVisual(context.Element, context.SpriteVisual);
}
private void CallPropertyChangedForEachElement(DependencyProperty property, object oldValue, object newValue)
{
if (ShadowElementContextTable == null)
{
return;
}
foreach (var context in ShadowElementContextTable)
{
if (context.Value.IsInitialized)
{
OnPropertyChanged(context.Value, property, oldValue, newValue);
}
}
}
/// <summary>
/// Get a <see cref="CompositionBrush"/> in the shape of the element that is casting the shadow.
/// </summary>
/// <returns>A <see cref="CompositionBrush"/> representing the shape of an element.</returns>
protected virtual CompositionBrush GetShadowMask(AttachedShadowElementContext context)
{
return null;
}
/// <summary>
/// Get the <see cref="CompositionClip"/> for the shadow's <see cref="SpriteVisual"/>
/// </summary>
/// <returns>A <see cref="CompositionClip"/> for the extent of the shadowed area.</returns>
protected virtual CompositionClip GetShadowClip(AttachedShadowElementContext context)
{
return null;
}
/// <summary>
/// Update the mask that gives the shadow its shape.
/// </summary>
protected void UpdateShadowMask(AttachedShadowElementContext context)
{
if (!context.IsInitialized)
{
return;
}
context.Shadow.Mask = GetShadowMask(context);
}
/// <summary>
/// Update the clipping on the shadow's <see cref="SpriteVisual"/>.
/// </summary>
protected void UpdateShadowClip(AttachedShadowElementContext context)
{
if (!context.IsInitialized)
{
return;
}
context.SpriteVisual.Clip = GetShadowClip(context);
}
/// <summary>
/// This method is called when a DependencyProperty is changed.
/// </summary>
protected virtual void OnPropertyChanged(AttachedShadowElementContext context, DependencyProperty property, object oldValue, object newValue)
{
if (!context.IsInitialized)
{
return;
}
if (property == BlurRadiusProperty)
{
context.Shadow.BlurRadius = (float)(double)newValue;
}
else if (property == OpacityProperty)
{
context.Shadow.Opacity = (float)(double)newValue;
}
else if (property == ColorProperty)
{
context.Shadow.Color = (Color)newValue;
}
else if (property == OffsetProperty)
{
context.Shadow.Offset = (Vector3)(newValue as string)?.ToVector3();
}
}
/// <summary>
/// This method is called when the element size changes, and <see cref="SupportsOnSizeChangedEvent"/> = <see cref="bool">true</see>.
/// </summary>
/// <param name="context">The <see cref="AttachedShadowElementContext"/> for the <see cref="FrameworkElement"/> firing its SizeChanged event</param>
/// <param name="newSize">The new size of the <see cref="FrameworkElement"/></param>
/// <param name="previousSize">The previous size of the <see cref="FrameworkElement"/></param>
protected internal virtual void OnSizeChanged(AttachedShadowElementContext context, Size newSize, Size previousSize)
{
}
}
}