diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index d47ed48e99..de3acf7a71 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -4,6 +4,7 @@
using System;
using osu.Framework.Configuration;
using osu.Framework.Platform;
+using osu.Game.Screens.Select;
namespace osu.Game.Configuration
{
@@ -34,6 +35,8 @@ namespace osu.Game.Configuration
Set(OsuConfig.MenuParallax, true);
+ Set(OsuConfig.BeatmapDetailTab, BeatmapDetailTab.Details);
+
Set(OsuConfig.ShowInterface, true);
Set(OsuConfig.KeyOverlay, false);
//todo: implement all settings below this line (remove the Disabled set when doing so).
@@ -316,6 +319,7 @@ namespace osu.Game.Configuration
MenuMusic,
MenuVoice,
MenuParallax,
+ BeatmapDetailTab,
RawInput,
AbsoluteToOsuWindow,
ConfineMouse,
diff --git a/osu.Game/Database/BeatmapMetrics.cs b/osu.Game/Database/BeatmapMetrics.cs
index 91320110d0..25de0f0a8d 100644
--- a/osu.Game/Database/BeatmapMetrics.cs
+++ b/osu.Game/Database/BeatmapMetrics.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
+using Newtonsoft.Json;
namespace osu.Game.Database
{
@@ -18,11 +19,13 @@ namespace osu.Game.Database
///
/// Points of failure on a relative time scale (usually 0..100).
///
+ [JsonProperty(@"fail")]
public IEnumerable Fails { get; set; }
///
/// Points of retry on a relative time scale (usually 0..100).
///
+ [JsonProperty(@"exit")]
public IEnumerable Retries { get; set; }
}
}
diff --git a/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.cs
new file mode 100644
index 0000000000..43e14e59de
--- /dev/null
+++ b/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.cs
@@ -0,0 +1,46 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using Newtonsoft.Json;
+using osu.Game.Database;
+
+namespace osu.Game.Online.API.Requests
+{
+ public class GetBeatmapDetailsRequest : APIRequest
+ {
+ private readonly BeatmapInfo beatmap;
+
+ private string lookupString => beatmap.OnlineBeatmapID > 0 ? beatmap.OnlineBeatmapID.ToString() : $@"lookup?checksum={beatmap.Hash}&filename={beatmap.Path}";
+
+ public GetBeatmapDetailsRequest(BeatmapInfo beatmap)
+ {
+ this.beatmap = beatmap;
+ }
+
+ protected override string Target => $@"beatmaps/{lookupString}";
+ }
+
+ public class GetBeatmapDeatilsResponse : BeatmapMetrics
+ {
+ //the online API returns some metrics as a nested object.
+ [JsonProperty(@"failtimes")]
+ private BeatmapMetrics failTimes
+ {
+ set
+ {
+ Fails = value.Fails;
+ Retries = value.Retries;
+ }
+ }
+
+ //and other metrics in the beatmap set.
+ [JsonProperty(@"beatmapset")]
+ private BeatmapMetrics beatmapSet
+ {
+ set
+ {
+ Ratings = value.Ratings;
+ }
+ }
+ }
+}
diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs
index ae117254fa..cc22cca8bf 100644
--- a/osu.Game/Screens/Select/BeatmapDetailArea.cs
+++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Screens.Select
{
beatmap = value;
Leaderboard.Beatmap = beatmap?.BeatmapInfo;
- Details.Beatmap = beatmap?.Beatmap.BeatmapInfo;
+ Details.Beatmap = beatmap?.BeatmapInfo;
}
}
@@ -66,16 +66,16 @@ namespace osu.Game.Screens.Select
{
Details = new BeatmapDetails
{
- RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding(5),
+ RelativeSizeAxes = Axes.X,
+ Masking = true,
+ Height = 352,
Alpha = 0,
},
Leaderboard = new Leaderboard
{
RelativeSizeAxes = Axes.Both,
-
}
});
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs
index 48a46f0b90..51ec6f7707 100644
--- a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs
+++ b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs
@@ -4,10 +4,12 @@
using System;
using OpenTK.Graphics;
using osu.Framework.Allocation;
+using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
+using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
@@ -21,15 +23,22 @@ namespace osu.Game.Screens.Select
public Action OnFilter; //passed the selected tab and if mods is checked
+ private Bindable selectedTab;
+
private void invokeOnFilter()
{
OnFilter?.Invoke(tabs.Current, modsCheckbox.Current);
}
[BackgroundDependencyLoader]
- private void load(OsuColour colour)
+ private void load(OsuColour colour, OsuConfigManager config)
{
modsCheckbox.AccentColour = tabs.AccentColour = colour.YellowLight;
+
+ selectedTab = config.GetBindable(OsuConfig.BeatmapDetailTab);
+
+ tabs.Current.BindTo(selectedTab);
+ tabs.Current.TriggerChange();
}
public BeatmapDetailAreaTabControl()
@@ -62,8 +71,6 @@ namespace osu.Game.Screens.Select
tabs.Current.ValueChanged += item => invokeOnFilter();
modsCheckbox.Current.ValueChanged += item => invokeOnFilter();
-
- tabs.Current.Value = BeatmapDetailTab.Global;
}
}
diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs
index a0d15101e0..63fdfe3717 100644
--- a/osu.Game/Screens/Select/BeatmapDetails.cs
+++ b/osu.Game/Screens/Select/BeatmapDetails.cs
@@ -14,6 +14,9 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using System.Globalization;
using System.Linq;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Framework.Threading;
namespace osu.Game.Screens.Select
{
@@ -39,60 +42,108 @@ namespace osu.Game.Screens.Select
private readonly BarGraph retryGraph;
private readonly BarGraph failGraph;
+ private ScheduledDelegate pendingBeatmapSwitch;
private BeatmapInfo beatmap;
+
public BeatmapInfo Beatmap
{
- get
- {
- return beatmap;
- }
+ get { return beatmap; }
set
{
beatmap = value;
- if (beatmap == null) return;
- description.Text = beatmap.Version;
- source.Text = beatmap.Metadata.Source;
- tags.Text = beatmap.Metadata.Tags;
-
- circleSize.Value = beatmap.Difficulty.CircleSize;
- drainRate.Value = beatmap.Difficulty.DrainRate;
- overallDifficulty.Value = beatmap.Difficulty.OverallDifficulty;
- approachRate.Value = beatmap.Difficulty.ApproachRate;
- stars.Value = (float)beatmap.StarDifficulty;
-
- if (beatmap.Metrics?.Ratings.Any() ?? false)
- {
- var ratings = beatmap.Metrics.Ratings.ToList();
- ratingsContainer.Show();
-
- negativeRatings.Text = ratings.GetRange(0, ratings.Count / 2).Sum().ToString();
- positiveRatings.Text = ratings.GetRange(ratings.Count / 2, ratings.Count / 2).Sum().ToString();
- ratingsBar.Length = (float)ratings.GetRange(0, ratings.Count / 2).Sum() / ratings.Sum();
-
- ratingsGraph.Values = ratings.Select(rating => (float)rating);
- }
- else
- ratingsContainer.Hide();
-
- if ((beatmap.Metrics?.Retries.Any() ?? false) && beatmap.Metrics.Fails.Any())
- {
- var retries = beatmap.Metrics.Retries;
- var fails = beatmap.Metrics.Fails;
- retryFailContainer.Show();
-
- float maxValue = fails.Zip(retries, (fail, retry) => fail + retry).Max();
- failGraph.MaxValue = maxValue;
- retryGraph.MaxValue = maxValue;
-
- failGraph.Values = fails.Select(fail => (float)fail);
- retryGraph.Values = retries.Zip(fails, (retry, fail) => retry + MathHelper.Clamp(fail, 0, maxValue));
- }
- else
- retryFailContainer.Hide();
+ pendingBeatmapSwitch?.Cancel();
+ pendingBeatmapSwitch = Schedule(updateStats);
}
}
+ private void updateStats()
+ {
+ if (beatmap == null) return;
+
+ description.Text = beatmap.Version;
+ source.Text = beatmap.Metadata.Source;
+ tags.Text = beatmap.Metadata.Tags;
+
+ circleSize.Value = beatmap.Difficulty.CircleSize;
+ drainRate.Value = beatmap.Difficulty.DrainRate;
+ overallDifficulty.Value = beatmap.Difficulty.OverallDifficulty;
+ approachRate.Value = beatmap.Difficulty.ApproachRate;
+ stars.Value = (float)beatmap.StarDifficulty;
+
+ var requestedBeatmap = beatmap;
+ if (requestedBeatmap.Metrics == null)
+ {
+ var lookup = new GetBeatmapDetailsRequest(requestedBeatmap);
+ lookup.Success += res =>
+ {
+ if (beatmap != requestedBeatmap)
+ //the beatmap has been changed since we started the lookup.
+ return;
+
+ requestedBeatmap.Metrics = res;
+ Schedule(() => updateMetrics(res));
+ };
+ lookup.Failure += e => updateMetrics(null);
+
+ api.Queue(lookup);
+ }
+
+ updateMetrics(requestedBeatmap.Metrics, false);
+ }
+
+ ///
+ /// Update displayed metrics.
+ ///
+ /// New metrics to overwrite the existing display. Can be null.
+ /// Whether to hide the display on null or empty metrics. If false, we will dim as if waiting for further updates.
+ private void updateMetrics(BeatmapMetrics metrics, bool failOnMissing = true)
+ {
+ var hasRatings = metrics?.Ratings.Any() ?? false;
+ var hasRetriesFails = (metrics?.Retries.Any() ?? false) && metrics.Fails.Any();
+
+ if (hasRatings)
+ {
+ var ratings = metrics.Ratings.ToList();
+ ratingsContainer.Show();
+
+ negativeRatings.Text = ratings.GetRange(0, ratings.Count / 2).Sum().ToString();
+ positiveRatings.Text = ratings.GetRange(ratings.Count / 2, ratings.Count / 2).Sum().ToString();
+ ratingsBar.Length = (float)ratings.GetRange(0, ratings.Count / 2).Sum() / ratings.Sum();
+
+ ratingsGraph.Values = ratings.Select(rating => (float)rating);
+
+ ratingsContainer.FadeColour(Color4.White, 500, EasingTypes.Out);
+ }
+ else if (failOnMissing)
+ ratingsGraph.Values = new float[10];
+ else
+ ratingsContainer.FadeColour(Color4.Gray, 500, EasingTypes.Out);
+
+ if (hasRetriesFails)
+ {
+ var retries = metrics.Retries;
+ var fails = metrics.Fails;
+ retryFailContainer.Show();
+
+ float maxValue = fails.Zip(retries, (fail, retry) => fail + retry).Max();
+ failGraph.MaxValue = maxValue;
+ retryGraph.MaxValue = maxValue;
+
+ failGraph.Values = fails.Select(fail => (float)fail);
+ retryGraph.Values = retries.Zip(fails, (retry, fail) => retry + MathHelper.Clamp(fail, 0, maxValue));
+
+ retryFailContainer.FadeColour(Color4.White, 500, EasingTypes.Out);
+ }
+ else if (failOnMissing)
+ {
+ failGraph.Values = new float[100];
+ retryGraph.Values = new float[100];
+ }
+ else
+ retryFailContainer.FadeColour(Color4.Gray, 500, EasingTypes.Out);
+ }
+
public BeatmapDetails()
{
Children = new Drawable[]
@@ -113,7 +164,6 @@ namespace osu.Game.Screens.Select
Direction = FillDirection.Vertical,
LayoutDuration = 200,
LayoutEasing = EasingTypes.OutQuint,
- Padding = new MarginPadding(10) { Top = 25 },
Children = new []
{
description = new MetadataSegment("Description"),
@@ -148,8 +198,8 @@ namespace osu.Game.Screens.Select
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
- Spacing = new Vector2(0,10),
- Padding = new MarginPadding(15) { Top = 25 },
+ Spacing = new Vector2(0,5),
+ Padding = new MarginPadding(10),
Children = new []
{
circleSize = new DifficultyRow("Circle Size", 7),
@@ -252,7 +302,7 @@ namespace osu.Game.Screens.Select
new Container
{
RelativeSizeAxes = Axes.X,
- Size = new Vector2(1/0.6f, 50),
+ Size = new Vector2(1 / 0.6f, 50),
Children = new[]
{
retryGraph = new BarGraph
@@ -272,9 +322,13 @@ namespace osu.Game.Screens.Select
};
}
+ private APIAccess api;
+
[BackgroundDependencyLoader]
- private void load(OsuColour colour)
+ private void load(OsuColour colour, APIAccess api)
{
+ this.api = api;
+
description.AccentColour = colour.GrayB;
source.AccentColour = colour.GrayB;
tags.AccentColour = colour.YellowLight;
@@ -308,7 +362,7 @@ namespace osu.Game.Screens.Select
{
difficultyValue = value;
bar.Length = value / maxValue;
- valueText.Text = value.ToString(CultureInfo.InvariantCulture);
+ valueText.Text = value.ToString("N1", CultureInfo.CurrentCulture);
}
}
@@ -431,4 +485,4 @@ namespace osu.Game.Screens.Select
}
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs
index 2c51429d4c..02a412685c 100644
--- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs
+++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs
@@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using System;
using osu.Framework.Allocation;
+using osu.Framework.Threading;
using osu.Game.Database;
using osu.Game.Rulesets.Scoring;
using osu.Game.Online.API;
@@ -93,13 +94,18 @@ namespace osu.Game.Screens.Select.Leaderboards
private BeatmapInfo beatmap;
+ private ScheduledDelegate pendingBeatmapSwitch;
+
public BeatmapInfo Beatmap
{
get { return beatmap; }
set
{
beatmap = value;
- Schedule(updateScores);
+ Scores = null;
+
+ pendingBeatmapSwitch?.Cancel();
+ pendingBeatmapSwitch = Schedule(updateScores);
}
}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index c9a3b08713..c32c5112cf 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -179,6 +179,7 @@
+