osu/osu.Game/Screens/Select/BeatmapDetails.cs

491 lines
20 KiB
C#
Raw Normal View History

2017-03-24 22:02:24 +00:00
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
2017-03-25 22:33:03 +00:00
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
2017-03-24 22:02:24 +00:00
using osu.Framework.Graphics;
2017-03-25 22:33:03 +00:00
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Database;
2017-03-24 22:02:24 +00:00
using osu.Game.Graphics;
2017-04-01 16:12:44 +00:00
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
2017-03-29 14:10:07 +00:00
using System.Globalization;
2017-03-25 22:33:03 +00:00
using System.Linq;
2017-04-24 10:17:11 +00:00
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Framework.Threading;
2017-03-24 22:02:24 +00:00
namespace osu.Game.Screens.Select
2017-03-24 22:02:24 +00:00
{
public class BeatmapDetails : Container
2017-03-24 22:02:24 +00:00
{
private readonly MetadataSegment description;
private readonly MetadataSegment source;
private readonly MetadataSegment tags;
2017-03-25 22:33:03 +00:00
private readonly DifficultyRow circleSize;
private readonly DifficultyRow drainRate;
private readonly DifficultyRow overallDifficulty;
private readonly DifficultyRow approachRate;
private readonly DifficultyRow stars;
2017-03-25 22:33:03 +00:00
private readonly Container ratingsContainer;
private readonly Bar ratingsBar;
2017-04-01 16:12:44 +00:00
private readonly OsuSpriteText negativeRatings;
private readonly OsuSpriteText positiveRatings;
private readonly BarGraph ratingsGraph;
2017-04-11 12:02:56 +00:00
private readonly FillFlowContainer retryFailContainer;
private readonly BarGraph retryGraph;
private readonly BarGraph failGraph;
2017-03-28 18:18:56 +00:00
2017-04-24 10:17:11 +00:00
private ScheduledDelegate pendingBeatmapSwitch;
2017-03-25 22:33:03 +00:00
private BeatmapInfo beatmap;
2017-04-24 10:17:11 +00:00
2017-03-25 22:33:03 +00:00
public BeatmapInfo Beatmap
2017-03-24 22:02:24 +00:00
{
2017-06-07 14:00:14 +00:00
get { return beatmap; }
2017-06-07 11:53:37 +00:00
2017-03-24 22:02:24 +00:00
set
{
if (beatmap == value) return;
2017-03-25 22:33:03 +00:00
beatmap = value;
2017-04-24 10:17:11 +00:00
pendingBeatmapSwitch?.Cancel();
pendingBeatmapSwitch = Schedule(updateStats);
}
}
2017-04-24 10:17:11 +00:00
private void updateStats()
{
2017-04-24 10:25:35 +00:00
if (beatmap == null) return;
2017-04-24 10:17:11 +00:00
description.Text = beatmap.Version;
source.Text = beatmap.Metadata.Source;
tags.Text = beatmap.Metadata.Tags;
2017-03-25 22:33:03 +00:00
2017-04-24 10:17:11 +00:00
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;
2017-03-24 22:02:24 +00:00
2017-04-24 10:17:11 +00:00
var requestedBeatmap = beatmap;
if (requestedBeatmap.Metrics == null)
{
2017-04-24 10:33:48 +00:00
var lookup = new GetBeatmapDetailsRequest(requestedBeatmap);
2017-04-24 10:17:11 +00:00
lookup.Success += res =>
{
2017-04-24 10:17:11 +00:00
if (beatmap != requestedBeatmap)
//the beatmap has been changed since we started the lookup.
return;
2017-04-11 12:02:56 +00:00
2017-04-24 10:17:11 +00:00
requestedBeatmap.Metrics = res;
2017-04-24 11:16:31 +00:00
Schedule(() => updateMetrics(res));
2017-04-24 10:17:11 +00:00
};
2017-04-24 11:16:31 +00:00
lookup.Failure += e => updateMetrics(null);
2017-04-24 10:17:11 +00:00
api.Queue(lookup);
}
2017-04-11 16:43:48 +00:00
2017-04-24 10:17:11 +00:00
updateMetrics(requestedBeatmap.Metrics, false);
}
2017-04-11 12:12:23 +00:00
/// <summary>
/// Update displayed metrics.
/// </summary>
/// <param name="metrics">New metrics to overwrite the existing display. Can be null.</param>
/// <param name="failOnMissing">Whether to hide the display on null or empty metrics. If false, we will dim as if waiting for further updates.</param>
2017-04-24 10:17:11 +00:00
private void updateMetrics(BeatmapMetrics metrics, bool failOnMissing = true)
{
var hasRatings = metrics?.Ratings.Any() ?? false;
var hasRetriesFails = (metrics?.Retries.Any() ?? false) && metrics.Fails.Any();
2017-04-11 12:12:23 +00:00
2017-04-24 10:17:11 +00:00
if (hasRatings)
{
var ratings = metrics.Ratings.ToList();
ratingsContainer.Show();
2017-04-11 12:12:23 +00:00
2017-04-24 10:17:11 +00:00
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();
2017-04-11 12:12:23 +00:00
2017-04-24 10:17:11 +00:00
ratingsGraph.Values = ratings.Select(rating => (float)rating);
ratingsContainer.FadeColour(Color4.White, 500, EasingTypes.Out);
}
2017-04-24 10:17:11 +00:00
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);
2017-03-28 18:18:56 +00:00
}
public BeatmapDetails()
2017-03-24 22:02:24 +00:00
{
2017-03-25 22:33:03 +00:00
Children = new Drawable[]
2017-03-24 22:02:24 +00:00
{
2017-03-25 22:33:03 +00:00
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = 0.5f,
},
new FillFlowContainer<MetadataSegment>()
2017-03-24 22:02:24 +00:00
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Width = 0.4f,
Direction = FillDirection.Vertical,
LayoutDuration = 200,
LayoutEasing = EasingTypes.OutQuint,
2017-06-07 11:53:37 +00:00
Children = new[]
2017-03-24 22:02:24 +00:00
{
2017-04-10 14:42:23 +00:00
description = new MetadataSegment("Description"),
source = new MetadataSegment("Source"),
tags = new MetadataSegment("Tags")
2017-03-24 22:02:24 +00:00
},
2017-03-25 22:33:03 +00:00
},
new FillFlowContainer
2017-03-25 22:33:03 +00:00
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Width = 0.6f,
Direction = FillDirection.Vertical,
2017-04-10 14:42:23 +00:00
Spacing = new Vector2(0, 15),
Padding = new MarginPadding(10) { Top = 0 },
Children = new Drawable[]
2017-03-25 22:33:03 +00:00
{
new Container
2017-03-25 22:33:03 +00:00
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
2017-03-25 22:33:03 +00:00
{
new Box
2017-03-25 22:33:03 +00:00
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = 0.5f,
2017-03-25 22:33:03 +00:00
},
new FillFlowContainer
2017-03-25 22:33:03 +00:00
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
2017-06-07 11:53:37 +00:00
Spacing = new Vector2(0, 5),
Padding = new MarginPadding(10),
2017-06-07 11:53:37 +00:00
Children = new[]
{
2017-04-10 14:42:23 +00:00
circleSize = new DifficultyRow("Circle Size", 7),
drainRate = new DifficultyRow("HP Drain"),
overallDifficulty = new DifficultyRow("Accuracy"),
approachRate = new DifficultyRow("Approach Rate"),
2017-05-16 20:54:33 +00:00
stars = new DifficultyRow("Star Difficulty"),
},
2017-03-25 22:33:03 +00:00
},
},
},
ratingsContainer = new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Alpha = 0,
AlwaysPresent = true,
Children = new Drawable[]
{
new Box
2017-03-25 22:33:03 +00:00
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = 0.5f,
2017-03-25 22:33:03 +00:00
},
new FillFlowContainer
2017-03-25 22:33:03 +00:00
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
2017-04-12 08:52:24 +00:00
Padding = new MarginPadding
{
Top = 25,
Left = 15,
Right = 15,
},
Children = new Drawable[]
{
2017-04-01 16:12:44 +00:00
new OsuSpriteText
{
Text = "User Rating",
Font = @"Exo2.0-Medium",
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
},
ratingsBar = new Bar
{
RelativeSizeAxes = Axes.X,
Height = 5,
},
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new[]
{
2017-04-01 16:12:44 +00:00
negativeRatings = new OsuSpriteText
{
2017-04-03 21:03:49 +00:00
Font = @"Exo2.0-Regular",
Text = "0",
},
2017-04-01 16:12:44 +00:00
positiveRatings = new OsuSpriteText
{
2017-04-03 21:03:49 +00:00
Font = @"Exo2.0-Regular",
Text = "0",
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
},
},
},
2017-04-01 16:12:44 +00:00
new OsuSpriteText
{
Text = "Rating Spread",
TextSize = 14,
2017-04-03 21:03:49 +00:00
Font = @"Exo2.0-Regular",
2017-03-28 18:18:56 +00:00
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
},
ratingsGraph = new BarGraph
{
RelativeSizeAxes = Axes.X,
Height = 50,
2017-03-28 18:18:56 +00:00
},
},
2017-03-25 22:33:03 +00:00
},
},
},
2017-04-11 12:02:56 +00:00
retryFailContainer = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Alpha = 0,
Children = new Drawable[]
{
new OsuSpriteText
{
Text = "Points of Failure",
2017-04-10 14:42:23 +00:00
Font = @"Exo2.0-Regular",
},
new Container<BarGraph>
{
RelativeSizeAxes = Axes.X,
Size = new Vector2(1 / 0.6f, 50),
Children = new[]
{
retryGraph = new BarGraph
{
RelativeSizeAxes = Axes.Both,
},
failGraph = new BarGraph
{
RelativeSizeAxes = Axes.Both,
},
},
},
}
2017-03-28 18:18:56 +00:00
},
},
2017-04-10 14:42:23 +00:00
}
2017-03-24 22:02:24 +00:00
};
}
2017-04-24 10:17:11 +00:00
private APIAccess api;
2017-03-24 22:02:24 +00:00
[BackgroundDependencyLoader]
2017-04-24 10:17:11 +00:00
private void load(OsuColour colour, APIAccess api)
2017-03-24 22:02:24 +00:00
{
2017-04-24 10:17:11 +00:00
this.api = api;
description.AccentColour = colour.GrayB;
source.AccentColour = colour.GrayB;
tags.AccentColour = colour.YellowLight;
stars.AccentColour = colour.Yellow;
ratingsBar.BackgroundColour = colour.Green;
2017-04-08 11:53:11 +00:00
ratingsBar.AccentColour = colour.YellowDark;
ratingsGraph.Colour = colour.BlueDark;
failGraph.Colour = colour.YellowDarker;
retryGraph.Colour = colour.Yellow;
2017-03-25 22:33:03 +00:00
}
private class DifficultyRow : Container, IHasAccentColour
2017-03-25 22:33:03 +00:00
{
2017-04-01 16:12:44 +00:00
private readonly OsuSpriteText name;
private readonly Bar bar;
2017-04-01 16:12:44 +00:00
private readonly OsuSpriteText valueText;
2017-03-25 22:33:03 +00:00
2017-04-10 14:42:23 +00:00
private readonly float maxValue;
2017-04-04 15:27:08 +00:00
2017-04-10 14:42:23 +00:00
private float difficultyValue;
2017-03-25 22:33:03 +00:00
public float Value
{
get
{
return difficultyValue;
}
set
{
difficultyValue = value;
2017-04-04 15:27:08 +00:00
bar.Length = value / maxValue;
valueText.Text = value.ToString("N1", CultureInfo.CurrentCulture);
2017-03-25 22:33:03 +00:00
}
}
public Color4 AccentColour
2017-03-25 22:33:03 +00:00
{
get
{
2017-04-08 11:53:11 +00:00
return bar.AccentColour;
2017-03-25 22:33:03 +00:00
}
set
{
2017-04-08 11:53:11 +00:00
bar.AccentColour = value;
2017-03-25 22:33:03 +00:00
}
}
2017-04-10 14:42:23 +00:00
public DifficultyRow(string difficultyName, float maxValue = 10)
2017-03-25 22:33:03 +00:00
{
2017-04-10 14:42:23 +00:00
this.maxValue = maxValue;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
2017-03-25 22:33:03 +00:00
Children = new Drawable[]
{
2017-04-01 16:12:44 +00:00
name = new OsuSpriteText
2017-03-25 22:33:03 +00:00
{
2017-04-03 21:03:49 +00:00
Font = @"Exo2.0-Regular",
2017-04-10 14:42:23 +00:00
Text = difficultyName,
2017-03-25 22:33:03 +00:00
},
bar = new Bar
2017-03-25 22:33:03 +00:00
{
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(1, 0.35f),
Padding = new MarginPadding { Left = 100, Right = 25 },
},
2017-04-01 16:12:44 +00:00
valueText = new OsuSpriteText
2017-03-25 22:33:03 +00:00
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
2017-04-03 21:03:49 +00:00
Font = @"Exo2.0-Regular",
2017-03-25 22:33:03 +00:00
},
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colour)
{
name.Colour = colour.GrayB;
bar.BackgroundColour = colour.Gray7;
valueText.Colour = colour.GrayB;
}
2017-03-24 22:02:24 +00:00
}
private class MetadataSegment : Container, IHasAccentColour
{
private readonly OsuSpriteText header;
private readonly FillFlowContainer<OsuSpriteText> content;
2017-04-10 14:42:23 +00:00
public string Text
{
set
{
2017-04-10 14:42:23 +00:00
if (string.IsNullOrEmpty(value))
Hide();
else
{
2017-04-10 14:42:23 +00:00
Show();
if (header.Text == "Tags")
content.Children = value.Split(' ').Select(text => new OsuSpriteText
{
Text = text,
Font = "Exo2.0-Regular",
});
else
content.Children = new[]
{
new OsuSpriteText
{
Text = value,
Font = "Exo2.0-Regular",
}
};
}
}
}
public Color4 AccentColour
{
get
{
return content.Colour;
}
set
{
content.Colour = value;
}
}
2017-04-10 14:42:23 +00:00
public MetadataSegment(string headerText)
{
2017-04-10 14:42:23 +00:00
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Margin = new MarginPadding { Top = 10 };
Children = new Drawable[]
{
header = new OsuSpriteText
{
Font = @"Exo2.0-Bold",
2017-04-10 14:42:23 +00:00
Text = headerText,
},
content = new FillFlowContainer<OsuSpriteText>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Full,
2017-06-07 11:53:37 +00:00
Spacing = new Vector2(5, 0),
Margin = new MarginPadding { Top = header.TextSize }
}
};
}
}
2017-03-24 22:02:24 +00:00
}
}