Skip to content

Commit 77f1681

Browse files
authored
Merge pull request #34322 from minetoblend/feature/heart-icon-animation
Animate heart icon when favouriting beatmaps
2 parents eeb7aba + 28765f6 commit 77f1681

File tree

1 file changed

+125
-20
lines changed

1 file changed

+125
-20
lines changed

osu.Game/Screens/SelectV2/BeatmapTitleWedge_FavouriteButton.cs

Lines changed: 125 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
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;
45
using System.Diagnostics;
56
using osu.Framework.Allocation;
67
using 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

Comments
 (0)