diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 7c05d99c59..64d1a9ddcd 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using osu.Game.Overlays; using NUnit.Framework; +using osu.Game.Overlays.BeatmapListing; namespace osu.Game.Tests.Visual.Online { @@ -13,6 +14,7 @@ namespace osu.Game.Tests.Visual.Online public override IReadOnlyList RequiredTypes => new[] { typeof(BeatmapListingOverlay), + typeof(BeatmapListingFilterControl) }; protected override bool UseOnlineAPI => true; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchSection.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs similarity index 69% rename from osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchSection.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index 1d8db71527..d6ede950df 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchSection.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs @@ -15,25 +15,27 @@ using osuTK; namespace osu.Game.Tests.Visual.UserInterface { - public class TestSceneBeatmapListingSearchSection : OsuTestScene + public class TestSceneBeatmapListingSearchControl : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { - typeof(BeatmapListingSearchSection), + typeof(BeatmapListingSearchControl), }; [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); - private readonly BeatmapListingSearchSection section; + private readonly BeatmapListingSearchControl control; - public TestSceneBeatmapListingSearchSection() + public TestSceneBeatmapListingSearchControl() { OsuSpriteText query; OsuSpriteText ruleset; OsuSpriteText category; + OsuSpriteText genre; + OsuSpriteText language; - Add(section = new BeatmapListingSearchSection + Add(control = new BeatmapListingSearchControl { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -49,20 +51,24 @@ namespace osu.Game.Tests.Visual.UserInterface query = new OsuSpriteText(), ruleset = new OsuSpriteText(), category = new OsuSpriteText(), + genre = new OsuSpriteText(), + language = new OsuSpriteText(), } }); - section.Query.BindValueChanged(q => query.Text = $"Query: {q.NewValue}", true); - section.Ruleset.BindValueChanged(r => ruleset.Text = $"Ruleset: {r.NewValue}", true); - section.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true); + control.Query.BindValueChanged(q => query.Text = $"Query: {q.NewValue}", true); + control.Ruleset.BindValueChanged(r => ruleset.Text = $"Ruleset: {r.NewValue}", true); + control.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true); + control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true); + control.Language.BindValueChanged(l => language.Text = $"Language: {l.NewValue}", true); } [Test] public void TestCovers() { - AddStep("Set beatmap", () => section.BeatmapSet = beatmap_set); - AddStep("Set beatmap (no cover)", () => section.BeatmapSet = no_cover_beatmap_set); - AddStep("Set null beatmap", () => section.BeatmapSet = null); + AddStep("Set beatmap", () => control.BeatmapSet = beatmap_set); + AddStep("Set beatmap (no cover)", () => control.BeatmapSet = no_cover_beatmap_set); + AddStep("Set null beatmap", () => control.BeatmapSet = null); } private static readonly BeatmapSetInfo beatmap_set = new BeatmapSetInfo diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSort.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSortTabControl.cs similarity index 91% rename from osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSort.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSortTabControl.cs index a5fa085abf..f643d4e3fe 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSort.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSortTabControl.cs @@ -13,18 +13,17 @@ using osuTK; namespace osu.Game.Tests.Visual.UserInterface { - public class TestSceneBeatmapListingSort : OsuTestScene + public class TestSceneBeatmapListingSortTabControl : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { - typeof(BeatmapListingSortTabControl), typeof(OverlaySortTabControl<>), }; [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); - public TestSceneBeatmapListingSort() + public TestSceneBeatmapListingSortTabControl() { BeatmapListingSortTabControl control; OsuSpriteText current; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapSearchFilter.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapSearchFilter.cs index 7b4424e568..283fe03af3 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapSearchFilter.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapSearchFilter.cs @@ -8,7 +8,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; -using osu.Game.Online.API.Requests; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapListing; using osuTK; @@ -20,8 +19,7 @@ namespace osu.Game.Tests.Visual.UserInterface public override IReadOnlyList RequiredTypes => new[] { typeof(BeatmapSearchFilterRow<>), - typeof(BeatmapSearchRulesetFilterRow), - typeof(BeatmapSearchSmallFilterRow<>), + typeof(BeatmapSearchRulesetFilterRow) }; [Cached] @@ -42,8 +40,8 @@ namespace osu.Game.Tests.Visual.UserInterface Children = new Drawable[] { new BeatmapSearchRulesetFilterRow(), - new BeatmapSearchFilterRow("Categories"), - new BeatmapSearchSmallFilterRow("Header Name") + new BeatmapSearchFilterRow("Categories"), + new BeatmapSearchFilterRow("Header Name") } }); } diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 930ca8fdf1..8345be5f82 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -1,9 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.ComponentModel; using osu.Framework.IO.Network; using osu.Game.Overlays; +using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.Direct; using osu.Game.Rulesets; @@ -11,20 +11,31 @@ namespace osu.Game.Online.API.Requests { public class SearchBeatmapSetsRequest : APIRequest { + public SearchCategory SearchCategory { get; set; } + + public DirectSortCriteria SortCriteria { get; set; } + + public SortDirection SortDirection { get; set; } + + public SearchGenre Genre { get; set; } + + public SearchLanguage Language { get; set; } + private readonly string query; private readonly RulesetInfo ruleset; - private readonly BeatmapSearchCategory searchCategory; - private readonly DirectSortCriteria sortCriteria; - private readonly SortDirection direction; - private string directionString => direction == SortDirection.Descending ? @"desc" : @"asc"; - public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset, BeatmapSearchCategory searchCategory = BeatmapSearchCategory.Any, DirectSortCriteria sortCriteria = DirectSortCriteria.Ranked, SortDirection direction = SortDirection.Descending) + private string directionString => SortDirection == SortDirection.Descending ? @"desc" : @"asc"; + + public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset) { this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query); this.ruleset = ruleset; - this.searchCategory = searchCategory; - this.sortCriteria = sortCriteria; - this.direction = direction; + + SearchCategory = SearchCategory.Any; + SortCriteria = DirectSortCriteria.Ranked; + SortDirection = SortDirection.Descending; + Genre = SearchGenre.Any; + Language = SearchLanguage.Any; } protected override WebRequest CreateWebRequest() @@ -35,31 +46,19 @@ namespace osu.Game.Online.API.Requests if (ruleset.ID.HasValue) req.AddParameter("m", ruleset.ID.Value.ToString()); - req.AddParameter("s", searchCategory.ToString().ToLowerInvariant()); - req.AddParameter("sort", $"{sortCriteria.ToString().ToLowerInvariant()}_{directionString}"); + req.AddParameter("s", SearchCategory.ToString().ToLowerInvariant()); + + if (Genre != SearchGenre.Any) + req.AddParameter("g", ((int)Genre).ToString()); + + if (Language != SearchLanguage.Any) + req.AddParameter("l", ((int)Language).ToString()); + + req.AddParameter("sort", $"{SortCriteria.ToString().ToLowerInvariant()}_{directionString}"); return req; } protected override string Target => @"beatmapsets/search"; } - - public enum BeatmapSearchCategory - { - Any, - - [Description("Has Leaderboard")] - Leaderboard, - Ranked, - Qualified, - Loved, - Favourites, - - [Description("Pending & WIP")] - Pending, - Graveyard, - - [Description("My Maps")] - Mine, - } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs new file mode 100644 index 0000000000..8817031bce --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -0,0 +1,163 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Threading; +using osu.Game.Beatmaps; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Direct; +using osu.Game.Rulesets; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.BeatmapListing +{ + public class BeatmapListingFilterControl : CompositeDrawable + { + public Action> SearchFinished; + public Action SearchStarted; + + [Resolved] + private IAPIProvider api { get; set; } + + [Resolved] + private RulesetStore rulesets { get; set; } + + private readonly BeatmapListingSearchControl searchControl; + private readonly BeatmapListingSortTabControl sortControl; + private readonly Box sortControlBackground; + + private SearchBeatmapSetsRequest getSetsRequest; + + public BeatmapListingFilterControl() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0.25f), + Type = EdgeEffectType.Shadow, + Radius = 3, + Offset = new Vector2(0f, 1f), + }, + Child = searchControl = new BeatmapListingSearchControl(), + }, + new Container + { + RelativeSizeAxes = Axes.X, + Height = 40, + Children = new Drawable[] + { + sortControlBackground = new Box + { + RelativeSizeAxes = Axes.Both + }, + sortControl = new BeatmapListingSortTabControl + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Left = 20 } + } + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + sortControlBackground.Colour = colourProvider.Background5; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + var sortCriteria = sortControl.Current; + var sortDirection = sortControl.SortDirection; + + searchControl.Query.BindValueChanged(query => + { + sortCriteria.Value = string.IsNullOrEmpty(query.NewValue) ? DirectSortCriteria.Ranked : DirectSortCriteria.Relevance; + sortDirection.Value = SortDirection.Descending; + queueUpdateSearch(true); + }); + + searchControl.Ruleset.BindValueChanged(_ => queueUpdateSearch()); + searchControl.Category.BindValueChanged(_ => queueUpdateSearch()); + searchControl.Genre.BindValueChanged(_ => queueUpdateSearch()); + searchControl.Language.BindValueChanged(_ => queueUpdateSearch()); + + sortCriteria.BindValueChanged(_ => queueUpdateSearch()); + sortDirection.BindValueChanged(_ => queueUpdateSearch()); + } + + private ScheduledDelegate queryChangedDebounce; + + private void queueUpdateSearch(bool queryTextChanged = false) + { + SearchStarted?.Invoke(); + + getSetsRequest?.Cancel(); + + queryChangedDebounce?.Cancel(); + queryChangedDebounce = Scheduler.AddDelayed(updateSearch, queryTextChanged ? 500 : 100); + } + + private void updateSearch() + { + getSetsRequest = new SearchBeatmapSetsRequest(searchControl.Query.Value, searchControl.Ruleset.Value) + { + SearchCategory = searchControl.Category.Value, + SortCriteria = sortControl.Current.Value, + SortDirection = sortControl.SortDirection.Value, + Genre = searchControl.Genre.Value, + Language = searchControl.Language.Value + }; + + getSetsRequest.Success += response => Schedule(() => onSearchFinished(response)); + + api.Queue(getSetsRequest); + } + + private void onSearchFinished(SearchBeatmapSetsResponse response) + { + var beatmaps = response.BeatmapSets.Select(r => r.ToBeatmapSet(rulesets)).ToList(); + + searchControl.BeatmapSet = response.Total == 0 ? null : beatmaps.First(); + + SearchFinished?.Invoke(beatmaps); + } + + protected override void Dispose(bool isDisposing) + { + getSetsRequest?.Cancel(); + queryChangedDebounce?.Cancel(); + + base.Dispose(isDisposing); + } + } +} diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs similarity index 82% rename from osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs rename to osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index f9799d8a6b..2ecdb18667 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -5,8 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Online.API.Requests; -using osu.Game.Rulesets; using osuTK; using osu.Framework.Bindables; using osu.Game.Beatmaps.Drawables; @@ -14,16 +12,21 @@ using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osuTK.Graphics; +using osu.Game.Rulesets; namespace osu.Game.Overlays.BeatmapListing { - public class BeatmapListingSearchSection : CompositeDrawable + public class BeatmapListingSearchControl : CompositeDrawable { public Bindable Query => textBox.Current; public Bindable Ruleset => modeFilter.Current; - public Bindable Category => categoryFilter.Current; + public Bindable Category => categoryFilter.Current; + + public Bindable Genre => genreFilter.Current; + + public Bindable Language => languageFilter.Current; public BeatmapSetInfo BeatmapSet { @@ -42,12 +45,14 @@ namespace osu.Game.Overlays.BeatmapListing private readonly BeatmapSearchTextBox textBox; private readonly BeatmapSearchRulesetFilterRow modeFilter; - private readonly BeatmapSearchFilterRow categoryFilter; + private readonly BeatmapSearchFilterRow categoryFilter; + private readonly BeatmapSearchFilterRow genreFilter; + private readonly BeatmapSearchFilterRow languageFilter; private readonly Box background; private readonly UpdateableBeatmapSetCover beatmapCover; - public BeatmapListingSearchSection() + public BeatmapListingSearchControl() { AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; @@ -97,7 +102,9 @@ namespace osu.Game.Overlays.BeatmapListing Children = new Drawable[] { modeFilter = new BeatmapSearchRulesetFilterRow(), - categoryFilter = new BeatmapSearchFilterRow(@"Categories"), + categoryFilter = new BeatmapSearchFilterRow(@"Categories"), + genreFilter = new BeatmapSearchFilterRow(@"Genre"), + languageFilter = new BeatmapSearchFilterRow(@"Language"), } } } @@ -105,7 +112,7 @@ namespace osu.Game.Overlays.BeatmapListing } }); - Category.Value = BeatmapSearchCategory.Leaderboard; + categoryFilter.Current.Value = SearchCategory.Leaderboard; } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs index 2c046a2bbf..64b3afcae1 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs @@ -15,6 +15,8 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osuTK; using osuTK.Graphics; +using Humanizer; +using osu.Game.Utils; namespace osu.Game.Overlays.BeatmapListing { @@ -53,8 +55,8 @@ namespace osu.Game.Overlays.BeatmapListing { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Font = OsuFont.GetFont(size: 10), - Text = headerName.ToUpper() + Font = OsuFont.GetFont(size: 13), + Text = headerName.Titleize() }, CreateFilter().With(f => { @@ -81,7 +83,7 @@ namespace osu.Game.Overlays.BeatmapListing if (typeof(T).IsEnum) { - foreach (var val in (T[])Enum.GetValues(typeof(T))) + foreach (var val in OrderAttributeUtils.GetValuesInOrder()) AddItem(val); } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchSmallFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchSmallFilterRow.cs deleted file mode 100644 index 6daa7cb0e0..0000000000 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchSmallFilterRow.cs +++ /dev/null @@ -1,32 +0,0 @@ -// 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.Graphics.UserInterface; - -namespace osu.Game.Overlays.BeatmapListing -{ - public class BeatmapSearchSmallFilterRow : BeatmapSearchFilterRow - { - public BeatmapSearchSmallFilterRow(string headerName) - : base(headerName) - { - } - - protected override BeatmapSearchFilter CreateFilter() => new SmallBeatmapSearchFilter(); - - private class SmallBeatmapSearchFilter : BeatmapSearchFilter - { - protected override TabItem CreateTabItem(T value) => new SmallTabItem(value); - - private class SmallTabItem : FilterTabItem - { - public SmallTabItem(T value) - : base(value) - { - } - - protected override float TextSize => 10; - } - } - } -} diff --git a/osu.Game/Overlays/BeatmapListing/SearchCategory.cs b/osu.Game/Overlays/BeatmapListing/SearchCategory.cs new file mode 100644 index 0000000000..84859bf5b5 --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/SearchCategory.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.ComponentModel; + +namespace osu.Game.Overlays.BeatmapListing +{ + public enum SearchCategory + { + Any, + + [Description("Has Leaderboard")] + Leaderboard, + Ranked, + Qualified, + Loved, + Favourites, + + [Description("Pending & WIP")] + Pending, + Graveyard, + + [Description("My Maps")] + Mine, + } +} diff --git a/osu.Game/Overlays/BeatmapListing/SearchGenre.cs b/osu.Game/Overlays/BeatmapListing/SearchGenre.cs new file mode 100644 index 0000000000..b12bba6249 --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/SearchGenre.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.ComponentModel; + +namespace osu.Game.Overlays.BeatmapListing +{ + public enum SearchGenre + { + Any = 0, + Unspecified = 1, + + [Description("Video Game")] + VideoGame = 2, + Anime = 3, + Rock = 4, + Pop = 5, + Other = 6, + Novelty = 7, + + [Description("Hip Hop")] + HipHop = 9, + Electronic = 10 + } +} diff --git a/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs b/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs new file mode 100644 index 0000000000..dac7e4f1a2 --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Utils; + +namespace osu.Game.Overlays.BeatmapListing +{ + [HasOrderedElements] + public enum SearchLanguage + { + [Order(0)] + Any, + + [Order(11)] + Other, + + [Order(1)] + English, + + [Order(6)] + Japanese, + + [Order(2)] + Chinese, + + [Order(10)] + Instrumental, + + [Order(7)] + Korean, + + [Order(3)] + French, + + [Order(4)] + German, + + [Order(9)] + Swedish, + + [Order(8)] + Spanish, + + [Order(5)] + Italian + } +} diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index b450f33ee1..e16924464d 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -1,27 +1,23 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.Linq; +using System.Threading; using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Framework.Threading; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API.Requests; using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.Direct; -using osu.Game.Rulesets; using osuTK; -using osuTK.Graphics; namespace osu.Game.Overlays { @@ -30,14 +26,9 @@ namespace osu.Game.Overlays [Resolved] private PreviewTrackManager previewTrackManager { get; set; } - [Resolved] - private RulesetStore rulesets { get; set; } - - private SearchBeatmapSetsRequest getSetsRequest; - private Drawable currentContent; - private BeatmapListingSearchSection searchSection; - private BeatmapListingSortTabControl sortControl; + private LoadingLayer loadingLayer; + private Container panelTarget; public BeatmapListingOverlay() : base(OverlayColourScheme.Blue) @@ -63,27 +54,13 @@ namespace osu.Game.Overlays AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10), Children = new Drawable[] { - new FillFlowContainer + new BeatmapListingHeader(), + new BeatmapListingFilterControl { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Masking = true, - EdgeEffect = new EdgeEffectParameters - { - Colour = Color4.Black.Opacity(0.25f), - Type = EdgeEffectType.Shadow, - Radius = 3, - Offset = new Vector2(0f, 1f), - }, - Children = new Drawable[] - { - new BeatmapListingHeader(), - searchSection = new BeatmapListingSearchSection(), - } + SearchStarted = onSearchStarted, + SearchFinished = onSearchFinished, }, new Container { @@ -96,128 +73,41 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Both, Colour = ColourProvider.Background4, }, - new FillFlowContainer + panelTarget = new Container { - RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.X, - Height = 40, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourProvider.Background5 - }, - sortControl = new BeatmapListingSortTabControl - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Margin = new MarginPadding { Left = 20 } - } - } - }, - new Container - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Padding = new MarginPadding { Horizontal = 20 }, - Children = new Drawable[] - { - panelTarget = new Container - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - }, - loadingLayer = new LoadingLayer(panelTarget), - } - }, - } - } + RelativeSizeAxes = Axes.X, + Padding = new MarginPadding { Horizontal = 20 } + }, + loadingLayer = new LoadingLayer(panelTarget) } - } + }, } } } }; } - protected override void LoadComplete() + private CancellationTokenSource cancellationToken; + + private void onSearchStarted() { - base.LoadComplete(); - - var sortCriteria = sortControl.Current; - var sortDirection = sortControl.SortDirection; - - searchSection.Query.BindValueChanged(query => - { - sortCriteria.Value = string.IsNullOrEmpty(query.NewValue) ? DirectSortCriteria.Ranked : DirectSortCriteria.Relevance; - sortDirection.Value = SortDirection.Descending; - - queueUpdateSearch(true); - }); - - searchSection.Ruleset.BindValueChanged(_ => queueUpdateSearch()); - searchSection.Category.BindValueChanged(_ => queueUpdateSearch()); - sortCriteria.BindValueChanged(_ => queueUpdateSearch()); - sortDirection.BindValueChanged(_ => queueUpdateSearch()); - } - - private ScheduledDelegate queryChangedDebounce; - - private LoadingLayer loadingLayer; - private Container panelTarget; - - private void queueUpdateSearch(bool queryTextChanged = false) - { - getSetsRequest?.Cancel(); - - queryChangedDebounce?.Cancel(); - queryChangedDebounce = Scheduler.AddDelayed(updateSearch, queryTextChanged ? 500 : 100); - } - - private void updateSearch() - { - if (!IsLoaded) - return; - - if (State.Value == Visibility.Hidden) - return; - - if (API == null) - return; + cancellationToken?.Cancel(); previewTrackManager.StopAnyPlaying(this); - loadingLayer.Show(); - - getSetsRequest = new SearchBeatmapSetsRequest( - searchSection.Query.Value, - searchSection.Ruleset.Value, - searchSection.Category.Value, - sortControl.Current.Value, - sortControl.SortDirection.Value); - - getSetsRequest.Success += response => Schedule(() => recreatePanels(response)); - - API.Queue(getSetsRequest); + if (panelTarget.Any()) + loadingLayer.Show(); } - private void recreatePanels(SearchBeatmapSetsResponse response) + private void onSearchFinished(List beatmaps) { - if (response.Total == 0) + if (!beatmaps.Any()) { - searchSection.BeatmapSet = null; - LoadComponentAsync(new NotFoundDrawable(), addContentToPlaceholder); + LoadComponentAsync(new NotFoundDrawable(), addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); return; } - var beatmaps = response.BeatmapSets.Select(r => r.ToBeatmapSet(rulesets)).ToList(); - var newPanels = new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -232,18 +122,14 @@ namespace osu.Game.Overlays }) }; - LoadComponentAsync(newPanels, loaded => - { - addContentToPlaceholder(loaded); - searchSection.BeatmapSet = beatmaps.First(); - }); + LoadComponentAsync(newPanels, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); } private void addContentToPlaceholder(Drawable content) { loadingLayer.Hide(); - Drawable lastContent = currentContent; + var lastContent = currentContent; if (lastContent != null) { @@ -262,9 +148,7 @@ namespace osu.Game.Overlays protected override void Dispose(bool isDisposing) { - getSetsRequest?.Cancel(); - queryChangedDebounce?.Cancel(); - + cancellationToken?.Cancel(); base.Dispose(isDisposing); } diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs index e5b2b5cc34..4ab5544550 100644 --- a/osu.Game/Overlays/Direct/FilterControl.cs +++ b/osu.Game/Overlays/Direct/FilterControl.cs @@ -6,20 +6,20 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Game.Graphics; -using osu.Game.Online.API.Requests; +using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.SearchableList; using osu.Game.Rulesets; using osuTK.Graphics; namespace osu.Game.Overlays.Direct { - public class FilterControl : SearchableListFilterControl + public class FilterControl : SearchableListFilterControl { private DirectRulesetSelector rulesetSelector; protected override Color4 BackgroundColour => Color4Extensions.FromHex(@"384552"); protected override DirectSortCriteria DefaultTab => DirectSortCriteria.Ranked; - protected override BeatmapSearchCategory DefaultCategory => BeatmapSearchCategory.Leaderboard; + protected override SearchCategory DefaultCategory => SearchCategory.Leaderboard; protected override Drawable CreateSupplementaryControls() => rulesetSelector = new DirectRulesetSelector(); diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index 61986d1cf0..5ed39af0dc 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -16,6 +16,7 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests; +using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.Direct; using osu.Game.Overlays.SearchableList; using osu.Game.Rulesets; @@ -24,7 +25,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays { - public class DirectOverlay : SearchableListOverlay + public class DirectOverlay : SearchableListOverlay { private const float panel_padding = 10f; @@ -40,7 +41,7 @@ namespace osu.Game.Overlays protected override Color4 TrianglesColourDark => Color4Extensions.FromHex(@"3f5265"); protected override SearchableListHeader CreateHeader() => new Header(); - protected override SearchableListFilterControl CreateFilterControl() => new FilterControl(); + protected override SearchableListFilterControl CreateFilterControl() => new FilterControl(); private IEnumerable beatmapSets; @@ -255,11 +256,11 @@ namespace osu.Game.Overlays previewTrackManager.StopAnyPlaying(this); - getSetsRequest = new SearchBeatmapSetsRequest( - currentQuery.Value, - ((FilterControl)Filter).Ruleset.Value, - Filter.DisplayStyleControl.Dropdown.Current.Value, - Filter.Tabs.Current.Value); //todo: sort direction (?) + getSetsRequest = new SearchBeatmapSetsRequest(currentQuery.Value, ((FilterControl)Filter).Ruleset.Value) + { + SearchCategory = Filter.DisplayStyleControl.Dropdown.Current.Value, + SortCriteria = Filter.Tabs.Current.Value + }; getSetsRequest.Success += response => { diff --git a/osu.Game/Utils/OrderAttribute.cs b/osu.Game/Utils/OrderAttribute.cs new file mode 100644 index 0000000000..aded7f9814 --- /dev/null +++ b/osu.Game/Utils/OrderAttribute.cs @@ -0,0 +1,52 @@ +// 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; + +namespace osu.Game.Utils +{ + public static class OrderAttributeUtils + { + /// + /// Get values of an enum in order. Supports custom ordering via . + /// + public static IEnumerable GetValuesInOrder() + { + var type = typeof(T); + + if (!type.IsEnum) + throw new InvalidOperationException("T must be an enum"); + + IEnumerable items = (T[])Enum.GetValues(type); + + if (Attribute.GetCustomAttribute(type, typeof(HasOrderedElementsAttribute)) == null) + return items; + + return items.OrderBy(i => + { + if (type.GetField(i.ToString()).GetCustomAttributes(typeof(OrderAttribute), false).FirstOrDefault() is OrderAttribute attr) + return attr.Order; + + throw new ArgumentException($"Not all values of {nameof(T)} have {nameof(OrderAttribute)} specified."); + }); + } + } + + [AttributeUsage(AttributeTargets.Field)] + public class OrderAttribute : Attribute + { + public readonly int Order; + + public OrderAttribute(int order) + { + Order = order; + } + } + + [AttributeUsage(AttributeTargets.Enum)] + public class HasOrderedElementsAttribute : Attribute + { + } +}