@@ -58,4 +58,109 @@ impl Shadow {
5858 self . inset = inset;
5959 self
6060 }
61+
62+ /// Returns the [`Shadow`] with a fully transparent color.
63+ ///
64+ /// The geometry is kept, so this is the "absent" end of a fade — a shadow
65+ /// appearing or disappearing rather than one moving.
66+ pub const fn transparent ( self ) -> Self {
67+ Self {
68+ color : Color {
69+ a : 0.0 ,
70+ ..self . color
71+ } ,
72+ ..self
73+ }
74+ }
75+
76+ /// Linearly interpolates between two [`Shadow`]s by the given amount.
77+ ///
78+ /// `inset` has no representable in-between — an inset shadow and an outset one
79+ /// are drawn by different branches entirely — so it steps to `other`'s.
80+ pub const fn lerp ( self , other : Self , amount : f32 ) -> Self {
81+ Self {
82+ color : self . color . lerp ( other. color , amount) ,
83+ offset : self . offset . lerp ( other. offset , amount) ,
84+ blur_radius : self . blur_radius
85+ + ( other. blur_radius - self . blur_radius ) * amount,
86+ spread_radius : self . spread_radius
87+ + ( other. spread_radius - self . spread_radius ) * amount,
88+ inset : other. inset ,
89+ }
90+ }
91+
92+ /// Linearly interpolates between two stacks of [`Shadow`]s by the given
93+ /// amount, pairing them up layer by layer.
94+ ///
95+ /// Elevation scales stack several layers, and the two ends of a transition
96+ /// need not use the same number of them. A layer present at only one end keeps
97+ /// that end's geometry and fades its alpha from (or to) zero, so a stack can
98+ /// grow and shrink across a transition.
99+ ///
100+ /// The result always holds one layer per index rather than both stacks
101+ /// concatenated: overlapping translucent layers composite, so drawing both
102+ /// would darken the shadow mid-transition even when the endpoints match.
103+ pub fn lerp_stacks ( from : & [ Self ] , to : & [ Self ] , amount : f32 ) -> Vec < Self > {
104+ ( 0 ..from. len ( ) . max ( to. len ( ) ) )
105+ . filter_map ( |i| match ( from. get ( i) , to. get ( i) ) {
106+ ( Some ( from) , Some ( to) ) => Some ( from. lerp ( * to, amount) ) ,
107+ ( Some ( from) , None ) => {
108+ Some ( from. lerp ( from. transparent ( ) , amount) )
109+ }
110+ ( None , Some ( to) ) => Some ( to. transparent ( ) . lerp ( * to, amount) ) ,
111+ // Unreachable: `i` is below the longer stack's length.
112+ ( None , None ) => None ,
113+ } )
114+ . collect ( )
115+ }
116+ }
117+
118+ #[ cfg( test) ]
119+ mod tests {
120+ use super :: * ;
121+
122+ fn shadow ( alpha : f32 , blur : f32 ) -> Shadow {
123+ Shadow {
124+ color : Color :: from_rgba ( 0.0 , 0.0 , 0.0 , alpha) ,
125+ offset : Vector :: new ( 0.0 , 2.0 ) ,
126+ blur_radius : blur,
127+ spread_radius : -1.0 ,
128+ inset : false ,
129+ }
130+ }
131+
132+ #[ test]
133+ fn lerp_stacks_pairs_layers_instead_of_concatenating ( ) {
134+ // Two overlapping translucent layers composite to more than either one,
135+ // so a stack transition that drew both ends would visibly darken halfway
136+ // through even when the endpoints are identical.
137+ let from = vec ! [ shadow( 0.1 , 6.0 ) , shadow( 0.1 , 15.0 ) ] ;
138+ let to = vec ! [ shadow( 0.2 , 6.0 ) , shadow( 0.2 , 15.0 ) ] ;
139+
140+ assert_eq ! ( Shadow :: lerp_stacks( & from, & to, 0.5 ) . len( ) , 2 ) ;
141+ assert_eq ! ( Shadow :: lerp_stacks( & from, & to, 0.0 ) , from) ;
142+ assert_eq ! ( Shadow :: lerp_stacks( & from, & to, 1.0 ) , to) ;
143+ }
144+
145+ #[ test]
146+ fn lerp_stacks_fades_unpaired_layers_from_transparent ( ) {
147+ // A stack that grows mid-transition (an extra ring layer appearing, say)
148+ // must fade the new layer in by alpha while it already holds its final
149+ // geometry — interpolating its geometry from a neighbour would slide it
150+ // into place from the wrong shape.
151+ let from = vec ! [ shadow( 0.1 , 6.0 ) ] ;
152+ let to = vec ! [ shadow( 0.1 , 6.0 ) , shadow( 0.4 , 20.0 ) ] ;
153+
154+ let mid = Shadow :: lerp_stacks ( & from, & to, 0.5 ) ;
155+ assert_eq ! ( mid. len( ) , 2 ) ;
156+ assert ! ( ( mid[ 1 ] . color. a - 0.2 ) . abs( ) < 1e-6 , "alpha fades in" ) ;
157+ assert ! (
158+ ( mid[ 1 ] . blur_radius - 20.0 ) . abs( ) < 1e-6 ,
159+ "geometry is already final"
160+ ) ;
161+
162+ // Endpoints stay exact in both directions.
163+ assert_eq ! ( Shadow :: lerp_stacks( & from, & to, 1.0 ) , to) ;
164+ assert_eq ! ( Shadow :: lerp_stacks( & to, & from, 1.0 ) [ 1 ] , to[ 1 ] . transparent( ) ) ;
165+ }
61166}
0 commit comments