From 0d32c94104d9da5b47d5b835d2cf91d6a0d077f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Jun 2022 16:32:15 +0900 Subject: [PATCH] Add initial implementation of beatmap carousel no-results-placeholder --- .../SongSelect/TestScenePlaySongSelect.cs | 34 +++++ osu.Game/Screens/Select/BeatmapCarousel.cs | 15 ++- .../Screens/Select/NoResultsPlaceholder.cs | 122 ++++++++++++++++++ 3 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/Select/NoResultsPlaceholder.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index aad7f6b301..77f5bd83d6 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -18,6 +18,7 @@ using osu.Game.Database; using osu.Game.Extensions; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Chat; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; @@ -80,6 +81,37 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("delete all beatmaps", () => manager?.Delete()); } + [Test] + public void TestPlaceholderBeatmapPresence() + { + createSongSelect(); + + AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Visible); + + addRulesetImportStep(0); + AddUntilStep("wait for placeholder hidden", () => getPlaceholder()?.State.Value == Visibility.Hidden); + + AddStep("delete all beatmaps", () => manager?.Delete()); + AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Visible); + } + + [Test] + public void TestPlaceholderConvertSetting() + { + changeRuleset(2); + addRulesetImportStep(0); + AddStep("change convert setting", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, false)); + + createSongSelect(); + + AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Visible); + + AddStep("click link in placeholder", () => getPlaceholder().ChildrenOfType().First().TriggerClick()); + + AddUntilStep("convert setting changed", () => config.Get(OsuSetting.ShowConvertedBeatmaps)); + AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Hidden); + } + [Test] public void TestSingleFilterOnEnter() { @@ -941,6 +973,8 @@ namespace osu.Game.Tests.Visual.SongSelect private int getBeatmapIndex(BeatmapSetInfo set, BeatmapInfo info) => set.Beatmaps.IndexOf(info); + private NoResultsPlaceholder getPlaceholder() => songSelect.ChildrenOfType().FirstOrDefault(); + private int getCurrentBeatmapIndex() => getBeatmapIndex(songSelect.Carousel.SelectedBeatmapSet, songSelect.Carousel.SelectedBeatmapInfo); private int getDifficultyIconIndex(DrawableCarouselBeatmapSet set, FilterableDifficultyIcon icon) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index a59f14647d..b7d253d7de 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -97,6 +97,8 @@ namespace osu.Game.Screens.Select protected readonly CarouselScrollContainer Scroll; + private readonly NoResultsPlaceholder noResultsPlaceholder; + private IEnumerable beatmapSets => root.Children.OfType(); // todo: only used for testing, maybe remove. @@ -170,7 +172,8 @@ namespace osu.Game.Screens.Select Scroll = new CarouselScrollContainer { RelativeSizeAxes = Axes.Both, - } + }, + noResultsPlaceholder = new NoResultsPlaceholder() } }; } @@ -648,8 +651,18 @@ namespace osu.Game.Screens.Select // First we iterate over all non-filtered carousel items and populate their // vertical position data. if (revalidateItems) + { updateYPositions(); + if (visibleItems.Count == 0) + { + noResultsPlaceholder.Filter = activeCriteria; + noResultsPlaceholder.Show(); + } + else + noResultsPlaceholder.Hide(); + } + // if there is a pending scroll action we apply it without animation and transfer the difference in position to the panels. // this is intentionally applied before updating the visible range below, to avoid animating new items (sourced from pool) from locations off-screen, as it looks bad. if (pendingScrollOperation != PendingScrollOperation.None) diff --git a/osu.Game/Screens/Select/NoResultsPlaceholder.cs b/osu.Game/Screens/Select/NoResultsPlaceholder.cs new file mode 100644 index 0000000000..0188dbd7db --- /dev/null +++ b/osu.Game/Screens/Select/NoResultsPlaceholder.cs @@ -0,0 +1,122 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Online.Chat; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Screens.Select +{ + public class NoResultsPlaceholder : CompositeDrawable + { + private FilterCriteria filter; + + private LinkFlowContainer textFlow; + + [Resolved] + private BeatmapManager beatmaps { get; set; } + + [Resolved(CanBeNull = true)] + private FirstRunSetupOverlay firstRunSetupOverlay { get; set; } + + public FilterCriteria Filter + { + get => filter; + set + { + filter = value; + Scheduler.AddOnce(updateText); + } + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Masking = true; + CornerRadius = 10; + + Width = 300; + AutoSizeAxes = Axes.Y; + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = colours.Gray2, + RelativeSizeAxes = Axes.Both, + }, + new SpriteIcon + { + Icon = FontAwesome.Regular.QuestionCircle, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Margin = new MarginPadding(10), + Size = new Vector2(50), + }, + textFlow = new LinkFlowContainer + { + Y = 70, + Padding = new MarginPadding(10), + TextAnchor = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } + }; + } + + public override void Show() + { + this.FadeIn(600, Easing.OutQuint); + + this.ScaleTo(0.8f) + .ScaleTo(1f, 1000, Easing.OutElastic); + + Scheduler.AddOnce(updateText); + } + + public override void Hide() + { + this.FadeOut(200, Easing.OutQuint); + } + + private void updateText() + { + textFlow.Clear(); + + if (beatmaps.QueryBeatmapSet(s => !s.Protected && !s.DeletePending) == null) + { + textFlow.AddParagraph("No beatmaps found!"); + textFlow.AddParagraph(string.Empty); + + textFlow.AddParagraph("Consider running the "); + textFlow.AddLink("first run setup", () => firstRunSetupOverlay?.Show()); + textFlow.AddText(" to load or import some beatmaps!"); + } + else + { + textFlow.AddParagraph("No beatmaps match your filter criteria!"); + textFlow.AddParagraph(string.Empty); + + // TODO: hint when beatmaps are available in another ruleset + // TODO: hint when beatmaps are available by toggling "show converted". + if (!string.IsNullOrEmpty(filter?.SearchText)) + { + textFlow.AddParagraph("You can try "); + textFlow.AddLink("searching online", LinkAction.SearchBeatmapSet, filter.SearchText); + textFlow.AddText(" for this query."); + } + } + } + } +}