1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-14 17:17:24 +08:00

Rewrite BeatmapDetails to be more modular for future code sharing.

This commit is contained in:
DrabWeb 2017-09-07 14:53:53 -03:00
parent c2b16dae10
commit 5e685ff5b1
8 changed files with 871 additions and 393 deletions

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -10,6 +11,8 @@ namespace osu.Game.Screens.Select
{ {
public class BeatmapDetailArea : Container public class BeatmapDetailArea : Container
{ {
private const float details_padding = 10;
private readonly Container content; private readonly Container content;
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
@ -66,9 +69,8 @@ namespace osu.Game.Screens.Select
Details = new BeatmapDetails Details = new BeatmapDetails
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Masking = true,
Height = 352,
Alpha = 0, Alpha = 0,
Margin = new MarginPadding { Top = details_padding },
}, },
Leaderboard = new Leaderboard Leaderboard = new Leaderboard
{ {
@ -76,5 +78,12 @@ namespace osu.Game.Screens.Select
} }
}); });
} }
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
Details.Height = Math.Min(DrawHeight - (details_padding * 3) - BeatmapDetailAreaTabControl.HEIGHT, 450);
}
} }
} }

View File

@ -9,71 +9,188 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using System.Globalization;
using System.Linq; using System.Linq;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Extensions.Color4Extensions;
using osu.Game.Screens.Select.Details;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
namespace osu.Game.Screens.Select namespace osu.Game.Screens.Select
{ {
public class BeatmapDetails : Container public class BeatmapDetails : Container
{ {
private readonly MetadataSegment description; private const float spacing = 10;
private readonly MetadataSegment source; private const float transition_duration = 250;
private readonly MetadataSegment tags;
private readonly DifficultyRow circleSize; private readonly FillFlowContainer top, statsFlow;
private readonly DifficultyRow drainRate; private readonly AdvancedStats advanced;
private readonly DifficultyRow overallDifficulty; private readonly DetailBox ratingsContainer;
private readonly DifficultyRow approachRate; private readonly UserRatings ratings;
private readonly DifficultyRow stars; private readonly ScrollContainer metadataScroll;
private readonly MetadataSection description, source, tags;
private readonly Container failRetryContainer;
private readonly FailRetryGraph failRetryGraph;
private readonly DimmedLoadingAnimation loading;
private readonly Container ratingsContainer; private APIAccess api;
private readonly Bar ratingsBar;
private readonly OsuSpriteText negativeRatings;
private readonly OsuSpriteText positiveRatings;
private readonly BarGraph ratingsGraph;
private readonly FillFlowContainer retryFailContainer;
private readonly BarGraph retryGraph;
private readonly BarGraph failGraph;
private ScheduledDelegate pendingBeatmapSwitch; private ScheduledDelegate pendingBeatmapSwitch;
private BeatmapInfo beatmap; private BeatmapInfo beatmap;
public BeatmapInfo Beatmap public BeatmapInfo Beatmap
{ {
get { return beatmap; } get { return beatmap; }
set set
{ {
if (beatmap == value) return; if (value == beatmap) return;
beatmap = value; beatmap = value;
pendingBeatmapSwitch?.Cancel(); pendingBeatmapSwitch?.Cancel();
pendingBeatmapSwitch = Schedule(updateStats); pendingBeatmapSwitch = Schedule(updateStatistics);
} }
} }
private void updateStats() public BeatmapDetails()
{ {
if (beatmap == null) return; Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.5f),
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = spacing },
Children = new Drawable[]
{
top = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
statsFlow = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Width = 0.5f,
Spacing = new Vector2(spacing),
Padding = new MarginPadding { Right = spacing / 2 },
Children = new[]
{
new DetailBox
{
Child = advanced = new AdvancedStats
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Horizontal = spacing, Top = spacing * 2, Bottom = spacing },
},
},
ratingsContainer = new DetailBox
{
Child = ratings = new UserRatings
{
RelativeSizeAxes = Axes.X,
Height = 134,
Padding = new MarginPadding { Horizontal = spacing, Top = spacing },
},
},
},
},
metadataScroll = new ScrollContainer
{
RelativeSizeAxes = Axes.X,
Width = 0.5f,
ScrollbarVisible = false,
Padding = new MarginPadding { Left = spacing / 2 },
Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
LayoutDuration = transition_duration,
Spacing = new Vector2(spacing * 2),
Margin = new MarginPadding { Top = spacing * 2 },
Children = new[]
{
description = new MetadataSection("Description")
{
TextColour = Color4.White.Opacity(0.75f),
},
source = new MetadataSection("Source")
{
TextColour = Color4.White.Opacity(0.75f),
},
tags = new MetadataSection("Tags"),
},
},
},
},
},
failRetryContainer = new Container
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
Children = new Drawable[]
{
new OsuSpriteText
{
Text = "Points of Failure",
Font = @"Exo2.0-Bold",
TextSize = 14,
},
failRetryGraph = new FailRetryGraph
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 14 + spacing / 2 },
},
},
},
},
},
loading = new DimmedLoadingAnimation
{
RelativeSizeAxes = Axes.Both,
},
};
}
description.Text = beatmap.Version; [BackgroundDependencyLoader]
source.Text = beatmap.Metadata.Source; private void load(OsuColour colours, APIAccess api)
tags.Text = beatmap.Metadata.Tags; {
this.api = api;
tags.TextColour = colours.Yellow;
}
circleSize.Value = beatmap.Difficulty.CircleSize; protected override void UpdateAfterChildren()
drainRate.Value = beatmap.Difficulty.DrainRate; {
overallDifficulty.Value = beatmap.Difficulty.OverallDifficulty; base.UpdateAfterChildren();
approachRate.Value = beatmap.Difficulty.ApproachRate;
stars.Value = (float)beatmap.StarDifficulty;
var requestedBeatmap = beatmap; metadataScroll.Height = statsFlow.DrawHeight;
failRetryContainer.Height = DrawHeight - Padding.TotalVertical - (top.DrawHeight + spacing / 2);
}
private void updateStatistics()
{
if (Beatmap == null)
{
clearStats();
return;
}
ratingsContainer.FadeIn(transition_duration);
advanced.Beatmap = Beatmap;
description.Text = Beatmap.Version;
source.Text = Beatmap.Metadata.Source;
tags.Text = Beatmap.Metadata.Tags;
var requestedBeatmap = Beatmap;
if (requestedBeatmap.Metrics == null) if (requestedBeatmap.Metrics == null)
{ {
var lookup = new GetBeatmapDetailsRequest(requestedBeatmap); var lookup = new GetBeatmapDetailsRequest(requestedBeatmap);
@ -84,413 +201,190 @@ namespace osu.Game.Screens.Select
return; return;
requestedBeatmap.Metrics = res; requestedBeatmap.Metrics = res;
Schedule(() => updateMetrics(res)); Schedule(() => displayMetrics(res));
}; };
lookup.Failure += e => Schedule(() => updateMetrics(null)); lookup.Failure += e => Schedule(() => displayMetrics(null));
api.Queue(lookup); api.Queue(lookup);
loading.Show(); loading.Show();
} }
updateMetrics(requestedBeatmap.Metrics, false); displayMetrics(requestedBeatmap.Metrics, false);
} }
/// <summary> private void displayMetrics(BeatmapMetrics metrics, bool failOnMissing = true)
/// 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>
private void updateMetrics(BeatmapMetrics metrics, bool failOnMissing = true)
{ {
var hasRatings = metrics?.Ratings.Any() ?? false; var hasRatings = metrics?.Ratings.Any() ?? false;
var hasRetriesFails = (metrics?.Retries.Any() ?? false) && metrics.Fails.Any(); var hasRetriesFails = (metrics?.Retries.Any() ?? false) && metrics.Fails.Any();
if (failOnMissing) if (failOnMissing) loading.Hide();
loading.Hide();
if (hasRatings) if (hasRatings)
{ {
var ratings = metrics.Ratings.ToList(); ratings.Metrics = metrics;
ratingsContainer.Show(); ratings.FadeIn(transition_duration);
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, Easing.Out);
}
else if (failOnMissing)
ratingsGraph.Values = new float[10];
else
ratingsContainer.FadeColour(Color4.Gray, 500, Easing.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, Easing.Out);
} }
else if (failOnMissing) else if (failOnMissing)
{ {
failGraph.Values = new float[100]; ratings.FadeTo(0.25f, transition_duration);
retryGraph.Values = new float[100]; ratings.Metrics = new BeatmapMetrics
}
else
retryFailContainer.FadeColour(Color4.Gray, 500, Easing.Out);
}
public BeatmapDetails()
{
Children = new Drawable[]
{
new Box
{ {
RelativeSizeAxes = Axes.Both, Ratings = new int[10],
Colour = Color4.Black,
Alpha = 0.5f,
},
new FillFlowContainer<MetadataSegment>
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Width = 0.4f,
Direction = FillDirection.Vertical,
LayoutDuration = 200,
LayoutEasing = Easing.OutQuint,
Children = new[]
{
description = new MetadataSegment("Description"),
source = new MetadataSegment("Source"),
tags = new MetadataSegment("Tags")
},
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Width = 0.6f,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 15),
Padding = new MarginPadding(10) { Top = 0 },
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = 0.5f,
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5),
Padding = new MarginPadding(10),
Children = new[]
{
circleSize = new DifficultyRow("Circle Size", 7),
drainRate = new DifficultyRow("HP Drain"),
overallDifficulty = new DifficultyRow("Accuracy"),
approachRate = new DifficultyRow("Approach Rate"),
stars = new DifficultyRow("Star Difficulty"),
},
},
},
},
ratingsContainer = new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Alpha = 0,
AlwaysPresent = true,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = 0.5f,
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Padding = new MarginPadding
{
Top = 25,
Left = 15,
Right = 15,
},
Children = new Drawable[]
{
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[]
{
negativeRatings = new OsuSpriteText
{
Font = @"Exo2.0-Regular",
Text = "0",
},
positiveRatings = new OsuSpriteText
{
Font = @"Exo2.0-Regular",
Text = "0",
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
},
},
},
new OsuSpriteText
{
Text = "Rating Spread",
TextSize = 14,
Font = @"Exo2.0-Regular",
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
},
ratingsGraph = new BarGraph
{
RelativeSizeAxes = Axes.X,
Height = 50,
},
},
},
},
},
retryFailContainer = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Alpha = 0,
Children = new Drawable[]
{
new OsuSpriteText
{
Text = "Points of Failure",
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,
},
},
},
}
},
},
},
loading = new LoadingAnimation()
};
}
private APIAccess api;
private readonly LoadingAnimation loading;
[BackgroundDependencyLoader]
private void load(OsuColour colour, APIAccess api)
{
this.api = api;
description.AccentColour = colour.GrayB;
source.AccentColour = colour.GrayB;
tags.AccentColour = colour.YellowLight;
stars.AccentColour = colour.Yellow;
ratingsBar.BackgroundColour = colour.Green;
ratingsBar.AccentColour = colour.YellowDark;
ratingsGraph.Colour = colour.BlueDark;
failGraph.Colour = colour.YellowDarker;
retryGraph.Colour = colour.Yellow;
}
private class DifficultyRow : Container, IHasAccentColour
{
private readonly OsuSpriteText name;
private readonly Bar bar;
private readonly OsuSpriteText valueText;
private readonly float maxValue;
private float difficultyValue;
public float Value
{
get
{
return difficultyValue;
}
set
{
difficultyValue = value;
bar.Length = value / maxValue;
valueText.Text = value.ToString("N1", CultureInfo.CurrentCulture);
}
}
public Color4 AccentColour
{
get
{
return bar.AccentColour;
}
set
{
bar.AccentColour = value;
}
}
public DifficultyRow(string difficultyName, float maxValue = 10)
{
this.maxValue = maxValue;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Children = new Drawable[]
{
name = new OsuSpriteText
{
Font = @"Exo2.0-Regular",
Text = difficultyName,
},
bar = new Bar
{
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(1, 0.35f),
Padding = new MarginPadding { Left = 100, Right = 25 },
},
valueText = new OsuSpriteText
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Font = @"Exo2.0-Regular",
},
}; };
} }
[BackgroundDependencyLoader] if (hasRetriesFails)
private void load(OsuColour colour)
{ {
name.Colour = colour.GrayB; failRetryGraph.Metrics = metrics;
bar.BackgroundColour = colour.Gray7; failRetryContainer.FadeIn(transition_duration);
valueText.Colour = colour.GrayB; }
else if (failOnMissing)
{
failRetryContainer.FadeTo(0.25f, transition_duration);
failRetryGraph.Metrics = new BeatmapMetrics
{
Fails = new int[100],
Retries = new int[100],
};
} }
} }
private class MetadataSegment : Container, IHasAccentColour private void clearStats()
{ {
private readonly OsuSpriteText header; description.Text = null;
private readonly FillFlowContainer<OsuSpriteText> content; source.Text = null;
tags.Text = null;
advanced.Beatmap = new BeatmapInfo
{
StarDifficulty = 0,
Difficulty = new BeatmapDifficulty
{
CircleSize = 0,
DrainRate = 0,
OverallDifficulty = 0,
ApproachRate = 0,
},
};
loading.Hide();
ratingsContainer.FadeOut(transition_duration);
failRetryContainer.FadeOut(transition_duration);
}
private class DetailBox : Container
{
private readonly Container content;
protected override Container<Drawable> Content => content;
public DetailBox()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChildren = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.5f),
},
content = new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
},
};
}
}
private class MetadataSection : Container
{
private readonly OsuSpriteText title;
private readonly TextFlowContainer textFlow;
public string Text public string Text
{ {
set set
{ {
if (string.IsNullOrEmpty(value)) if (string.IsNullOrEmpty(value))
Hide();
else
{ {
Show(); this.FadeOut(transition_duration);
if (header.Text == "Tags") return;
content.ChildrenEnumerable = value.Split(' ').Select(text => new OsuSpriteText
{
Text = text,
Font = "Exo2.0-Regular",
});
else
content.Children = new[]
{
new OsuSpriteText
{
Text = value,
Font = "Exo2.0-Regular",
}
};
} }
this.FadeIn(transition_duration);
textFlow.Clear();
textFlow.AddText(value, s => s.TextSize = 14);
} }
} }
public Color4 AccentColour public Color4 TextColour
{ {
get get { return textFlow.Colour; }
{ set { textFlow.Colour = value; }
return content.Colour;
}
set
{
content.Colour = value;
}
} }
public MetadataSegment(string headerText) public MetadataSection(string title)
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y; AutoSizeAxes = Axes.Y;
Margin = new MarginPadding { Top = 10 };
InternalChild = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(spacing / 2),
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Child = this.title = new OsuSpriteText
{
Text = title,
Font = @"Exo2.0-Bold",
TextSize = 14,
},
},
textFlow = new TextFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
},
},
};
}
}
private class DimmedLoadingAnimation : VisibilityContainer
{
private readonly LoadingAnimation loading;
public DimmedLoadingAnimation()
{
Children = new Drawable[] Children = new Drawable[]
{ {
header = new OsuSpriteText new Box
{ {
Font = @"Exo2.0-Bold", RelativeSizeAxes = Axes.Both,
Text = headerText, Colour = Color4.Black.Opacity(0.5f),
}, },
content = new FillFlowContainer<OsuSpriteText> loading = new LoadingAnimation(),
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Full,
Spacing = new Vector2(5, 0),
Margin = new MarginPadding { Top = header.TextSize }
}
}; };
} }
protected override void PopIn()
{
this.FadeIn(transition_duration, Easing.OutQuint);
loading.State = Visibility.Visible;
}
protected override void PopOut()
{
this.FadeOut(transition_duration, Easing.OutQuint);
loading.State = Visibility.Hidden;
}
} }
} }
} }

View File

@ -0,0 +1,152 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using System;
using osu.Game.Beatmaps;
namespace osu.Game.Screens.Select.Details
{
public class AdvancedStats : Container
{
private readonly StatRow firstValue, hpDrain, accuracy, approachRate, starDifficulty;
private BeatmapInfo beatmap;
public BeatmapInfo Beatmap
{
get { return beatmap; }
set
{
if (value == beatmap) return;
beatmap = value;
//mania specific
if ((Beatmap?.Ruleset?.ID ?? 0) == 3)
{
firstValue.Title = "Key Amount";
firstValue.Value = (int)Math.Round(Beatmap.Difficulty.CircleSize);
}
else
{
firstValue.Title = "Circle Size";
firstValue.Value = Beatmap.Difficulty.CircleSize;
}
hpDrain.Value = beatmap.Difficulty.DrainRate;
accuracy.Value = beatmap.Difficulty.OverallDifficulty;
approachRate.Value = beatmap.Difficulty.ApproachRate;
starDifficulty.Value = (float)beatmap.StarDifficulty;
}
}
public AdvancedStats()
{
Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(4f),
Children = new[]
{
firstValue = new StatRow(), //circle size/key amount
hpDrain = new StatRow { Title = "HP Drain" },
accuracy = new StatRow { Title = "Accuracy" },
approachRate = new StatRow { Title = "Approach Rate" },
starDifficulty = new StatRow(10, true) { Title = "Star Difficulty" },
},
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
starDifficulty.AccentColour = colours.Yellow;
}
private class StatRow : Container, IHasAccentColour
{
private const float value_width = 25;
private const float name_width = 70;
private readonly float maxValue;
private readonly bool forceDecimalPlaces;
private readonly OsuSpriteText name, value;
private readonly Bar bar;
public string Title
{
get { return name.Text; }
set { name.Text = value; }
}
private float difficultyValue;
public float Value
{
get { return difficultyValue; }
set
{
difficultyValue = value;
bar.Length = value / maxValue;
this.value.Text = value.ToString(forceDecimalPlaces ? "#.00" : "0.##");
}
}
public Color4 AccentColour
{
get { return bar.AccentColour; }
set { bar.AccentColour = value; }
}
public StatRow(float maxValue = 10, bool forceDecimalPlaces = false)
{
this.maxValue = maxValue;
this.forceDecimalPlaces = forceDecimalPlaces;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Children = new Drawable[]
{
new Container
{
Width = name_width,
AutoSizeAxes = Axes.Y,
Child = this.name = new OsuSpriteText
{
TextSize = 13,
},
},
bar = new Bar
{
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
RelativeSizeAxes = Axes.X,
Height = 5,
BackgroundColour = Color4.White.Opacity(0.5f),
Padding = new MarginPadding { Left = name_width + 10, Right = value_width + 10 },
},
new Container
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Width = value_width,
RelativeSizeAxes = Axes.Y,
Child = value = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
TextSize = 13,
},
},
};
}
}
}
}

View File

@ -0,0 +1,120 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Screens.Select.Details
{
public class BasicStats : Container
{
private const float stat_count = 4;
private readonly Statistic length, bpm, circleCount, sliderCount;
private BeatmapInfo beatmap;
public BeatmapInfo Beatmap
{
get { return beatmap; }
set
{
if (value == beatmap) return;
beatmap = value;
//length.Value = TimeSpan.FromMilliseconds(Beatmap.OnlineInfo.Length).ToString(@"m\:ss");
//bpm.Value = Beatmap.BeatmapSet.OnlineInfo.BPM.ToString(@"0.##");
//circleCount.Value = string.Format(@"{0:n0}", beatmap.OnlineInfo.CircleCount);
//sliderCount.Value = string.Format(@"{0:n0}", beatmap.OnlineInfo.SliderCount);
}
}
public BasicStats()
{
var statWidth = 1 / stat_count;
Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Children = new[]
{
length = new Statistic(FontAwesome.fa_clock_o, "Length") { Width = statWidth },
bpm = new Statistic(FontAwesome.fa_circle, "BPM") { Width = statWidth },
circleCount = new Statistic(FontAwesome.fa_circle_o, "Circle Count") { Width = statWidth },
sliderCount = new Statistic(FontAwesome.fa_circle, "Slider Count") { Width = statWidth },
},
};
}
private class Statistic : Container, IHasTooltip
{
private readonly string name;
private readonly OsuSpriteText value;
public string TooltipText => name;
public string Value
{
get { return value.Text; }
set { this.value.Text = value; }
}
public Statistic(FontAwesome icon, string name)
{
this.name = name;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Children = new Drawable[]
{
new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new SpriteIcon
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.Centre,
Icon = FontAwesome.fa_square,
Size = new Vector2(13),
Rotation = 45,
Colour = OsuColour.FromHex(@"441288"),
},
new SpriteIcon
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.Centre,
Icon = icon,
Size = new Vector2(13),
Colour = OsuColour.FromHex(@"f7dd55"),
Scale = new Vector2(0.8f),
},
value = new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
TextSize = 13,
Font = @"Exo2.0-Bold",
Margin = new MarginPadding { Left = 10 },
},
},
},
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colour)
{
value.Colour = colour.Yellow;
}
}
}
}

View File

@ -0,0 +1,62 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using System.Linq;
using osu.Game.Beatmaps;
namespace osu.Game.Screens.Select.Details
{
public class FailRetryGraph : Container
{
private readonly BarGraph retryGraph, failGraph;
private BeatmapMetrics metrics;
public BeatmapMetrics Metrics
{
get { return metrics; }
set
{
if (value == metrics) return;
metrics = value;
var retries = Metrics.Retries;
var fails = Metrics.Fails;
float maxValue = fails.Zip(retries, (fail, retry) => fail + retry).Max();
failGraph.MaxValue = maxValue;
retryGraph.MaxValue = maxValue;
failGraph.Values = fails.Select(f => (float)f);
retryGraph.Values = retries.Zip(fails, (retry, fail) => retry + MathHelper.Clamp(fail, 0, maxValue));
}
}
public FailRetryGraph()
{
Children = new[]
{
retryGraph = new BarGraph
{
RelativeSizeAxes = Axes.Both,
},
failGraph = new BarGraph
{
RelativeSizeAxes = Axes.Both,
},
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
retryGraph.Colour = colours.Yellow;
failGraph.Colour = colours.YellowDarker;
}
}
}

View File

@ -0,0 +1,114 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Beatmaps;
namespace osu.Game.Screens.Select.Details
{
public class SuccessRate : Container
{
private readonly FillFlowContainer header;
private readonly OsuSpriteText successRateLabel, successPercent, graphLabel;
private readonly Bar successRate;
private readonly Container percentContainer;
private readonly FailRetryGraph graph;
private BeatmapInfo beatmap;
public BeatmapInfo Beatmap
{
get { return beatmap; }
set
{
if (value == beatmap) return;
beatmap = value;
//successPercent.Text = $"{beatmap.OnlineInfo.SuccessRate}%";
//successRate.Length = (float)beatmap.OnlineInfo.SuccessRate / 100f;
graph.Metrics = Beatmap.Metrics;
}
}
public SuccessRate()
{
Children = new Drawable[]
{
header = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
successRateLabel = new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = "Success Rate",
TextSize = 13,
},
successRate = new Bar
{
RelativeSizeAxes = Axes.X,
Height = 5,
Margin = new MarginPadding { Top = 5 },
},
percentContainer = new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Child = successPercent = new OsuSpriteText
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopCentre,
TextSize = 13,
},
},
graphLabel = new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = "Points of Failure",
TextSize = 13,
Margin = new MarginPadding { Vertical = 20 },
},
},
},
graph = new FailRetryGraph
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Both,
},
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
successRateLabel.Colour = successPercent.Colour = graphLabel.Colour = colours.Gray5;
successRate.AccentColour = colours.Green;
successRate.BackgroundColour = colours.GrayD;
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
graph.Padding = new MarginPadding { Top = header.DrawHeight };
}
protected override void Update()
{
base.Update();
percentContainer.Width = successRate.Length;
}
}
}

View File

@ -0,0 +1,122 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using System.Linq;
using osu.Game.Beatmaps;
namespace osu.Game.Screens.Select.Details
{
public class UserRatings : Container
{
private readonly FillFlowContainer header;
private readonly Bar ratingsBar;
private readonly OsuSpriteText negativeRatings, positiveRatings;
private readonly Container graphContainer;
private readonly BarGraph graph;
private BeatmapMetrics metrics;
public BeatmapMetrics Metrics
{
get { return metrics; }
set
{
if (value == metrics) return;
metrics = value;
var ratings = Metrics.Ratings.ToList();
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();
graph.Values = Metrics.Ratings.Select(r => (float)r);
}
}
public UserRatings()
{
Children = new Drawable[]
{
header = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = "User Rating",
TextSize = 13,
},
ratingsBar = new Bar
{
RelativeSizeAxes = Axes.X,
Height = 5,
Margin = new MarginPadding { Top = 5 },
},
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new[]
{
negativeRatings = new OsuSpriteText
{
Text = "0",
TextSize = 13,
},
positiveRatings = new OsuSpriteText
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Text = @"0",
TextSize = 13,
},
},
},
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = "Rating Spread",
TextSize = 13,
Margin = new MarginPadding { Top = 10, Bottom = 5 },
},
},
},
graphContainer = new Container
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Both,
Child = graph = new BarGraph
{
RelativeSizeAxes = Axes.Both,
},
},
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
ratingsBar.BackgroundColour = colours.Green;
ratingsBar.AccentColour = colours.Yellow;
graph.Colour = colours.BlueDark;
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
graphContainer.Padding = new MarginPadding { Top = header.DrawHeight };
}
}
}

View File

@ -538,6 +538,11 @@
<Compile Include="Screens\Multiplayer\ParticipantInfo.cs" /> <Compile Include="Screens\Multiplayer\ParticipantInfo.cs" />
<Compile Include="Screens\Multiplayer\ModeTypeInfo.cs" /> <Compile Include="Screens\Multiplayer\ModeTypeInfo.cs" />
<Compile Include="Beatmaps\Drawables\BeatmapSetCover.cs" /> <Compile Include="Beatmaps\Drawables\BeatmapSetCover.cs" />
<Compile Include="Screens\Select\Details\AdvancedStats.cs" />
<Compile Include="Screens\Select\Details\BasicStats.cs" />
<Compile Include="Screens\Select\Details\FailRetryGraph.cs" />
<Compile Include="Screens\Select\Details\SuccessRate.cs" />
<Compile Include="Screens\Select\Details\UserRatings.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj"> <ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj">