diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs index 972677a6f1..89821a10fb 100644 --- a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -93,6 +93,7 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing float approxFollowCircleRadius = (float)(slider.Radius * 3); var computeVertex = new Action(t => { + // ReSharper disable once PossibleInvalidOperationException (bugged in current r# version) var diff = slider.PositionAt(t) - slider.LazyEndPosition.Value; float dist = diff.Length; diff --git a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs index cc8923976c..30a442594c 100644 --- a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs +++ b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs @@ -6,14 +6,44 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; +using osu.Framework.Allocation; using OpenTK; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; namespace osu.Game.Tests.Visual { [Description("PlaySongSelect leaderboard")] public class TestCaseLeaderboard : OsuTestCase { - private readonly Leaderboard leaderboard; + private RulesetStore rulesets; + + private readonly FailableLeaderboard leaderboard; + + public TestCaseLeaderboard() + { + Add(leaderboard = new FailableLeaderboard + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Size = new Vector2(550f, 450f), + Scope = LeaderboardScope.Global, + }); + + AddStep(@"New Scores", newScores); + AddStep(@"Empty Scores", () => leaderboard.SetRetrievalState(PlaceholderState.NoScores)); + AddStep(@"Network failure", () => leaderboard.SetRetrievalState(PlaceholderState.NetworkFailure)); + AddStep(@"No supporter", () => leaderboard.SetRetrievalState(PlaceholderState.NotSupporter)); + AddStep(@"Not logged in", () => leaderboard.SetRetrievalState(PlaceholderState.NotLoggedIn)); + AddStep(@"Real beatmap", realBeatmap); + } + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + this.rulesets = rulesets; + } private void newScores() { @@ -204,17 +234,44 @@ namespace osu.Game.Tests.Visual leaderboard.Scores = scores; } - public TestCaseLeaderboard() + private void realBeatmap() { - Add(leaderboard = new Leaderboard + leaderboard.Beatmap = new BeatmapInfo { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Size = new Vector2(550f, 450f), - }); + StarDifficulty = 1.36, + Version = @"BASIC", + OnlineBeatmapID = 1113057, + Ruleset = rulesets.GetRuleset(0), + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 4, + DrainRate = 6.5f, + OverallDifficulty = 6.5f, + ApproachRate = 5, + }, + OnlineInfo = new BeatmapOnlineInfo + { + Length = 115000, + CircleCount = 265, + SliderCount = 71, + PlayCount = 47906, + PassCount = 19899, + }, + Metrics = new BeatmapMetrics + { + Ratings = Enumerable.Range(0, 11), + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + }, + }; + } - AddStep(@"New Scores", newScores); - newScores(); + private class FailableLeaderboard : Leaderboard + { + public void SetRetrievalState(PlaceholderState state) + { + PlaceholderState = state; + } } } } diff --git a/osu.Game.Tests/Visual/TestCaseSettings.cs b/osu.Game.Tests/Visual/TestCaseSettings.cs index 13698b645f..15a60386ef 100644 --- a/osu.Game.Tests/Visual/TestCaseSettings.cs +++ b/osu.Game.Tests/Visual/TestCaseSettings.cs @@ -1,6 +1,8 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; using osu.Game.Overlays; namespace osu.Game.Tests.Visual @@ -8,16 +10,30 @@ namespace osu.Game.Tests.Visual public class TestCaseSettings : OsuTestCase { private readonly SettingsOverlay settings; + private readonly DialogOverlay dialogOverlay; + + private DependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); public TestCaseSettings() { - Children = new[] { settings = new MainSettings() }; + settings = new MainSettings + { + State = Visibility.Visible + }; + Add(dialogOverlay = new DialogOverlay + { + Depth = -1 + }); } - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load() { - base.LoadComplete(); - settings.ToggleVisibility(); + dependencies.Cache(dialogOverlay); + + Add(settings); } } } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index a1ad708304..0325785016 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -341,6 +341,61 @@ namespace osu.Game.Beatmaps } } + public void UndeleteAll() + { + var deleteMaps = QueryBeatmapSets(bs => bs.DeletePending).ToList(); + + if (!deleteMaps.Any()) return; + + var notification = new ProgressNotification + { + CompletionText = "Restored all deleted beatmaps!", + Progress = 0, + State = ProgressNotificationState.Active, + }; + + PostNotification?.Invoke(notification); + + int i = 0; + + foreach (var bs in deleteMaps) + { + if (notification.State == ProgressNotificationState.Cancelled) + // user requested abort + return; + + notification.Text = $"Restoring ({i} of {deleteMaps.Count})"; + notification.Progress = (float)++i / deleteMaps.Count; + Undelete(bs); + } + + notification.State = ProgressNotificationState.Completed; + } + + public void Undelete(BeatmapSetInfo beatmapSet) + { + if (beatmapSet.Protected) + return; + + lock (importContext) + { + var context = importContext.Value; + + using (var transaction = context.BeginTransaction()) + { + context.ChangeTracker.AutoDetectChangesEnabled = false; + + var iFiles = new FileStore(() => context, storage); + var iBeatmaps = createBeatmapStore(() => context); + + undelete(iBeatmaps, iFiles, beatmapSet); + + context.ChangeTracker.AutoDetectChangesEnabled = true; + context.SaveChanges(transaction); + } + } + } + /// /// Delete a beatmap difficulty. /// diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index 3777e10a31..065c770738 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -10,19 +10,28 @@ using osu.Game.Rulesets; using osu.Game.Users; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Select.Leaderboards; +using osu.Framework.IO.Network; namespace osu.Game.Online.API.Requests { public class GetScoresRequest : APIRequest { private readonly BeatmapInfo beatmap; + private readonly LeaderboardScope scope; + private readonly RulesetInfo ruleset; - public GetScoresRequest(BeatmapInfo beatmap) + public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, LeaderboardScope scope = LeaderboardScope.Global) { if (!beatmap.OnlineBeatmapID.HasValue) throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}."); + if (scope == LeaderboardScope.Local) + throw new InvalidOperationException("Should not attempt to request online scores for a local scoped leaderboard"); + this.beatmap = beatmap; + this.scope = scope; + this.ruleset = ruleset ?? throw new ArgumentNullException(nameof(ruleset)); Success += onSuccess; } @@ -33,6 +42,17 @@ namespace osu.Game.Online.API.Requests score.ApplyBeatmap(beatmap); } + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + + req.Timeout = 30000; + req.AddParameter(@"type", scope.ToString().ToLowerInvariant()); + req.AddParameter(@"mode", ruleset.ShortName); + + return req; + } + protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}/scores"; } diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 0d658b346e..0a88f586b5 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -104,7 +104,7 @@ namespace osu.Game.Overlays scores.IsLoading = true; - getScoresRequest = new GetScoresRequest(beatmap); + getScoresRequest = new GetScoresRequest(beatmap, beatmap.Ruleset); getScoresRequest.Success += r => { scores.Scores = r.Scores; diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index e3be23ebc6..9639907914 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -31,6 +31,8 @@ namespace osu.Game.Overlays.Mods protected readonly OsuSpriteText MultiplierLabel; private readonly FillFlowContainer footerContainer; + protected override bool BlockPassThroughKeyboard => false; + protected readonly FillFlowContainer ModSectionsContainer; public readonly Bindable> SelectedMods = new Bindable>(); diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 23bec53014..d913895159 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -13,7 +13,6 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using OpenTK; using OpenTK.Graphics; -using System.Threading; namespace osu.Game.Overlays.Music { @@ -153,11 +152,6 @@ namespace osu.Game.Overlays.Music var track = beatmapBacking.Value.Track; track.Restart(); - - // this is temporary until we have blocking (async.Wait()) audio component methods. - // then we can call RestartAsync().Wait() or the blocking version above. - while (!track.IsRunning) - Thread.Sleep(1); } } diff --git a/osu.Game/Overlays/Settings/DangerousSettingsButton.cs b/osu.Game/Overlays/Settings/DangerousSettingsButton.cs new file mode 100644 index 0000000000..7c324658a3 --- /dev/null +++ b/osu.Game/Overlays/Settings/DangerousSettingsButton.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; + +namespace osu.Game.Overlays.Settings +{ + /// + /// A with pink colours to mark dangerous/destructive actions. + /// + public class DangerousSettingsButton : SettingsButton + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BackgroundColour = colours.Pink; + + Triangles.ColourDark = colours.PinkDark; + Triangles.ColourLight = colours.PinkLight; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs new file mode 100644 index 0000000000..8ae520651a --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Graphics; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Overlays.Settings.Sections.Maintenance +{ + public class DeleteAllBeatmapsDialog : PopupDialog + { + public DeleteAllBeatmapsDialog(Action deleteAction) + { + BodyText = "Everything?"; + + Icon = FontAwesome.fa_trash_o; + HeaderText = @"Confirm deletion of"; + Buttons = new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = @"Yes. Go for it.", + Action = deleteAction + }, + new PopupDialogCancelButton + { + Text = @"No! Abort mission!", + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 953e9a54ae..e288445c6d 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -15,12 +15,12 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private TriangleButton importButton; private TriangleButton deleteButton; private TriangleButton restoreButton; - private TriangleButton migrateButton; + private TriangleButton undeleteButton; protected override string Header => "General"; [BackgroundDependencyLoader] - private void load(BeatmapManager beatmaps) + private void load(BeatmapManager beatmaps, DialogOverlay dialogOverlay) { Children = new Drawable[] { @@ -34,13 +34,16 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance .ContinueWith(t => Schedule(() => importButton.Enabled.Value = true), TaskContinuationOptions.LongRunning); } }, - deleteButton = new SettingsButton + deleteButton = new DangerousSettingsButton { Text = "Delete ALL beatmaps", Action = () => { - deleteButton.Enabled.Value = false; - Task.Run(() => beatmaps.DeleteAll()).ContinueWith(t => Schedule(() => deleteButton.Enabled.Value = true)); + dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() => + { + deleteButton.Enabled.Value = false; + Task.Run(() => beatmaps.DeleteAll()).ContinueWith(t => Schedule(() => deleteButton.Enabled.Value = true)); + })); } }, restoreButton = new SettingsButton @@ -55,7 +58,16 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance beatmaps.Restore(b); }).ContinueWith(t => Schedule(() => restoreButton.Enabled.Value = true)); } - } + }, + undeleteButton = new SettingsButton + { + Text = "Restore all recently deleted beatmaps", + Action = () => + { + undeleteButton.Enabled.Value = false; + Task.Run(() => beatmaps.UndeleteAll()).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true)); + } + }, }; } } diff --git a/osu.Game/Overlays/WaveOverlayContainer.cs b/osu.Game/Overlays/WaveOverlayContainer.cs index 50150b6660..3f9703a523 100644 --- a/osu.Game/Overlays/WaveOverlayContainer.cs +++ b/osu.Game/Overlays/WaveOverlayContainer.cs @@ -28,6 +28,8 @@ namespace osu.Game.Overlays private readonly Container contentContainer; + protected override bool BlockPassThroughKeyboard => true; + protected override Container Content => contentContainer; protected Color4 FirstWaveColour diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e9992f2df7..340fc39d52 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -17,7 +17,6 @@ using osu.Game.Rulesets.UI; using osu.Game.Screens.Backgrounds; using System; using System.Linq; -using System.Threading; using System.Threading.Tasks; using osu.Framework.Threading; using osu.Game.Rulesets.Mods; @@ -327,11 +326,6 @@ namespace osu.Game.Screens.Play { adjustableSourceClock.Reset(); - // this is temporary until we have blocking (async.Wait()) audio component methods. - // then we can call ResetAsync().Wait() or the blocking version above. - while (adjustableSourceClock.IsRunning) - Thread.Sleep(1); - Schedule(() => { decoupledClock.ChangeSource(adjustableSourceClock); diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs index a676516300..4403d412fc 100644 --- a/osu.Game/Screens/Select/BeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs @@ -52,6 +52,7 @@ namespace osu.Game.Screens.Select default: Details.Hide(); + Leaderboard.Scope = (LeaderboardScope)tab - 1; Leaderboard.Show(); break; } diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index d896da5319..b3f2649ab6 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -18,14 +18,26 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using System.Linq; +using osu.Framework.Configuration; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Framework.Logging; +using osu.Game.Rulesets; +using osu.Framework.Input; namespace osu.Game.Screens.Select.Leaderboards { public class Leaderboard : Container { + private const double fade_duration = 200; + private readonly ScrollContainer scrollContainer; + private readonly Container placeholderContainer; + private FillFlowContainer scrollFlow; + private readonly Bindable ruleset = new Bindable(); + public Action ScoreSelected; private readonly LoadingAnimation loading; @@ -38,15 +50,18 @@ namespace osu.Game.Screens.Select.Leaderboards set { scores = value; - getScoresRequest?.Cancel(); - scrollFlow?.FadeOut(200); - scrollFlow?.Expire(); + scrollFlow?.FadeOut(fade_duration).Expire(); scrollFlow = null; - if (scores == null) + loading.Hide(); + + if (scores == null || !scores.Any()) return; + // ensure placeholder is hidden when displaying scores + PlaceholderState = PlaceholderState.Successful; + // schedule because we may not be loaded yet (LoadComponentAsync complains). Schedule(() => { @@ -74,6 +89,55 @@ namespace osu.Game.Screens.Select.Leaderboards } } + private LeaderboardScope scope; + public LeaderboardScope Scope + { + get { return scope; } + set + { + if (value == scope) return; + + scope = value; + updateScores(); + } + } + + private PlaceholderState placeholderState; + protected PlaceholderState PlaceholderState + { + get { return placeholderState; } + set + { + if (value == placeholderState) return; + + switch (placeholderState = value) + { + case PlaceholderState.NetworkFailure: + replacePlaceholder(new RetrievalFailurePlaceholder + { + OnRetry = updateScores, + }); + break; + + case PlaceholderState.NoScores: + replacePlaceholder(new MessagePlaceholder(@"No records yet!")); + break; + + case PlaceholderState.NotLoggedIn: + replacePlaceholder(new MessagePlaceholder(@"Please login to view online leaderboards!")); + break; + + case PlaceholderState.NotSupporter: + replacePlaceholder(new MessagePlaceholder(@"Please invest in a supporter tag to view this leaderboard!")); + break; + + default: + replacePlaceholder(null); + break; + } + } + } + public Leaderboard() { Children = new Drawable[] @@ -83,7 +147,14 @@ namespace osu.Game.Screens.Select.Leaderboards RelativeSizeAxes = Axes.Both, ScrollbarVisible = false, }, - loading = new LoadingAnimation() + loading = new LoadingAnimation(), + placeholderContainer = new Container + { + Alpha = 0, + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, }; } @@ -91,6 +162,8 @@ namespace osu.Game.Screens.Select.Leaderboards private BeatmapInfo beatmap; + private OsuGame osuGame; + private ScheduledDelegate pendingBeatmapSwitch; public BeatmapInfo Beatmap @@ -109,33 +182,115 @@ namespace osu.Game.Screens.Select.Leaderboards } [BackgroundDependencyLoader(permitNulls: true)] - private void load(APIAccess api) + private void load(APIAccess api, OsuGame osuGame) { this.api = api; + this.osuGame = osuGame; + + if (osuGame != null) + ruleset.BindTo(osuGame.Ruleset); + + ruleset.ValueChanged += r => updateScores(); + + if (api != null) + api.OnStateChange += handleApiStateChange; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (api != null) + api.OnStateChange -= handleApiStateChange; } private GetScoresRequest getScoresRequest; + private void handleApiStateChange(APIState oldState, APIState newState) + { + if (Scope == LeaderboardScope.Local) + // No need to respond to API state change while current scope is local + return; + + if (newState == APIState.Online) + updateScores(); + } + private void updateScores() { - if (!IsLoaded) return; - - Scores = null; getScoresRequest?.Cancel(); + getScoresRequest = null; + Scores = null; - if (api == null || Beatmap?.OnlineBeatmapID == null) return; + if (Scope == LeaderboardScope.Local) + { + // TODO: get local scores from wherever here. + PlaceholderState = PlaceholderState.NoScores; + return; + } + if (api?.IsLoggedIn != true) + { + PlaceholderState = PlaceholderState.NotLoggedIn; + return; + } + + if (Beatmap?.OnlineBeatmapID == null) + { + PlaceholderState = PlaceholderState.NetworkFailure; + return; + } + + PlaceholderState = PlaceholderState.Retrieving; loading.Show(); - getScoresRequest = new GetScoresRequest(Beatmap); + if (Scope != LeaderboardScope.Global && !api.LocalUser.Value.IsSupporter) + { + loading.Hide(); + PlaceholderState = PlaceholderState.NotSupporter; + return; + } + + getScoresRequest = new GetScoresRequest(Beatmap, osuGame?.Ruleset.Value ?? Beatmap.Ruleset, Scope); getScoresRequest.Success += r => { Scores = r.Scores; - loading.Hide(); + PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; }; + getScoresRequest.Failure += onUpdateFailed; + api.Queue(getScoresRequest); } + private void onUpdateFailed(Exception e) + { + if (e is OperationCanceledException) return; + + PlaceholderState = PlaceholderState.NetworkFailure; + Logger.Error(e, @"Couldn't fetch beatmap scores!"); + } + + private void replacePlaceholder(Placeholder placeholder) + { + if (placeholder == null) + { + placeholderContainer.FadeOutFromOne(fade_duration, Easing.OutQuint); + placeholderContainer.Clear(true); + return; + } + + var existingPlaceholder = placeholderContainer.Children.FirstOrDefault() as Placeholder; + + if (placeholder.Equals(existingPlaceholder)) + return; + + Scores = null; + + placeholderContainer.Clear(true); + placeholderContainer.Child = placeholder; + placeholderContainer.FadeInFromZero(fade_duration, Easing.OutQuint); + } + protected override void Update() { base.Update(); @@ -164,5 +319,118 @@ namespace osu.Game.Screens.Select.Leaderboards } } } + + private abstract class Placeholder : FillFlowContainer, IEquatable + { + public virtual bool Equals(Placeholder other) => GetType() == other?.GetType(); + } + + private class MessagePlaceholder : Placeholder + { + private readonly string message; + + public MessagePlaceholder(string message) + { + Direction = FillDirection.Horizontal; + AutoSizeAxes = Axes.Both; + Children = new Drawable[] + { + new SpriteIcon + { + Icon = FontAwesome.fa_exclamation_circle, + Size = new Vector2(26), + Margin = new MarginPadding { Right = 10 }, + }, + new OsuSpriteText + { + Text = this.message = message, + TextSize = 22, + }, + }; + } + + public override bool Equals(Placeholder other) => (other as MessagePlaceholder)?.message == message; + } + + private class RetrievalFailurePlaceholder : Placeholder + { + public Action OnRetry; + + public RetrievalFailurePlaceholder() + { + Direction = FillDirection.Horizontal; + AutoSizeAxes = Axes.Both; + Children = new Drawable[] + { + new RetryButton + { + Action = () => OnRetry?.Invoke(), + Margin = new MarginPadding { Right = 10 }, + }, + new OsuSpriteText + { + Anchor = Anchor.TopLeft, + Text = @"Couldn't retrieve scores!", + TextSize = 22, + }, + }; + } + + private class RetryButton : OsuHoverContainer + { + private readonly SpriteIcon icon; + + public Action Action; + + public RetryButton() + { + Height = 26; + Width = 26; + Child = new OsuClickableContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Action = () => Action?.Invoke(), + Child = icon = new SpriteIcon + { + Icon = FontAwesome.fa_refresh, + Size = new Vector2(26), + Shadow = true, + }, + }; + } + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + icon.ScaleTo(0.8f, 4000, Easing.OutQuint); + return base.OnMouseDown(state, args); + } + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + { + icon.ScaleTo(1, 1000, Easing.OutElastic); + return base.OnMouseUp(state, args); + } + } + } + } + + public enum LeaderboardScope + { + Local, + Country, + Global, + Friend, + } + + public enum PlaceholderState + { + Successful, + Retrieving, + NetworkFailure, + NoScores, + NotLoggedIn, + NotSupporter, } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 4d5101447a..68ee08e721 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -275,7 +275,7 @@ namespace osu.Game.Screens.Select if (beatmap == Beatmap.Value.BeatmapInfo) performLoad(); else - selectionChangedDebounce = Scheduler.AddDelayed(performLoad, 100); + selectionChangedDebounce = Scheduler.AddDelayed(performLoad, 200); } } diff --git a/osu.Game/Tests/Visual/TestCasePerformancePoints.cs b/osu.Game/Tests/Visual/TestCasePerformancePoints.cs index 6da14e9b12..f71bece279 100644 --- a/osu.Game/Tests/Visual/TestCasePerformancePoints.cs +++ b/osu.Game/Tests/Visual/TestCasePerformancePoints.cs @@ -244,7 +244,7 @@ namespace osu.Game.Tests.Visual if (!api.IsLoggedIn) return; - lastRequest = new GetScoresRequest(newBeatmap.BeatmapInfo); + lastRequest = new GetScoresRequest(newBeatmap.BeatmapInfo, newBeatmap.BeatmapInfo.Ruleset); lastRequest.Success += res => res.Scores.ForEach(s => scores.Add(new PerformanceDisplay(s, newBeatmap.Beatmap))); api.Queue(lastRequest); } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 223dd77b04..1241017b9b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -268,6 +268,7 @@ + @@ -308,6 +309,7 @@ +