diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs index 0feaa8f480..addec15881 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs @@ -27,6 +27,9 @@ namespace osu.Game.Tests.Visual.Beatmaps private APIBeatmapSet[] testCases; + [Resolved] + private BeatmapManager beatmaps { get; set; } + #region Test case generation [BackgroundDependencyLoader] @@ -35,6 +38,7 @@ namespace osu.Game.Tests.Visual.Beatmaps var normal = CreateAPIBeatmapSet(Ruleset.Value); normal.HasVideo = true; normal.HasStoryboard = true; + normal.OnlineID = 241526; var withStatistics = CreateAPIBeatmapSet(Ruleset.Value); withStatistics.Title = withStatistics.TitleUnicode = "play favourite stats"; @@ -180,6 +184,19 @@ namespace osu.Game.Tests.Visual.Beatmaps request.TriggerSuccess(); return true; }); + + ensureSoleilyRemoved(); + } + + private void ensureSoleilyRemoved() + { + AddUntilStep("ensure manager loaded", () => beatmaps != null); + AddStep("remove soleily", () => + { + var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineID == 241526); + + if (beatmap != null) beatmaps.Delete(beatmap); + }); } private Drawable createContent(OverlayColourScheme colourScheme, Func creationFunc) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs new file mode 100644 index 0000000000..068d2bdcbd --- /dev/null +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs @@ -0,0 +1,92 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables.Cards.Buttons; +using osu.Game.Configuration; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Resources.Localisation.Web; +using osu.Game.Rulesets.Osu; +using osuTK; + +namespace osu.Game.Tests.Visual.Beatmaps +{ + public class TestSceneBeatmapCardDownloadButton : OsuTestScene + { + private DownloadButton downloadButton; + + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + + [Resolved] + private OsuConfigManager config { get; set; } + + [Test] + public void TestDownloadableBeatmapWithVideo() + { + createButton(true, true); + assertDownloadEnabled(true); + + AddStep("prefer no video", () => config.SetValue(OsuSetting.PreferNoVideo, true)); + AddAssert("tooltip text correct", () => downloadButton.TooltipText == BeatmapsetsStrings.PanelDownloadNoVideo); + + AddStep("prefer video", () => config.SetValue(OsuSetting.PreferNoVideo, false)); + AddAssert("tooltip text correct", () => downloadButton.TooltipText == BeatmapsetsStrings.PanelDownloadVideo); + } + + [Test] + public void TestUndownloadableBeatmap() + { + createButton(false); + assertDownloadEnabled(false); + AddAssert("tooltip text correct", () => downloadButton.TooltipText == BeatmapsetsStrings.AvailabilityDisabled); + } + + private void assertDownloadEnabled(bool enabled) => AddAssert($"download {(enabled ? "enabled" : "disabled")}", () => downloadButton.Enabled.Value == enabled); + + private void createButton(bool downloadable, bool hasVideo = false) + { + AddStep("create button", () => + { + Child = downloadButton = new DownloadButton(downloadable ? getDownloadableBeatmapSet(hasVideo) : getUndownloadableBeatmapSet()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(2) + }; + }); + } + + private APIBeatmapSet getDownloadableBeatmapSet(bool hasVideo) + { + var normal = CreateAPIBeatmapSet(new OsuRuleset().RulesetInfo); + normal.HasVideo = hasVideo; + normal.HasStoryboard = true; + + return normal; + } + + private APIBeatmapSet getUndownloadableBeatmapSet() + { + var beatmap = CreateAPIBeatmapSet(new OsuRuleset().RulesetInfo); + beatmap.Artist = "test"; + beatmap.Title = "undownloadable"; + beatmap.AuthorString = "test"; + + beatmap.HasVideo = true; + beatmap.HasStoryboard = true; + + beatmap.Availability = new BeatmapSetOnlineAvailability + { + DownloadDisabled = true, + ExternalLink = "https://osu.ppy.sh", + }; + + return beatmap; + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 71376c28f1..e3af253db9 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -16,6 +16,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet; @@ -34,14 +35,18 @@ namespace osu.Game.Beatmaps.Drawables.Cards private const float width = 408; private const float height = 100; private const float corner_radius = 10; + private const float icon_area_width = 30; private readonly APIBeatmapSet beatmapSet; private readonly Bindable favouriteState; + private readonly BeatmapDownloadTracker downloadTracker; + private UpdateableOnlineBeatmapSetCover leftCover; private FillFlowContainer leftIconArea; - private Container rightButtonArea; + private Container rightAreaBackground; + private Container rightAreaButtons; private Container mainContent; private BeatmapCardContentBackground mainContentBackground; @@ -50,6 +55,12 @@ namespace osu.Game.Beatmaps.Drawables.Cards private GridContainer artistContainer; private FillFlowContainer statisticsContainer; + private FillFlowContainer idleBottomContent; + private BeatmapCardDownloadProgressBar downloadProgressBar; + + [Resolved] + private OsuColour colours { get; set; } + [Resolved] private OverlayColourProvider colourProvider { get; set; } @@ -58,6 +69,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards { this.beatmapSet = beatmapSet; favouriteState = new Bindable(new BeatmapSetFavouriteState(beatmapSet.HasFavourited, beatmapSet.FavouriteCount)); + downloadTracker = new BeatmapDownloadTracker(beatmapSet); } [BackgroundDependencyLoader] @@ -70,10 +82,21 @@ namespace osu.Game.Beatmaps.Drawables.Cards InternalChildren = new Drawable[] { - new Box + downloadTracker, + rightAreaBackground = new Container { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background3 + RelativeSizeAxes = Axes.Y, + Width = icon_area_width + 2 * corner_radius, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + // workaround for masking artifacts at the top & bottom of card, + // which become especially visible on downloaded beatmaps (when the icon area has a lime background). + Padding = new MarginPadding { Vertical = 1 }, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.White + }, }, new Container { @@ -95,24 +118,37 @@ namespace osu.Game.Beatmaps.Drawables.Cards } } }, - rightButtonArea = new Container + new Container { Name = @"Right (button) area", Width = 30, RelativeSizeAxes = Axes.Y, Origin = Anchor.TopRight, Anchor = Anchor.TopRight, - Child = new FillFlowContainer + Padding = new MarginPadding { Vertical = 17.5f }, + Child = rightAreaButtons = new Container { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 14), + RelativeSizeAxes = Axes.Both, Children = new BeatmapCardIconButton[] { - new FavouriteButton(beatmapSet) { Current = favouriteState }, + new FavouriteButton(beatmapSet) + { + Current = favouriteState, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + }, new DownloadButton(beatmapSet) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + State = { BindTarget = downloadTracker.State } + }, + new GoToBeatmapButton(beatmapSet) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + State = { BindTarget = downloadTracker.State } + } } } }, @@ -207,45 +243,74 @@ namespace osu.Game.Beatmaps.Drawables.Cards d.AddText("mapped by ", t => t.Colour = colourProvider.Content2); d.AddUserLink(beatmapSet.Author); }), - statisticsContainer = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), - Alpha = 0, - ChildrenEnumerable = createStatistics() - } } }, - new FillFlowContainer + new Container { Name = @"Bottom content", RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, Padding = new MarginPadding { Horizontal = 10, Vertical = 4 }, - Spacing = new Vector2(4, 0), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, Children = new Drawable[] { - new BeatmapSetOnlineStatusPill + idleBottomContent = new FillFlowContainer { - AutoSizeAxes = Axes.Both, - Status = beatmapSet.Status, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 3), + AlwaysPresent = true, + Children = new Drawable[] + { + statisticsContainer = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Alpha = 0, + AlwaysPresent = true, + ChildrenEnumerable = createStatistics() + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(4, 0), + Children = new Drawable[] + { + new BeatmapSetOnlineStatusPill + { + AutoSizeAxes = Axes.Both, + Status = beatmapSet.Status, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft + }, + new DifficultySpectrumDisplay(beatmapSet) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + DotSize = new Vector2(6, 12) + } + } + } + } }, - new DifficultySpectrumDisplay(beatmapSet) + downloadProgressBar = new BeatmapCardDownloadProgressBar { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - DotSize = new Vector2(6, 12) + RelativeSizeAxes = Axes.X, + Height = 6, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + State = { BindTarget = downloadTracker.State }, + Progress = { BindTarget = downloadTracker.Progress } } } } @@ -283,7 +348,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards protected override void LoadComplete() { base.LoadComplete(); - updateState(); + + downloadTracker.State.BindValueChanged(_ => updateState(), true); FinishTransforms(true); } @@ -327,14 +393,27 @@ namespace osu.Game.Beatmaps.Drawables.Cards { float targetWidth = width - height; if (IsHovered) - targetWidth -= 20; + targetWidth = targetWidth - icon_area_width + corner_radius; mainContent.ResizeWidthTo(targetWidth, TRANSITION_DURATION, Easing.OutQuint); mainContentBackground.Dimmed.Value = IsHovered; leftCover.FadeColour(IsHovered ? OsuColour.Gray(0.2f) : Color4.White, TRANSITION_DURATION, Easing.OutQuint); statisticsContainer.FadeTo(IsHovered ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); - rightButtonArea.FadeTo(IsHovered ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); + + rightAreaBackground.FadeColour(downloadTracker.State.Value == DownloadState.LocallyAvailable ? colours.Lime0 : colourProvider.Background3, TRANSITION_DURATION, Easing.OutQuint); + rightAreaButtons.FadeTo(IsHovered ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); + + foreach (var button in rightAreaButtons) + { + button.IdleColour = downloadTracker.State.Value != DownloadState.LocallyAvailable ? colourProvider.Light1 : colourProvider.Background3; + button.HoverColour = downloadTracker.State.Value != DownloadState.LocallyAvailable ? colourProvider.Content1 : colourProvider.Foreground1; + } + + bool showProgress = downloadTracker.State.Value == DownloadState.Downloading || downloadTracker.State.Value == DownloadState.Importing; + + idleBottomContent.FadeTo(showProgress ? 0 : 1, TRANSITION_DURATION, Easing.OutQuint); + downloadProgressBar.FadeTo(showProgress ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs new file mode 100644 index 0000000000..ffb4e0c540 --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs @@ -0,0 +1,95 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Online; +using osu.Game.Overlays; + +namespace osu.Game.Beatmaps.Drawables.Cards +{ + public class BeatmapCardDownloadProgressBar : CompositeDrawable + { + public IBindable State => state; + private readonly Bindable state = new Bindable(); + + public IBindable Progress => progress; + private readonly BindableDouble progress = new BindableDouble(); + + public override bool IsPresent => true; + + private readonly CircularContainer foreground; + + private readonly Box backgroundFill; + private readonly Box foregroundFill; + + [Resolved] + private OsuColour colours { get; set; } + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + public BeatmapCardDownloadProgressBar() + { + InternalChildren = new Drawable[] + { + new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = backgroundFill = new Box + { + RelativeSizeAxes = Axes.Both, + } + }, + foreground = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = foregroundFill = new Box + { + RelativeSizeAxes = Axes.Both, + } + } + }; + } + + [BackgroundDependencyLoader] + private void load() + { + backgroundFill.Colour = colourProvider.Background6; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + state.BindValueChanged(_ => stateChanged(), true); + progress.BindValueChanged(_ => progressChanged(), true); + } + + private void stateChanged() + { + switch (state.Value) + { + case DownloadState.Downloading: + FinishTransforms(true); + foregroundFill.Colour = colourProvider.Highlight1; + break; + + case DownloadState.Importing: + foregroundFill.FadeColour(colours.Yellow, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + break; + } + } + + private void progressChanged() + { + foreground.ResizeWidthTo((float)progress.Value, progress.Value > 0 ? BeatmapCard.TRANSITION_DURATION : 0, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs index 155259d859..ad9caf7e34 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs @@ -3,44 +3,117 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using osu.Game.Overlays; using osuTK; namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { - public abstract class BeatmapCardIconButton : OsuHoverContainer + public abstract class BeatmapCardIconButton : OsuClickableContainer { - protected readonly SpriteIcon Icon; + private Colour4 idleColour; - private float size; - - public new float Size + public Colour4 IdleColour { - get => size; + get => idleColour; set { - size = value; - Icon.Size = new Vector2(size); + idleColour = value; + if (IsLoaded) + updateState(); } } + private Colour4 hoverColour; + + public Colour4 HoverColour + { + get => hoverColour; + set + { + hoverColour = value; + if (IsLoaded) + updateState(); + } + } + + private float iconSize; + + public float IconSize + { + get => iconSize; + set + { + iconSize = value; + Icon.Size = new Vector2(iconSize); + } + } + + protected readonly SpriteIcon Icon; + + private readonly Container content; + protected BeatmapCardIconButton() { - Add(Icon = new SpriteIcon()); + Origin = Anchor.Centre; + Anchor = Anchor.Centre; - AutoSizeAxes = Axes.Both; - Size = 12; + Child = content = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Children = new Drawable[] + { + Icon = new SpriteIcon + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre + } + } + }; + + Size = new Vector2(24); + IconSize = 12; } [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { - Anchor = Origin = Anchor.Centre; - IdleColour = colourProvider.Light1; HoverColour = colourProvider.Content1; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Enabled.BindValueChanged(_ => updateState(), true); + FinishTransforms(true); + } + + protected override bool OnHover(HoverEvent e) + { + updateState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + updateState(); + } + + private void updateState() + { + bool isHovered = IsHovered && Enabled.Value; + + content.ScaleTo(isHovered ? 1.2f : 1, 500, Easing.OutQuint); + content.FadeColour(isHovered ? HoverColour : IdleColour, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs index 00c0ccc3ce..7430fce1c8 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs @@ -1,18 +1,69 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Configuration; +using osu.Game.Online; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { public class DownloadButton : BeatmapCardIconButton { + public IBindable State => state; + private readonly Bindable state = new Bindable(); + + private readonly APIBeatmapSet beatmapSet; + + private Bindable preferNoVideo = null!; + + [Resolved] + private BeatmapManager beatmaps { get; set; } = null!; + public DownloadButton(APIBeatmapSet beatmapSet) { - Icon.Icon = FontAwesome.Solid.FileDownload; + Icon.Icon = FontAwesome.Solid.Download; + + this.beatmapSet = beatmapSet; } - // TODO: implement behaviour + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + preferNoVideo = config.GetBindable(OsuSetting.PreferNoVideo); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + preferNoVideo.BindValueChanged(_ => updateState()); + state.BindValueChanged(_ => updateState(), true); + FinishTransforms(true); + } + + private void updateState() + { + this.FadeTo(state.Value != DownloadState.LocallyAvailable ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + + if (beatmapSet.Availability.DownloadDisabled) + { + Enabled.Value = false; + TooltipText = BeatmapsetsStrings.AvailabilityDisabled; + return; + } + + if (!beatmapSet.HasVideo) + TooltipText = BeatmapsetsStrings.PanelDownloadAll; + else + TooltipText = preferNoVideo.Value ? BeatmapsetsStrings.PanelDownloadNoVideo : BeatmapsetsStrings.PanelDownloadVideo; + + Action = () => beatmaps.Download(beatmapSet, preferNoVideo.Value); + } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/GoToBeatmapButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/GoToBeatmapButton.cs new file mode 100644 index 0000000000..9a6a3c01b7 --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/GoToBeatmapButton.cs @@ -0,0 +1,49 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Online; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Beatmaps.Drawables.Cards.Buttons +{ + public class GoToBeatmapButton : BeatmapCardIconButton + { + public IBindable State => state; + private readonly Bindable state = new Bindable(); + + private readonly APIBeatmapSet beatmapSet; + + public GoToBeatmapButton(APIBeatmapSet beatmapSet) + { + this.beatmapSet = beatmapSet; + + Icon.Icon = FontAwesome.Solid.AngleDoubleRight; + TooltipText = "Go to beatmap"; + } + + [BackgroundDependencyLoader(true)] + private void load(OsuGame? game) + { + Action = () => game?.PresentBeatmap(beatmapSet); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + state.BindValueChanged(_ => updateState(), true); + FinishTransforms(true); + } + + private void updateState() + { + this.FadeTo(state.Value == DownloadState.LocallyAvailable ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + } + } +}