diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs new file mode 100644 index 0000000000..4fef93e291 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs @@ -0,0 +1,183 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics.Containers; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneUpdateableBeatmapSetCover : OsuTestScene + { + [Test] + public void TestLocal([Values] BeatmapSetCoverType coverType) + { + AddStep("setup cover", () => Child = new UpdateableBeatmapSetCover(coverType) + { + BeatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet, + RelativeSizeAxes = Axes.Both, + Masking = true, + }); + + AddUntilStep("wait for load", () => this.ChildrenOfType().SingleOrDefault()?.IsLoaded ?? false); + } + + [Test] + public void TestUnloadAndReload() + { + OsuScrollContainer scroll = null; + List covers = new List(); + + AddStep("setup covers", () => + { + BeatmapSetInfo setInfo = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet; + + FillFlowContainer fillFlow; + + Child = scroll = new OsuScrollContainer + { + Size = new Vector2(500f), + Child = fillFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10), + Padding = new MarginPadding { Bottom = 550 } + } + }; + + var coverTypes = Enum.GetValues(typeof(BeatmapSetCoverType)) + .Cast() + .ToList(); + + for (int i = 0; i < 25; i++) + { + var coverType = coverTypes[i % coverTypes.Count]; + + var cover = new UpdateableBeatmapSetCover(coverType) + { + BeatmapSet = setInfo, + Height = 100, + Masking = true, + }; + + if (coverType == BeatmapSetCoverType.Cover) + cover.Width = 500; + else if (coverType == BeatmapSetCoverType.Card) + cover.Width = 400; + else if (coverType == BeatmapSetCoverType.List) + cover.Size = new Vector2(100, 50); + + fillFlow.Add(cover); + covers.Add(cover); + } + }); + + var loadedCovers = covers.Where(c => c.ChildrenOfType().SingleOrDefault()?.IsLoaded ?? false); + + AddUntilStep("some loaded", () => loadedCovers.Any()); + AddStep("scroll to end", () => scroll.ScrollToEnd()); + AddUntilStep("all unloaded", () => !loadedCovers.Any()); + } + + [Test] + public void TestSetNullBeatmapWhileLoading() + { + TestUpdateableBeatmapSetCover updateableCover = null; + + AddStep("setup cover", () => Child = updateableCover = new TestUpdateableBeatmapSetCover + { + BeatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet, + RelativeSizeAxes = Axes.Both, + Masking = true, + }); + + AddStep("change model", () => updateableCover.BeatmapSet = null); + AddWaitStep("wait some", 5); + AddAssert("no cover added", () => !updateableCover.ChildrenOfType().Any()); + } + + [Test] + public void TestCoverChangeOnNewBeatmap() + { + TestUpdateableBeatmapSetCover updateableCover = null; + BeatmapSetCover initialCover = null; + + AddStep("setup cover", () => Child = updateableCover = new TestUpdateableBeatmapSetCover(0) + { + BeatmapSet = createBeatmapWithCover("https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg"), + RelativeSizeAxes = Axes.Both, + Masking = true, + Alpha = 0.4f + }); + + AddUntilStep("cover loaded", () => updateableCover.ChildrenOfType().Any()); + AddStep("store initial cover", () => initialCover = updateableCover.ChildrenOfType().Single()); + AddUntilStep("wait for fade complete", () => initialCover.Alpha == 1); + + AddStep("switch beatmap", + () => updateableCover.BeatmapSet = createBeatmapWithCover("https://assets.ppy.sh/beatmaps/1079428/covers/cover.jpg")); + AddUntilStep("new cover loaded", () => updateableCover.ChildrenOfType().Except(new[] { initialCover }).Any()); + } + + private static BeatmapSetInfo createBeatmapWithCover(string coverUrl) => new BeatmapSetInfo + { + OnlineInfo = new BeatmapSetOnlineInfo + { + Covers = new BeatmapSetOnlineCovers { Cover = coverUrl } + } + }; + + private class TestUpdateableBeatmapSetCover : UpdateableBeatmapSetCover + { + private readonly int loadDelay; + + public TestUpdateableBeatmapSetCover(int loadDelay = 10000) + { + this.loadDelay = loadDelay; + } + + protected override Drawable CreateDrawable(BeatmapSetInfo model) + { + if (model == null) + return null; + + return new TestBeatmapSetCover(model, loadDelay) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fill, + }; + } + } + + private class TestBeatmapSetCover : BeatmapSetCover + { + private readonly int loadDelay; + + public TestBeatmapSetCover(BeatmapSetInfo set, int loadDelay) + : base(set) + { + this.loadDelay = loadDelay; + } + + [BackgroundDependencyLoader] + private void load() + { + Thread.Sleep(loadDelay); + } + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs index 6c229755e7..7248c9213c 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -8,78 +9,52 @@ using osu.Game.Graphics; namespace osu.Game.Beatmaps.Drawables { - public class UpdateableBeatmapSetCover : Container + public class UpdateableBeatmapSetCover : ModelBackedDrawable { - private Drawable displayedCover; - - private BeatmapSetInfo beatmapSet; + private readonly BeatmapSetCoverType coverType; public BeatmapSetInfo BeatmapSet { - get => beatmapSet; - set - { - if (value == beatmapSet) return; - - beatmapSet = value; - - if (IsLoaded) - updateCover(); - } + get => Model; + set => Model = value; } - private BeatmapSetCoverType coverType = BeatmapSetCoverType.Cover; - - public BeatmapSetCoverType CoverType + public new bool Masking { - get => coverType; - set - { - if (value == coverType) return; - - coverType = value; - - if (IsLoaded) - updateCover(); - } + get => base.Masking; + set => base.Masking = value; } - public UpdateableBeatmapSetCover() + public UpdateableBeatmapSetCover(BeatmapSetCoverType coverType = BeatmapSetCoverType.Cover) { - Child = new Box + this.coverType = coverType; + + InternalChild = new Box { RelativeSizeAxes = Axes.Both, Colour = OsuColour.Gray(0.2f), }; } - protected override void LoadComplete() - { - base.LoadComplete(); - updateCover(); - } + protected override double LoadDelay => 500; - private void updateCover() - { - displayedCover?.FadeOut(400); - displayedCover?.Expire(); - displayedCover = null; + protected override double TransformDuration => 400; - if (beatmapSet != null) + protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func createContentFunc, double timeBeforeLoad) + => new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad); + + protected override Drawable CreateDrawable(BeatmapSetInfo model) + { + if (model == null) + return null; + + return new BeatmapSetCover(model, coverType) { - Add(displayedCover = new DelayedLoadUnloadWrapper(() => - { - var cover = new BeatmapSetCover(beatmapSet, coverType) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fill, - }; - cover.OnLoadComplete += d => d.FadeInFromZero(400, Easing.Out); - return cover; - })); - } + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fill, + }; } } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index 1576431d40..97ccb66599 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -90,7 +90,7 @@ namespace osu.Game.Overlays.BeatmapListing { RelativeSizeAxes = Axes.Both, Masking = true, - Child = beatmapCover = new UpdateableBeatmapSetCover + Child = beatmapCover = new TopSearchBeatmapSetCover { RelativeSizeAxes = Axes.Both, Alpha = 0, @@ -184,5 +184,10 @@ namespace osu.Game.Overlays.BeatmapListing return true; } } + + private class TopSearchBeatmapSetCover : UpdateableBeatmapSetCover + { + protected override bool TransformImmediately => true; + } } } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs index 20e40569e8..6d6ff32aac 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs @@ -41,12 +41,11 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { AddRangeInternal(new Drawable[] { - new UpdateableBeatmapSetCover + new UpdateableBeatmapSetCover(BeatmapSetCoverType.List) { RelativeSizeAxes = Axes.Y, Width = cover_width, BeatmapSet = beatmap.BeatmapSet, - CoverType = BeatmapSetCoverType.List, }, new Container {