22// The .NET Foundation licenses this file to you under the MIT license.
33// See the LICENSE file in the project root for more information.
44
5- // DropShadows are currently disabled because of LayerVisual bugs.
6- //#define EnableDropShadow
75#nullable enable
86
97using System ;
8+ using System . Collections . Generic ;
109using System . Diagnostics ;
1110using System . Linq ;
1211using Microsoft . Toolkit . Uwp . UI . Lottie . Animatables ;
@@ -73,16 +72,12 @@ static class PreComps
7372 result . Children . Add ( rootNode ) ;
7473 }
7574
76- #if EnableDropShadow
7775 var dropShadowEffect = context . Effects . DropShadowEffect ;
7876
7977 if ( dropShadowEffect is not null )
8078 {
8179 result = ApplyDropShadow ( context , result , dropShadowEffect ) ;
8280 }
83- #else
84- context . Effects . EmitIssueIfDropShadow ( ) ;
85- #endif
8681
8782 var gaussianBlurEffect = context . Effects . GaussianBlurEffect ;
8883
@@ -97,59 +92,84 @@ static class PreComps
9792 /// <summary>
9893 /// Applies the given <see cref="DropShadowEffect"/>.
9994 /// </summary>
100- static LayerVisual ApplyDropShadow ( PreCompLayerContext context , Visual visual , DropShadowEffect dropShadowEffect )
95+ static ContainerVisual ApplyDropShadow (
96+ PreCompLayerContext context ,
97+ ContainerVisual source ,
98+ DropShadowEffect dropShadowEffect )
10199 {
102100 Debug . Assert ( dropShadowEffect . IsEnabled , "Precondition" ) ;
103101
104- // Create a LayerVisual so we can add a drop shadow.
105- var result = context . ObjectFactory . CreateLayerVisual ( ) ;
106- result . Children . Add ( visual ) ;
102+ // Shadow:
103+ // +------------------+
104+ // | Container Visual | -- Has the final composited result.
105+ // +------------------+ <
106+ // ^ Child #1 \ Child #2 (original layer)
107+ // | (shadow layer) \
108+ // | \
109+ // +---------------------+ \
110+ // | ApplyGaussianBlur() | \
111+ // +---------------------+ +-----------------+
112+ // ^ | ContainerVisual | - Original Visual node.
113+ // | +-----------------+
114+ // +----------------+ .
115+ // | SpriteVisual | .
116+ // +----------------+ .
117+ // ^ Source .
118+ // | .
119+ // +--------------+ .
120+ // | MaskBrush | .
121+ // +--------------+ .
122+ // ^ Source ^ Mask . Source
123+ // | \ V
124+ // +----------+ +---------------+
125+ // |ColorBrush| | VisualSurface |
126+ // +----------+ +---------------+
127+ GaussianBlurEffect gaussianBlurEffect = new GaussianBlurEffect (
128+ name : dropShadowEffect . Name + "_blur" ,
129+ isEnabled : true ,
130+ blurriness : dropShadowEffect . Softness ,
131+ blurDimensions : new Animatable < Enum < BlurDimension > > ( BlurDimension . HorizontalAndVertical ) ,
132+ repeatEdgePixels : new Animatable < bool > ( true ) ,
133+ forceGpuRendering : true ) ;
107134
108- // TODO: Due to a Composition bug, LayerVisual currently must be given a size for the drop
109- // shadow to show up correctly. And even then it is not reliable.
110- result . Size = context . CompositionContext . Size ;
111-
112- var shadow = context . ObjectFactory . CreateDropShadow ( ) ;
135+ var factory = context . ObjectFactory ;
136+ var size = ConvertTo . Vector2 ( context . Layer . Width , context . Layer . Height ) ;
113137
114- result . Shadow = shadow ;
115- shadow . SourcePolicy = CompositionDropShadowSourcePolicy . InheritFromVisualContent ;
138+ var visualSurface = factory . CreateVisualSurface ( ) ;
139+ visualSurface . SourceSize = size ;
140+ visualSurface . SourceVisual = source ;
116141
117- var isShadowOnly = Optimizer . TrimAnimatable ( context , dropShadowEffect . IsShadowOnly ) ;
118- if ( ! isShadowOnly . IsAlways ( true ) )
119- {
120- context . Issues . ShadowOnlyShadowEffect ( ) ;
121- }
142+ var maskBrush = factory . CreateMaskBrush ( ) ;
122143
123- // TODO - it's not clear whether BlurRadius and Softness are equivalent. We may
124- // need to scale Softness to convert it to BlurRadius.
125- var blurRadius = Optimizer . TrimAnimatable ( context , dropShadowEffect . Softness ) ;
126- if ( blurRadius . IsAnimated )
127- {
128- Animate . Scalar ( context , blurRadius , shadow , nameof ( shadow . BlurRadius ) ) ;
129- }
130- else
131- {
132- shadow . BlurRadius = ( float ) blurRadius . InitialValue ;
133- }
144+ var colorBrush = factory . CreateColorBrush ( dropShadowEffect . Color . InitialValue ) ;
134145
135146 var color = Optimizer . TrimAnimatable ( context , dropShadowEffect . Color ) ;
136147 if ( color . IsAnimated )
137148 {
138- Animate . Color ( context , color , shadow , nameof ( shadow . Color ) ) ;
149+ Animate . Color ( context , color , colorBrush , nameof ( colorBrush . Color ) ) ;
139150 }
140151 else
141152 {
142- shadow . Color = ConvertTo . Color ( color . InitialValue ) ;
153+ colorBrush . Color = ConvertTo . Color ( color . InitialValue ) ;
143154 }
144155
156+ maskBrush . Source = colorBrush ;
157+ maskBrush . Mask = factory . CreateSurfaceBrush ( visualSurface ) ;
158+
159+ var shadowSpriteVisual = factory . CreateSpriteVisual ( ) ;
160+ shadowSpriteVisual . Size = size ;
161+ shadowSpriteVisual . Brush = maskBrush ;
162+
163+ var blurResult = ApplyGaussianBlur ( context , shadowSpriteVisual , gaussianBlurEffect ) ;
164+
145165 var opacity = Optimizer . TrimAnimatable ( context , dropShadowEffect . Opacity ) ;
146166 if ( opacity . IsAnimated )
147167 {
148- Animate . Opacity ( context , opacity , shadow , nameof ( shadow . Opacity ) ) ;
168+ Animate . Opacity ( context , opacity , blurResult , nameof ( blurResult . Opacity ) ) ;
149169 }
150170 else
151171 {
152- shadow . Opacity = ( float ) opacity . InitialValue . Value ;
172+ blurResult . Opacity = ( float ) opacity . InitialValue . Value ;
153173 }
154174
155175 // Convert direction and distance to a Vector3.
@@ -173,7 +193,7 @@ static LayerVisual ApplyDropShadow(PreCompLayerContext context, Visual visual, D
173193 var keyFrames = direction . KeyFrames . Select (
174194 kf => new KeyFrame < Vector3 > ( kf . Frame , VectorFromRotationAndDistance ( kf . Value , distanceValue ) , kf . Easing ) ) . ToArray ( ) ;
175195 var directionAnimation = new TrimmedAnimatable < Vector3 > ( context , keyFrames [ 0 ] . Value , keyFrames ) ;
176- Animate . Vector3 ( context , directionAnimation , shadow , nameof ( shadow . Offset ) ) ;
196+ Animate . Vector3 ( context , directionAnimation , blurResult , nameof ( blurResult . Offset ) ) ;
177197 }
178198 }
179199 else if ( distance . IsAnimated )
@@ -183,15 +203,41 @@ static LayerVisual ApplyDropShadow(PreCompLayerContext context, Visual visual, D
183203 var keyFrames = distance . KeyFrames . Select (
184204 kf => new KeyFrame < Vector3 > ( kf . Frame , VectorFromRotationAndDistance ( directionRadians , kf . Value ) , kf . Easing ) ) . ToArray ( ) ;
185205 var distanceAnimation = new TrimmedAnimatable < Vector3 > ( context , keyFrames [ 0 ] . Value , keyFrames ) ;
186- Animate . Vector3 ( context , distanceAnimation , shadow , nameof ( shadow . Offset ) ) ;
206+ Animate . Vector3 ( context , distanceAnimation , blurResult , nameof ( blurResult . Offset ) ) ;
187207 }
188208 else
189209 {
190210 // Direction and distance are both not animated.
191211 var directionRadians = direction . InitialValue . Radians ;
192212 var distanceValue = distance . InitialValue ;
193213
194- shadow . Offset = ConvertTo . Vector3 ( VectorFromRotationAndDistance ( direction . InitialValue , distance . InitialValue ) ) ;
214+ blurResult . Offset = ConvertTo . Vector3 ( VectorFromRotationAndDistance ( direction . InitialValue , distance . InitialValue ) ) ;
215+ }
216+
217+ var result = factory . CreateContainerVisual ( ) ;
218+ result . Size = size ;
219+ result . Children . Add ( blurResult ) ;
220+
221+ // Check if ShadowOnly can be true
222+ if ( ! dropShadowEffect . IsShadowOnly . IsAlways ( false ) )
223+ {
224+ // Check if ShadowOnly can be false
225+ if ( ! dropShadowEffect . IsShadowOnly . IsAlways ( true ) )
226+ {
227+ var isVisible = FlipBoolAnimatable ( dropShadowEffect . IsShadowOnly ) ; // isVisible = !isShadowOnly
228+
229+ source . IsVisible = isVisible . InitialValue ;
230+ if ( isVisible . IsAnimated )
231+ {
232+ Animate . Boolean (
233+ context ,
234+ Optimizer . TrimAnimatable ( context , isVisible ) ,
235+ source ,
236+ nameof ( blurResult . IsVisible ) ) ;
237+ }
238+ }
239+
240+ result . Children . Add ( source ) ;
195241 }
196242
197243 return result ;
@@ -200,12 +246,37 @@ static LayerVisual ApplyDropShadow(PreCompLayerContext context, Visual visual, D
200246 static Vector3 VectorFromRotationAndDistance ( Rotation direction , double distance ) =>
201247 VectorFromRotationAndDistance ( direction . Radians , distance ) ;
202248
249+ /// <summary>
250+ /// Construct a 2D vector with a given rotation and length.
251+ /// Note: In After Effects 0 degrees angle corresponds to UP direction
252+ /// and 90 degrees angle corresponds to RIGHT direction.
253+ /// </summary>
254+ /// <param name="directionRadians">Rotation in radians.</param>
255+ /// <param name="distance">Vector length.</param>
256+ /// <returns>Vector with given parameters.</returns>
203257 static Vector3 VectorFromRotationAndDistance ( double directionRadians , double distance ) =>
204258 new Vector3 (
205259 x : Math . Sin ( directionRadians ) * distance ,
206- y : Math . Cos ( directionRadians ) * distance ,
260+ y : - Math . Cos ( directionRadians ) * distance ,
207261 z : 1 ) ;
208262
263+ static Animatable < bool > FlipBoolAnimatable ( Animatable < bool > animatable )
264+ {
265+ if ( ! animatable . IsAnimated )
266+ {
267+ return new Animatable < bool > ( ! animatable . InitialValue ) ;
268+ }
269+
270+ var keyFrames = new List < KeyFrame < bool > > ( ) ;
271+
272+ foreach ( var keyFrame in animatable . KeyFrames )
273+ {
274+ keyFrames . Add ( new KeyFrame < bool > ( keyFrame . Frame , ! keyFrame . Value , keyFrame . Easing ) ) ;
275+ }
276+
277+ return new Animatable < bool > ( ! animatable . InitialValue , keyFrames ) ;
278+ }
279+
209280 /// <summary>
210281 /// Applies a Gaussian blur effect to the given <paramref name="source"/> and
211282 /// returns a new root. This is only designed to work on a <see cref="PreCompLayer"/>
0 commit comments