diff --git a/osu.Android.props b/osu.Android.props
index b9b127309a..b9ef783b2a 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -53,7 +53,7 @@
-
+
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-bananas-overlay.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-bananas-overlay.png
new file mode 100644
index 0000000000..3a6612378e
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-bananas-overlay.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-bananas.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-bananas.png
new file mode 100644
index 0000000000..afb8698b2d
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-bananas.png differ
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
index db52fbac1b..1a5d0f983b 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
@@ -34,9 +34,14 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
foreach (var obj in Beatmap.HitObjects.OfType())
{
- obj.IndexInBeatmap = index++;
+ obj.IndexInBeatmap = index;
+ foreach (var nested in obj.NestedHitObjects.OfType())
+ nested.IndexInBeatmap = index;
+
if (obj.LastInCombo && obj.NestedHitObjects.LastOrDefault() is IHasComboInformation lastNested)
lastNested.LastInCombo = true;
+
+ index++;
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
index e4ad49ea50..5243091625 100644
--- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
@@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.Objects
public int IndexInBeatmap { get; set; }
- public virtual FruitVisualRepresentation VisualRepresentation => (FruitVisualRepresentation)(ComboIndex % 4);
+ public virtual FruitVisualRepresentation VisualRepresentation => (FruitVisualRepresentation)(IndexInBeatmap % 4);
public virtual bool NewCombo { get; set; }
@@ -100,8 +100,8 @@ namespace osu.Game.Rulesets.Catch.Objects
{
Pear,
Grape,
- Raspberry,
Pineapple,
+ Raspberry,
Banana // banananananannaanana
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs
index 4f54e451b7..9fbe8f7ffe 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
typeof(DrawableRoomPlaylistItem)
};
- private DrawableRoomPlaylist playlist;
+ private TestPlaylist playlist;
[Test]
public void TestNonEditableNonSelectable()
@@ -211,30 +211,45 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void assertDeleteButtonVisibility(int index, bool visible)
=> AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible", () => (playlist.ChildrenOfType().ElementAt(2 + index * 2).Alpha > 0) == visible);
- private void createPlaylist(bool allowEdit, bool allowSelection) => AddStep("create playlist", () =>
+ private void createPlaylist(bool allowEdit, bool allowSelection)
{
- Child = playlist = new DrawableRoomPlaylist(allowEdit, allowSelection)
+ AddStep("create playlist", () =>
{
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(500, 300)
- };
-
- for (int i = 0; i < 20; i++)
- {
- playlist.Items.Add(new PlaylistItem
+ Child = playlist = new TestPlaylist(allowEdit, allowSelection)
{
- ID = i,
- Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
- Ruleset = { Value = new OsuRuleset().RulesetInfo },
- RequiredMods =
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(500, 300)
+ };
+
+ for (int i = 0; i < 20; i++)
+ {
+ playlist.Items.Add(new PlaylistItem
{
- new OsuModHardRock(),
- new OsuModDoubleTime(),
- new OsuModAutoplay()
- }
- });
+ ID = i,
+ Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
+ Ruleset = { Value = new OsuRuleset().RulesetInfo },
+ RequiredMods =
+ {
+ new OsuModHardRock(),
+ new OsuModDoubleTime(),
+ new OsuModAutoplay()
+ }
+ });
+ }
+ });
+
+ AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
+ }
+
+ private class TestPlaylist : DrawableRoomPlaylist
+ {
+ public new IReadOnlyDictionary> ItemMap => base.ItemMap;
+
+ public TestPlaylist(bool allowEdit, bool allowSelection)
+ : base(allowEdit, allowSelection)
+ {
}
- });
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedPlaylist.cs
index 4e38f6d2c2..14b7934dc7 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedPlaylist.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedPlaylist.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Multi.Components;
@@ -27,12 +26,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
});
}
- Add(new Container
+ Add(new OverlinedPlaylist(false)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(500),
- Child = new OverlinedPlaylist(false)
});
}
}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs
new file mode 100644
index 0000000000..7c05d99c59
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs
@@ -0,0 +1,39 @@
+// 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 osu.Game.Overlays;
+using NUnit.Framework;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneBeatmapListingOverlay : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(BeatmapListingOverlay),
+ };
+
+ protected override bool UseOnlineAPI => true;
+
+ private readonly BeatmapListingOverlay overlay;
+
+ public TestSceneBeatmapListingOverlay()
+ {
+ Add(overlay = new BeatmapListingOverlay());
+ }
+
+ [Test]
+ public void TestShow()
+ {
+ AddStep("Show", overlay.Show);
+ }
+
+ [Test]
+ public void TestHide()
+ {
+ AddStep("Hide", overlay.Hide);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs
index a769ebe4a9..83e5cd0fe7 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs
@@ -29,8 +29,8 @@ namespace osu.Game.Tests.Visual.Online
typeof(RankingsOverlayHeader)
};
- [Cached]
- private RankingsOverlay rankingsOverlay;
+ [Cached(typeof(RankingsOverlay))]
+ private readonly RankingsOverlay rankingsOverlay;
private readonly Bindable countryBindable = new Bindable();
private readonly Bindable scope = new Bindable();
diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs
index 3c959e05c1..51f4089058 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs
@@ -195,6 +195,29 @@ namespace osu.Game.Tests.Visual.Online
Position = 1337,
};
+ var myBestScoreWithNullPosition = new APILegacyUserTopScoreInfo
+ {
+ Score = new APILegacyScoreInfo
+ {
+ User = new User
+ {
+ Id = 7151382,
+ Username = @"Mayuri Hana",
+ Country = new Country
+ {
+ FullName = @"Thailand",
+ FlagName = @"TH",
+ },
+ },
+ Rank = ScoreRank.D,
+ PP = 160,
+ MaxCombo = 1234,
+ TotalScore = 123456,
+ Accuracy = 0.6543,
+ },
+ Position = null,
+ };
+
var oneScore = new APILegacyScores
{
Scores = new List
@@ -250,6 +273,12 @@ namespace osu.Game.Tests.Visual.Online
allScores.UserScore = myBestScore;
scoresContainer.Scores = allScores;
});
+
+ AddStep("Load scores with null my best position", () =>
+ {
+ allScores.UserScore = myBestScoreWithNullPosition;
+ scoresContainer.Scores = allScores;
+ });
}
private class TestScoresContainer : ScoresContainer
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs
index 3eff75b020..1198488bda 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs
@@ -59,6 +59,33 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep(@"None selected", () => leaderboard.SetRetrievalState(PlaceholderState.NoneSelected));
foreach (BeatmapSetOnlineStatus status in Enum.GetValues(typeof(BeatmapSetOnlineStatus)))
AddStep($"{status} beatmap", () => showBeatmapWithStatus(status));
+ AddStep("null personal best position", showPersonalBestWithNullPosition);
+ }
+
+ private void showPersonalBestWithNullPosition()
+ {
+ leaderboard.TopScore = new APILegacyUserTopScoreInfo
+ {
+ Position = null,
+ Score = new APILegacyScoreInfo
+ {
+ Rank = ScoreRank.XH,
+ Accuracy = 1,
+ MaxCombo = 244,
+ TotalScore = 1707827,
+ Mods = new[] { new OsuModHidden().Acronym, new OsuModHardRock().Acronym, },
+ User = new User
+ {
+ Id = 6602580,
+ Username = @"waaiiru",
+ Country = new Country
+ {
+ FullName = @"Spain",
+ FlagName = @"ES",
+ },
+ },
+ }
+ };
}
private void showPersonalBest()
diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs
index bcc9ab885e..68d113ce40 100644
--- a/osu.Game/Beatmaps/BeatmapInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapInfo.cs
@@ -51,6 +51,9 @@ namespace osu.Game.Beatmaps
[NotMapped]
public BeatmapOnlineInfo OnlineInfo { get; set; }
+ [NotMapped]
+ public int? MaxCombo { get; set; }
+
///
/// The playable length in milliseconds of this beatmap.
///
diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs
index f4d67a56aa..e023a2502f 100644
--- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs
+++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs
@@ -61,6 +61,9 @@ namespace osu.Game.Online.API.Requests.Responses
[JsonProperty(@"failtimes")]
private BeatmapMetrics metrics { get; set; }
+ [JsonProperty(@"max_combo")]
+ private int? maxCombo { get; set; }
+
public BeatmapInfo ToBeatmap(RulesetStore rulesets)
{
var set = BeatmapSet?.ToBeatmapSet(rulesets);
@@ -76,6 +79,7 @@ namespace osu.Game.Online.API.Requests.Responses
Status = Status,
BeatmapSet = set,
Metrics = metrics,
+ MaxCombo = maxCombo,
BaseDifficulty = new BeatmapDifficulty
{
DrainRate = drainRate,
diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs
index 318fcb00de..75be9171b0 100644
--- a/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs
+++ b/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Online.API.Requests.Responses
public class APILegacyUserTopScoreInfo
{
[JsonProperty(@"position")]
- public int Position;
+ public int? Position;
[JsonProperty(@"score")]
public APILegacyScoreInfo Score;
diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs
index 5652b8d2bd..930ca8fdf1 100644
--- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs
+++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs
@@ -2,6 +2,7 @@
// 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.Direct;
using osu.Game.Rulesets;
@@ -26,8 +27,21 @@ namespace osu.Game.Online.API.Requests
this.direction = direction;
}
- // ReSharper disable once ImpureMethodCallOnReadonlyValueField
- protected override string Target => $@"beatmapsets/search?q={query}&m={ruleset.ID ?? 0}&s={searchCategory.ToString().ToLowerInvariant()}&sort={sortCriteria.ToString().ToLowerInvariant()}_{directionString}";
+ protected override WebRequest CreateWebRequest()
+ {
+ var req = base.CreateWebRequest();
+ req.AddParameter("q", query);
+
+ if (ruleset.ID.HasValue)
+ req.AddParameter("m", ruleset.ID.Value.ToString());
+
+ req.AddParameter("s", searchCategory.ToString().ToLowerInvariant());
+ req.AddParameter("sort", $"{sortCriteria.ToString().ToLowerInvariant()}_{directionString}");
+
+ return req;
+ }
+
+ protected override string Target => @"beatmapsets/search";
}
public enum BeatmapSearchCategory
diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsResponse.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsResponse.cs
index 28863cb0e0..3c4fb11ed1 100644
--- a/osu.Game/Online/API/Requests/SearchBeatmapSetsResponse.cs
+++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsResponse.cs
@@ -2,12 +2,17 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
+using Newtonsoft.Json;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests
{
public class SearchBeatmapSetsResponse : ResponseWithCursor
{
+ [JsonProperty("beatmapsets")]
public IEnumerable BeatmapSets;
+
+ [JsonProperty("total")]
+ public int Total;
}
}
diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs
index 1f52a4481b..ba92b993a2 100644
--- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs
+++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs
@@ -41,7 +41,7 @@ namespace osu.Game.Online.Leaderboards
protected Container RankContainer { get; private set; }
private readonly ScoreInfo score;
- private readonly int rank;
+ private readonly int? rank;
private readonly bool allowHighlight;
private Box background;
@@ -58,7 +58,7 @@ namespace osu.Game.Online.Leaderboards
[Resolved(CanBeNull = true)]
private DialogOverlay dialogOverlay { get; set; }
- public LeaderboardScore(ScoreInfo score, int rank, bool allowHighlight = true)
+ public LeaderboardScore(ScoreInfo score, int? rank, bool allowHighlight = true)
{
this.score = score;
this.rank = rank;
@@ -90,7 +90,7 @@ namespace osu.Game.Online.Leaderboards
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = OsuFont.GetFont(size: 20, italics: true),
- Text = rank.ToMetric(decimals: rank < 100000 ? 1 : 0),
+ Text = rank == null ? "-" : rank.Value.ToMetric(decimals: rank < 100000 ? 1 : 0),
},
},
},
diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs
new file mode 100644
index 0000000000..5af92914de
--- /dev/null
+++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs
@@ -0,0 +1,24 @@
+// 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;
+using osu.Game.Graphics.UserInterface;
+
+namespace osu.Game.Overlays.BeatmapListing
+{
+ public class BeatmapListingHeader : OverlayHeader
+ {
+ protected override ScreenTitle CreateTitle() => new BeatmapListingTitle();
+
+ private class BeatmapListingTitle : ScreenTitle
+ {
+ public BeatmapListingTitle()
+ {
+ Title = @"beatmap";
+ Section = @"listing";
+ }
+
+ protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/changelog");
+ }
+ }
+}
diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs
index f47144e5d8..f9799d8a6b 100644
--- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs
+++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs
@@ -104,6 +104,8 @@ namespace osu.Game.Overlays.BeatmapListing
}
}
});
+
+ Category.Value = BeatmapSearchCategory.Leaderboard;
}
[BackgroundDependencyLoader]
diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs
index cb41b33bc4..27c43b092a 100644
--- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs
+++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs
@@ -8,16 +8,17 @@ using osu.Framework.Graphics;
using osuTK.Graphics;
using osuTK;
using osu.Framework.Input.Events;
+using osu.Game.Overlays.Direct;
namespace osu.Game.Overlays.BeatmapListing
{
- public class BeatmapListingSortTabControl : OverlaySortTabControl
+ public class BeatmapListingSortTabControl : OverlaySortTabControl
{
public readonly Bindable SortDirection = new Bindable(Overlays.SortDirection.Descending);
public BeatmapListingSortTabControl()
{
- Current.Value = BeatmapSortCriteria.Ranked;
+ Current.Value = DirectSortCriteria.Ranked;
}
protected override SortTabControl CreateControl() => new BeatmapSortTabControl
@@ -29,7 +30,7 @@ namespace osu.Game.Overlays.BeatmapListing
{
public readonly Bindable SortDirection = new Bindable();
- protected override TabItem CreateTabItem(BeatmapSortCriteria value) => new BeatmapSortTabItem(value)
+ protected override TabItem CreateTabItem(DirectSortCriteria value) => new BeatmapSortTabItem(value)
{
SortDirection = { BindTarget = SortDirection }
};
@@ -39,12 +40,12 @@ namespace osu.Game.Overlays.BeatmapListing
{
public readonly Bindable SortDirection = new Bindable();
- public BeatmapSortTabItem(BeatmapSortCriteria value)
+ public BeatmapSortTabItem(DirectSortCriteria value)
: base(value)
{
}
- protected override TabButton CreateTabButton(BeatmapSortCriteria value) => new BeatmapTabButton(value)
+ protected override TabButton CreateTabButton(DirectSortCriteria value) => new BeatmapTabButton(value)
{
Active = { BindTarget = Active },
SortDirection = { BindTarget = SortDirection }
@@ -66,7 +67,7 @@ namespace osu.Game.Overlays.BeatmapListing
private readonly SpriteIcon icon;
- public BeatmapTabButton(BeatmapSortCriteria value)
+ public BeatmapTabButton(DirectSortCriteria value)
: base(value)
{
Add(icon = new SpriteIcon
@@ -104,15 +105,4 @@ namespace osu.Game.Overlays.BeatmapListing
}
}
}
-
- public enum BeatmapSortCriteria
- {
- Title,
- Artist,
- Difficulty,
- Ranked,
- Rating,
- Plays,
- Favourites,
- }
}
diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs
new file mode 100644
index 0000000000..213e9a4244
--- /dev/null
+++ b/osu.Game/Overlays/BeatmapListingOverlay.cs
@@ -0,0 +1,299 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+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.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.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
+{
+ public class BeatmapListingOverlay : FullscreenOverlay
+ {
+ [Resolved]
+ private PreviewTrackManager previewTrackManager { get; set; }
+
+ [Resolved]
+ private RulesetStore rulesets { get; set; }
+
+ private SearchBeatmapSetsRequest getSetsRequest;
+
+ private Container panelsPlaceholder;
+ private Drawable currentContent;
+ private BeatmapListingSearchSection searchSection;
+ private BeatmapListingSortTabControl sortControl;
+
+ public BeatmapListingOverlay()
+ : base(OverlayColourScheme.Blue)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = ColourProvider.Background6
+ },
+ new BasicScrollContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ ScrollbarVisible = false,
+ Child = new ReverseChildIDFillFlowContainer
+ {
+ AutoSizeAxes = Axes.Y,
+ RelativeSizeAxes = Axes.X,
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(0, 10),
+ Children = new Drawable[]
+ {
+ new FillFlowContainer
+ {
+ 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(),
+ }
+ },
+ new Container
+ {
+ AutoSizeAxes = Axes.Y,
+ RelativeSizeAxes = Axes.X,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = ColourProvider.Background4,
+ },
+ new FillFlowContainer
+ {
+ 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 }
+ }
+ }
+ },
+ panelsPlaceholder = new Container
+ {
+ AutoSizeAxes = Axes.Y,
+ RelativeSizeAxes = Axes.X,
+ Padding = new MarginPadding { Horizontal = 20 },
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ 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 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;
+
+ previewTrackManager.StopAnyPlaying(this);
+
+ currentContent?.FadeColour(Color4.DimGray, 400, Easing.OutQuint);
+
+ 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);
+ }
+
+ private void recreatePanels(SearchBeatmapSetsResponse response)
+ {
+ if (response.Total == 0)
+ {
+ searchSection.BeatmapSet = null;
+ LoadComponentAsync(new NotFoundDrawable(), addContentToPlaceholder);
+ return;
+ }
+
+ var beatmaps = response.BeatmapSets.Select(r => r.ToBeatmapSet(rulesets)).ToList();
+
+ var newPanels = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Spacing = new Vector2(10),
+ Alpha = 0,
+ Margin = new MarginPadding { Vertical = 15 },
+ ChildrenEnumerable = beatmaps.Select(b => new DirectGridPanel(b)
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ })
+ };
+
+ LoadComponentAsync(newPanels, loaded =>
+ {
+ addContentToPlaceholder(loaded);
+ searchSection.BeatmapSet = beatmaps.First();
+ });
+ }
+
+ private void addContentToPlaceholder(Drawable content)
+ {
+ Drawable lastContent = currentContent;
+
+ if (lastContent != null)
+ {
+ lastContent.FadeOut(100, Easing.OutQuint).Expire();
+
+ // Consider the case when the new content is smaller than the last content.
+ // If the auto-size computation is delayed until fade out completes, the background remain high for too long making the resulting transition to the smaller height look weird.
+ // At the same time, if the last content's height is bypassed immediately, there is a period where the new content is at Alpha = 0 when the auto-sized height will be 0.
+ // To resolve both of these issues, the bypass is delayed until a point when the content transitions (fade-in and fade-out) overlap and it looks good to do so.
+ lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y);
+ }
+
+ panelsPlaceholder.Add(currentContent = content);
+ currentContent.FadeIn(200, Easing.OutQuint);
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ getSetsRequest?.Cancel();
+ queryChangedDebounce?.Cancel();
+
+ base.Dispose(isDisposing);
+ }
+
+ private class NotFoundDrawable : CompositeDrawable
+ {
+ public NotFoundDrawable()
+ {
+ RelativeSizeAxes = Axes.X;
+ Height = 250;
+ Alpha = 0;
+ Margin = new MarginPadding { Top = 15 };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ AddInternal(new FillFlowContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Y,
+ AutoSizeAxes = Axes.X,
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(10, 0),
+ Children = new Drawable[]
+ {
+ new Sprite
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ FillMode = FillMode.Fit,
+ Texture = textures.Get(@"Online/not-found")
+ },
+ new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Text = @"... nope, nothing found.",
+ }
+ }
+ });
+ }
+ }
+ }
+}
diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs
index bb85b4a37b..cad37dd082 100644
--- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs
+++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
{
private readonly Box background;
- public DrawableTopScore(ScoreInfo score, int position = 1)
+ public DrawableTopScore(ScoreInfo score, int? position = 1)
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs
index 43a45bd2fc..954eada612 100644
--- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs
+++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs
@@ -146,7 +146,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
new OsuSpriteText
{
Text = $@"{score.MaxCombo:N0}x",
- Font = OsuFont.GetFont(size: text_size)
+ Font = OsuFont.GetFont(size: text_size),
+ Colour = score.MaxCombo == score.Beatmap?.MaxCombo ? highAccuracyColour : Color4.White
}
});
diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs
index 8a368aa535..9a67ea2b24 100644
--- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs
+++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs
@@ -112,9 +112,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
};
}
- public int ScorePosition
+ public int? ScorePosition
{
- set => rankText.Text = $"#{value}";
+ set => rankText.Text = value == null ? "-" : $"#{value}";
}
///
diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index 8b04bf0387..70a3ab54fb 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -34,14 +34,13 @@ namespace osu.Game.Overlays.Direct
public enum DirectSortCriteria
{
- Relevance,
Title,
Artist,
- Creator,
Difficulty,
Ranked,
Rating,
Plays,
Favourites,
+ Relevance,
}
}
diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs
index 8f753fd3aa..b878aba489 100644
--- a/osu.Game/Overlays/Music/PlaylistOverlay.cs
+++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs
@@ -75,8 +75,6 @@ namespace osu.Game.Overlays.Music
},
};
- list.Items.BindTo(beatmapSets);
-
filter.Search.OnCommit = (sender, newText) =>
{
BeatmapInfo toSelect = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault();
@@ -87,7 +85,13 @@ namespace osu.Game.Overlays.Music
beatmap.Value.Track.Restart();
}
};
+ }
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ list.Items.BindTo(beatmapSets);
beatmap.BindValueChanged(working => list.SelectedSet.Value = working.NewValue.BeatmapSetInfo, true);
}
diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs
index 7c7daf6eb9..d788929739 100644
--- a/osu.Game/Overlays/MusicController.cs
+++ b/osu.Game/Overlays/MusicController.cs
@@ -25,7 +25,16 @@ namespace osu.Game.Overlays
[Resolved]
private BeatmapManager beatmaps { get; set; }
- public IBindableList BeatmapSets => beatmapSets;
+ public IBindableList BeatmapSets
+ {
+ get
+ {
+ if (LoadState < LoadState.Ready)
+ throw new InvalidOperationException($"{nameof(BeatmapSets)} should not be accessed before the music controller is loaded.");
+
+ return beatmapSets;
+ }
+ }
///
/// Point in time after which the current track will be restarted on triggering a "previous track" action.
@@ -54,16 +63,18 @@ namespace osu.Game.Overlays
[BackgroundDependencyLoader]
private void load()
{
- beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets().OrderBy(_ => RNG.Next()));
beatmaps.ItemAdded += handleBeatmapAdded;
beatmaps.ItemRemoved += handleBeatmapRemoved;
+
+ beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets().OrderBy(_ => RNG.Next()));
}
protected override void LoadComplete()
{
+ base.LoadComplete();
+
beatmap.BindValueChanged(beatmapChanged, true);
mods.BindValueChanged(_ => ResetTrackAdjustments(), true);
- base.LoadComplete();
}
///
@@ -82,11 +93,16 @@ namespace osu.Game.Overlays
///
public bool IsPlaying => current?.Track.IsRunning ?? false;
- private void handleBeatmapAdded(BeatmapSetInfo set) =>
- Schedule(() => beatmapSets.Add(set));
+ private void handleBeatmapAdded(BeatmapSetInfo set) => Schedule(() =>
+ {
+ if (!beatmapSets.Contains(set))
+ beatmapSets.Add(set);
+ });
- private void handleBeatmapRemoved(BeatmapSetInfo set) =>
- Schedule(() => beatmapSets.RemoveAll(s => s.ID == set.ID));
+ private void handleBeatmapRemoved(BeatmapSetInfo set) => Schedule(() =>
+ {
+ beatmapSets.RemoveAll(s => s.ID == set.ID);
+ });
private ScheduledDelegate seekDelegate;
diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs
index dfcf99d30c..118cb037cb 100644
--- a/osu.Game/Overlays/NowPlayingOverlay.cs
+++ b/osu.Game/Overlays/NowPlayingOverlay.cs
@@ -58,6 +58,9 @@ namespace osu.Game.Overlays
[Resolved]
private Bindable beatmap { get; set; }
+ [Resolved]
+ private OsuColour colours { get; set; }
+
public NowPlayingOverlay()
{
Width = 400;
@@ -65,7 +68,7 @@ namespace osu.Game.Overlays
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours)
+ private void load()
{
Children = new Drawable[]
{
@@ -182,15 +185,15 @@ namespace osu.Game.Overlays
}
}
};
-
- playlist.BeatmapSets.BindTo(musicController.BeatmapSets);
- playlist.State.ValueChanged += s => playlistButton.FadeColour(s.NewValue == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint);
}
protected override void LoadComplete()
{
base.LoadComplete();
+ playlist.BeatmapSets.BindTo(musicController.BeatmapSets);
+ playlist.State.BindValueChanged(s => playlistButton.FadeColour(s.NewValue == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint), true);
+
beatmap.BindDisabledChanged(beatmapDisabledChanged, true);
musicController.TrackChanged += trackChanged;
diff --git a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs
index 32ac1404bc..0b9a48ce0e 100644
--- a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs
+++ b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs
@@ -9,6 +9,7 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Graphics;
using System.Collections.Generic;
using osu.Framework.Allocation;
+using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays.Rankings.Tables
{
@@ -61,18 +62,35 @@ namespace osu.Game.Overlays.Rankings.Tables
}
};
- private class CountryName : OsuSpriteText
+ private class CountryName : OsuHoverContainer
{
+ protected override IEnumerable EffectTargets => new[] { text };
+
+ [Resolved(canBeNull: true)]
+ private RankingsOverlay rankings { get; set; }
+
+ private readonly OsuSpriteText text;
+ private readonly Country country;
+
public CountryName(Country country)
{
- Font = OsuFont.GetFont(size: 12);
- Text = country.FullName ?? string.Empty;
+ this.country = country;
+
+ AutoSizeAxes = Axes.Both;
+ Add(text = new OsuSpriteText
+ {
+ Font = OsuFont.GetFont(size: 12),
+ Text = country.FullName ?? string.Empty,
+ });
}
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
- Colour = colourProvider.Light2;
+ IdleColour = colourProvider.Light2;
+ HoverColour = colourProvider.Content2;
+
+ Action = () => rankings?.ShowCountry(country);
}
}
}
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index 6f20bcf595..67fe18e8dd 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
if (HitObject is IHasComboInformation combo)
{
comboIndexBindable = combo.ComboIndexBindable.GetBoundCopy();
- comboIndexBindable.BindValueChanged(_ => updateAccentColour(), true);
+ comboIndexBindable.BindValueChanged(_ => updateComboColour(), true);
}
samplesBindable = HitObject.SamplesBindable.GetBoundCopy();
@@ -336,7 +336,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
{
base.SkinChanged(skin, allowFallback);
- updateAccentColour();
+ updateComboColour();
ApplySkin(skin, allowFallback);
@@ -344,13 +344,29 @@ namespace osu.Game.Rulesets.Objects.Drawables
updateState(State.Value, true);
}
- private void updateAccentColour()
+ private void updateComboColour()
{
- if (HitObject is IHasComboInformation combo)
- {
- var comboColours = CurrentSkin.GetConfig>(GlobalSkinColours.ComboColours)?.Value;
- AccentColour.Value = comboColours?.Count > 0 ? comboColours[combo.ComboIndex % comboColours.Count] : Color4.White;
- }
+ if (!(HitObject is IHasComboInformation)) return;
+
+ var comboColours = CurrentSkin.GetConfig>(GlobalSkinColours.ComboColours)?.Value;
+
+ AccentColour.Value = GetComboColour(comboColours);
+ }
+
+ ///
+ /// Called to retrieve the combo colour. Automatically assigned to .
+ /// Defaults to using to decide on a colour.
+ ///
+ ///
+ /// This will only be called if the implements .
+ ///
+ /// A list of combo colours provided by the beatmap or skin. Can be null if not available.
+ protected virtual Color4 GetComboColour(IReadOnlyList comboColours)
+ {
+ if (!(HitObject is IHasComboInformation combo))
+ throw new InvalidOperationException($"{nameof(HitObject)} must implement {nameof(IHasComboInformation)}");
+
+ return comboColours?.Count > 0 ? comboColours[combo.ComboIndex % comboColours.Count] : Color4.White;
}
///
diff --git a/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs b/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs
index 600fa99a9a..71cabd8b50 100644
--- a/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs
+++ b/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs
@@ -104,15 +104,13 @@ namespace osu.Game.Screens.Multi.Components
new Dimension(AutoSizeAxes.HasFlag(Axes.Y) ? GridSizeMode.AutoSize : GridSizeMode.Distributed),
};
- grid.AutoSizeAxes = Axes.None;
- grid.RelativeSizeAxes = Axes.None;
- grid.AutoSizeAxes = AutoSizeAxes;
- grid.RelativeSizeAxes = ~AutoSizeAxes;
+ // Assigning to none is done so that setting auto and relative size modes doesn't cause exceptions to be thrown
+ grid.AutoSizeAxes = Content.AutoSizeAxes = Axes.None;
+ grid.RelativeSizeAxes = Content.RelativeSizeAxes = Axes.None;
- Content.AutoSizeAxes = Axes.None;
- Content.RelativeSizeAxes = Axes.None;
- Content.AutoSizeAxes = grid.AutoSizeAxes;
- Content.RelativeSizeAxes = grid.RelativeSizeAxes;
+ // Auto-size when required, otherwise eagerly relative-size
+ grid.AutoSizeAxes = Content.AutoSizeAxes = AutoSizeAxes;
+ grid.RelativeSizeAxes = Content.RelativeSizeAxes = ~AutoSizeAxes;
}
}
}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index bd5219b872..6b8f8fee8c 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -22,7 +22,7 @@
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 2c1aff7d3c..f68bedc57f 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -73,7 +73,7 @@
-
+