11// Copyright (c) ppy Pty Ltd <[email protected] >. Licensed under the MIT Licence. 22// See the LICENCE file in the repository root for full licence text.
33
4+ using System ;
45using System . Diagnostics ;
56using osu . Framework . Allocation ;
67using osu . Framework . Bindables ;
@@ -35,8 +36,7 @@ public partial class FavouriteButton : OsuClickableContainer
3536 private OsuSpriteText valueText = null ! ;
3637 private LoadingSpinner loadingSpinner = null ! ;
3738 private Box hoverLayer = null ! ;
38- private Box flashLayer = null ! ;
39- private SpriteIcon icon = null ! ;
39+ private HeartIcon icon = null ! ;
4040
4141 private APIBeatmapSet ? onlineBeatmapSet ;
4242 private PostBeatmapFavouriteRequest ? favouriteRequest ;
@@ -82,13 +82,11 @@ private void load()
8282 Shear = - OsuGame . SHEAR ,
8383 Children = new Drawable [ ]
8484 {
85- icon = new SpriteIcon
85+ icon = new HeartIcon
8686 {
8787 Anchor = Anchor . CentreLeft ,
8888 Origin = Anchor . CentreLeft ,
89- Icon = OsuIcon . Heart ,
9089 Size = new Vector2 ( OsuFont . Style . Heading2 . Size ) ,
91- Colour = colourProvider . Content2 ,
9290 } ,
9391 new Container
9492 {
@@ -142,12 +140,6 @@ private void load()
142140 Colour = Colour4 . White . Opacity ( 0.1f ) ,
143141 Blending = BlendingParameters . Additive ,
144142 } ,
145- flashLayer = new Box
146- {
147- RelativeSizeAxes = Axes . Both ,
148- Alpha = 0 ,
149- Colour = Colour4 . White ,
150- }
151143 } ) ;
152144 Action = toggleFavourite ;
153145 }
@@ -192,16 +184,16 @@ public void SetBeatmapSet(APIBeatmapSet? beatmapSet)
192184 setBeatmapSet ( beatmapSet ) ;
193185 }
194186
195- private void setBeatmapSet ( APIBeatmapSet ? beatmapSet )
187+ private void setBeatmapSet ( APIBeatmapSet ? beatmapSet , bool withHeartAnimation = false )
196188 {
197189 loadingSpinner . State . Value = Visibility . Hidden ;
198190 valueText . FadeIn ( 120 , Easing . OutQuint ) ;
199191
200192 onlineBeatmapSet = beatmapSet ;
201- updateFavouriteState ( ) ;
193+ updateFavouriteState ( withHeartAnimation ) ;
202194 }
203195
204- private void updateFavouriteState ( )
196+ private void updateFavouriteState ( bool withAnimation = false )
205197 {
206198 Enabled . Value = onlineBeatmapSet != null ;
207199
@@ -211,10 +203,8 @@ private void updateFavouriteState()
211203 isFavourite . Value = onlineBeatmapSet ? . HasFavourited == true ;
212204
213205 background . FadeColour ( isFavourite . Value ? colours . Pink4 . Darken ( 1f ) . Opacity ( 0.5f ) : Color4 . Black . Opacity ( 0.2f ) , 500 , Easing . OutQuint ) ;
214- icon . FadeColour ( isFavourite . Value ? colours . Pink1 : colourProvider . Content2 , 500 , Easing . OutQuint ) ;
215206 valueText . FadeColour ( isFavourite . Value ? colours . Pink1 : colourProvider . Content2 , 500 , Easing . OutQuint ) ;
216-
217- icon . Icon = isFavourite . Value ? FontAwesome . Solid . Heart : FontAwesome . Regular . Heart ;
207+ icon . SetActive ( isFavourite . Value , withAnimation ) ;
218208 }
219209
220210 private void toggleFavourite ( )
@@ -232,13 +222,128 @@ private void toggleFavourite()
232222 bool hasFavourited = favouriteRequest . Action == BeatmapFavouriteAction . Favourite ;
233223 beatmapSet . HasFavourited = hasFavourited ;
234224 beatmapSet . FavouriteCount += hasFavourited ? 1 : - 1 ;
235- setBeatmapSet ( beatmapSet ) ;
236- if ( hasFavourited )
237- flashLayer . FadeOutFromOne ( 500 , Easing . OutQuint ) ;
225+ setBeatmapSet ( beatmapSet , withHeartAnimation : hasFavourited ) ;
238226 } ;
239227 api . Queue ( favouriteRequest ) ;
240228 setLoading ( ) ;
241229 }
242230 }
231+
232+ private partial class HeartIcon : CompositeDrawable
233+ {
234+ private readonly SpriteIcon icon ;
235+
236+ [ Resolved ]
237+ private OverlayColourProvider colourProvider { get ; set ; } = null ! ;
238+
239+ [ Resolved ]
240+ private OsuColour colours { get ; set ; } = null ! ;
241+
242+ public HeartIcon ( )
243+ {
244+ InternalChildren = new Drawable [ ]
245+ {
246+ icon = new SpriteIcon
247+ {
248+ Anchor = Anchor . Centre ,
249+ Origin = Anchor . Centre ,
250+ Icon = FontAwesome . Regular . Heart ,
251+ RelativeSizeAxes = Axes . Both ,
252+ } ,
253+ } ;
254+ }
255+
256+ private const double pop_out_duration = 100 ;
257+ private const double pop_in_duration = 500 ;
258+
259+ private bool active ;
260+
261+ public void SetActive ( bool active , bool withAnimation = false )
262+ {
263+ if ( this . active == active )
264+ return ;
265+
266+ this . active = active ;
267+
268+ FinishTransforms ( true ) ;
269+
270+ if ( active )
271+ {
272+ transitionIcon ( FontAwesome . Solid . Heart , colours . Pink1 , emphasised : withAnimation ) ;
273+
274+ if ( withAnimation )
275+ playFavouriteAnimation ( ) ;
276+ }
277+ else
278+ {
279+ transitionIcon ( FontAwesome . Regular . Heart , colourProvider . Content2 ) ;
280+ }
281+ }
282+
283+ private void transitionIcon ( IconUsage newIcon , Color4 colour , bool emphasised = false )
284+ {
285+ icon . ScaleTo ( emphasised ? 0.5f : 0.8f , pop_out_duration , Easing . OutQuad )
286+ . Then ( )
287+ . FadeColour ( colour )
288+ . Schedule ( ( ) => icon . Icon = newIcon )
289+ . ScaleTo ( 1 , pop_in_duration , Easing . OutElasticHalf ) ;
290+ }
291+
292+ private void playFavouriteAnimation ( )
293+ {
294+ var circle = new FastCircle
295+ {
296+ RelativeSizeAxes = Axes . Both ,
297+ Anchor = Anchor . Centre ,
298+ Origin = Anchor . Centre ,
299+ Scale = new Vector2 ( 0.5f ) ,
300+ Blending = BlendingParameters . Additive ,
301+ Alpha = 0 ,
302+ Depth = float . MinValue ,
303+ } ;
304+
305+ AddInternal ( circle ) ;
306+
307+ circle . Delay ( pop_out_duration )
308+ . FadeTo ( 0.35f )
309+ . FadeOut ( 1400 , Easing . OutCubic )
310+ . ScaleTo ( 10f , 750 , Easing . OutQuint )
311+ . Expire ( ) ;
312+
313+ const int num_particles = 8 ;
314+
315+ static float randomFloat ( float min , float max ) => min + Random . Shared . NextSingle ( ) * ( max - min ) ;
316+
317+ for ( int i = 0 ; i < num_particles ; i ++ )
318+ {
319+ double duration = randomFloat ( 600 , 1000 ) ;
320+ float angle = ( i + randomFloat ( 0 , 0.75f ) ) / num_particles * MathF . PI * 2 ;
321+ var direction = new Vector2 ( MathF . Cos ( angle ) , MathF . Sin ( angle ) ) ;
322+ float distance = randomFloat ( DrawWidth / 2 , DrawWidth ) ;
323+
324+ var particle = new FastCircle
325+ {
326+ Position = direction * DrawWidth / 4 ,
327+ Size = new Vector2 ( 3 ) ,
328+ Anchor = Anchor . Centre ,
329+ Origin = Anchor . Centre ,
330+ Blending = BlendingParameters . Additive ,
331+ Alpha = 0 ,
332+ Depth = 2 ,
333+ Colour = colours . Pink ,
334+ } ;
335+
336+ AddInternal ( particle ) ;
337+
338+ particle
339+ . Delay ( pop_out_duration )
340+ . FadeTo ( 0.5f )
341+ . MoveTo ( direction * distance , 1300 , Easing . OutQuint )
342+ . FadeOut ( duration , Easing . Out )
343+ . ScaleTo ( 0.5f , duration )
344+ . Expire ( ) ;
345+ }
346+ }
347+ }
243348 }
244349}
0 commit comments