mirror of
https://github.com/ppy/osu.git
synced 2026-06-08 03:43:39 +08:00
4b9335ca59
I am aware of the possibly-one-day-definitely-upcoming redesign, but while it's not yet ready I've made a couple of small design improvements to the current result screen. This includes: - Slightly tighter and more consistent paddings - StatisticPanel's header being consistent with other similar panels (it's using subsection design right now which probably made sense back when it was all one panel) - More compact PerformanceBreakdown section - Slightly less background blur and slightly more transparent statistics panels Before: <img width="1922" height="1112" alt="image" src="https://github.com/user-attachments/assets/89b564fd-0205-40bd-a7d5-9e0484fbe5dd" /> <img width="1922" height="1112" alt="image" src="https://github.com/user-attachments/assets/6d58f538-7a78-4de2-9316-7d070dabaf2c" /> After: <img width="1922" height="1112" alt="image" src="https://github.com/user-attachments/assets/fa242349-27a1-4391-90fa-4d53be718fd4" /> <img width="1922" height="1112" alt="image" src="https://github.com/user-attachments/assets/9a755e6f-d6fe-4df2-880c-1f6195bd2a69" /> --------- Co-authored-by: Dean Herbert <pe@ppy.sh>
272 lines
12 KiB
C#
272 lines
12 KiB
C#
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
// See the LICENCE file in the repository root for full licence text.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using osu.Framework.Allocation;
|
|
using osu.Framework.Extensions;
|
|
using osu.Framework.Extensions.Color4Extensions;
|
|
using osu.Framework.Extensions.LocalisationExtensions;
|
|
using osu.Framework.Graphics;
|
|
using osu.Framework.Graphics.Containers;
|
|
using osu.Framework.Utils;
|
|
using osu.Game.Beatmaps;
|
|
using osu.Game.Graphics;
|
|
using osu.Game.Graphics.Sprites;
|
|
using osu.Game.Graphics.UserInterface;
|
|
using osu.Game.Rulesets.Difficulty;
|
|
using osu.Game.Scoring;
|
|
using osuTK.Graphics;
|
|
|
|
namespace osu.Game.Screens.Ranking.Statistics
|
|
{
|
|
public partial class PerformanceBreakdownChart : Container
|
|
{
|
|
private readonly ScoreInfo score;
|
|
|
|
private Drawable spinner = null!;
|
|
private Drawable content = null!;
|
|
private GridContainer chart = null!;
|
|
private OsuSpriteText achievedPerformance = null!;
|
|
private OsuSpriteText maximumPerformance = null!;
|
|
|
|
private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
|
|
|
|
[Resolved]
|
|
private BeatmapDifficultyCache difficultyCache { get; set; } = null!;
|
|
|
|
public PerformanceBreakdownChart(ScoreInfo score, IBeatmap playableBeatmap)
|
|
{
|
|
this.score = score;
|
|
}
|
|
|
|
[BackgroundDependencyLoader]
|
|
private void load()
|
|
{
|
|
Children = new[]
|
|
{
|
|
spinner = new LoadingSpinner(true)
|
|
{
|
|
Origin = Anchor.Centre,
|
|
Anchor = Anchor.Centre
|
|
},
|
|
content = new GridContainer
|
|
{
|
|
Alpha = 0,
|
|
RelativeSizeAxes = Axes.X,
|
|
AutoSizeAxes = Axes.Y,
|
|
Origin = Anchor.TopCentre,
|
|
Anchor = Anchor.TopCentre,
|
|
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
|
ColumnDimensions = new[]
|
|
{
|
|
new Dimension(),
|
|
new Dimension(GridSizeMode.Absolute, 50),
|
|
new Dimension()
|
|
},
|
|
Content = new[]
|
|
{
|
|
new Drawable[]
|
|
{
|
|
chart = new GridContainer
|
|
{
|
|
RelativeSizeAxes = Axes.X,
|
|
AutoSizeAxes = Axes.Y,
|
|
Origin = Anchor.Centre,
|
|
Anchor = Anchor.Centre,
|
|
ColumnDimensions = new[]
|
|
{
|
|
new Dimension(GridSizeMode.AutoSize),
|
|
new Dimension(),
|
|
new Dimension(GridSizeMode.AutoSize)
|
|
}
|
|
},
|
|
new SimpleStatisticTable.Spacer(),
|
|
new GridContainer
|
|
{
|
|
RelativeSizeAxes = Axes.X,
|
|
AutoSizeAxes = Axes.Y,
|
|
Origin = Anchor.Centre,
|
|
Anchor = Anchor.Centre,
|
|
ColumnDimensions = new[]
|
|
{
|
|
new Dimension(),
|
|
new Dimension(GridSizeMode.AutoSize)
|
|
},
|
|
RowDimensions = new[]
|
|
{
|
|
new Dimension(GridSizeMode.AutoSize),
|
|
new Dimension(GridSizeMode.AutoSize)
|
|
},
|
|
Content = new[]
|
|
{
|
|
new Drawable[]
|
|
{
|
|
new OsuSpriteText
|
|
{
|
|
Origin = Anchor.CentreLeft,
|
|
Anchor = Anchor.CentreLeft,
|
|
Font = OsuFont.GetFont(weight: FontWeight.Regular, size: StatisticItem.FONT_SIZE),
|
|
Text = "Achieved PP",
|
|
Colour = Color4Extensions.FromHex("#66FFCC")
|
|
},
|
|
achievedPerformance = new OsuSpriteText
|
|
{
|
|
Origin = Anchor.CentreRight,
|
|
Anchor = Anchor.CentreRight,
|
|
Font = OsuFont.GetFont(weight: FontWeight.Regular, size: StatisticItem.FONT_SIZE),
|
|
Colour = Color4Extensions.FromHex("#66FFCC")
|
|
}
|
|
},
|
|
new Drawable[]
|
|
{
|
|
new OsuSpriteText
|
|
{
|
|
Origin = Anchor.CentreLeft,
|
|
Anchor = Anchor.CentreLeft,
|
|
Font = OsuFont.GetFont(weight: FontWeight.Regular, size: StatisticItem.FONT_SIZE),
|
|
Text = "Maximum",
|
|
Colour = OsuColour.Gray(0.7f)
|
|
},
|
|
maximumPerformance = new OsuSpriteText
|
|
{
|
|
Origin = Anchor.CentreLeft,
|
|
Anchor = Anchor.CentreLeft,
|
|
Font = OsuFont.GetFont(weight: FontWeight.Regular, size: StatisticItem.FONT_SIZE),
|
|
Colour = OsuColour.Gray(0.7f)
|
|
}
|
|
}
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
spinner.Show();
|
|
|
|
computePerformance(cancellationTokenSource.Token)
|
|
.ContinueWith(t => Schedule(() =>
|
|
{
|
|
if (t.GetResultSafely() is PerformanceBreakdown breakdown)
|
|
setPerformance(breakdown);
|
|
}), TaskContinuationOptions.OnlyOnRanToCompletion);
|
|
}
|
|
|
|
private async Task<PerformanceBreakdown?> computePerformance(CancellationToken token)
|
|
{
|
|
var performanceCalculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator();
|
|
if (performanceCalculator == null)
|
|
return null;
|
|
|
|
var starsTask = difficultyCache.GetDifficultyAsync(score.BeatmapInfo!, score.Ruleset, score.Mods, token).ConfigureAwait(false);
|
|
if (await starsTask is not StarDifficulty stars)
|
|
return null;
|
|
|
|
if (stars.DifficultyAttributes == null || stars.PerformanceAttributes == null)
|
|
return null;
|
|
|
|
return new PerformanceBreakdown(
|
|
await performanceCalculator.CalculateAsync(score, stars.DifficultyAttributes, token).ConfigureAwait(false),
|
|
stars.PerformanceAttributes);
|
|
}
|
|
|
|
private void setPerformance(PerformanceBreakdown breakdown)
|
|
{
|
|
spinner.Hide();
|
|
content.FadeIn(200);
|
|
|
|
var displayAttributes = breakdown.Performance.GetAttributesForDisplay();
|
|
var perfectDisplayAttributes = breakdown.PerfectPerformance.GetAttributesForDisplay();
|
|
|
|
setTotalValues(
|
|
displayAttributes.First(a => a.PropertyName == nameof(PerformanceAttributes.Total)),
|
|
perfectDisplayAttributes.First(a => a.PropertyName == nameof(PerformanceAttributes.Total))
|
|
);
|
|
|
|
var rowDimensions = new List<Dimension>();
|
|
var rows = new List<Drawable[]>();
|
|
|
|
foreach (PerformanceDisplayAttribute attr in displayAttributes)
|
|
{
|
|
if (attr.PropertyName == nameof(PerformanceAttributes.Total)) continue;
|
|
|
|
var row = createAttributeRow(attr, perfectDisplayAttributes.First(a => a.PropertyName == attr.PropertyName));
|
|
|
|
if (row != null)
|
|
{
|
|
rows.Add(row);
|
|
rowDimensions.Add(new Dimension(GridSizeMode.AutoSize));
|
|
}
|
|
}
|
|
|
|
chart.RowDimensions = rowDimensions.ToArray();
|
|
chart.Content = rows.ToArray();
|
|
}
|
|
|
|
private void setTotalValues(PerformanceDisplayAttribute attribute, PerformanceDisplayAttribute perfectAttribute)
|
|
{
|
|
achievedPerformance.Text = Math.Round(attribute.Value, MidpointRounding.AwayFromZero).ToLocalisableString();
|
|
maximumPerformance.Text = Math.Round(perfectAttribute.Value, MidpointRounding.AwayFromZero).ToLocalisableString();
|
|
}
|
|
|
|
private Drawable[]? createAttributeRow(PerformanceDisplayAttribute attribute, PerformanceDisplayAttribute perfectAttribute)
|
|
{
|
|
// Don't display the attribute if its maximum is 0
|
|
// For example, flashlight bonus would be zero if flashlight mod isn't on
|
|
if (Precision.AlmostEquals(perfectAttribute.Value, 0f))
|
|
return null;
|
|
|
|
float percentage = (float)(attribute.Value / perfectAttribute.Value);
|
|
|
|
return new Drawable[]
|
|
{
|
|
new OsuSpriteText
|
|
{
|
|
Origin = Anchor.CentreLeft,
|
|
Anchor = Anchor.CentreLeft,
|
|
Font = OsuFont.GetFont(weight: FontWeight.Regular, size: StatisticItem.FONT_SIZE),
|
|
Text = attribute.DisplayName,
|
|
Colour = Colour4.White
|
|
},
|
|
new Container
|
|
{
|
|
RelativeSizeAxes = Axes.Both,
|
|
Padding = new MarginPadding { Left = 10, Right = 10 },
|
|
Child = new Bar
|
|
{
|
|
RelativeSizeAxes = Axes.X,
|
|
Origin = Anchor.Centre,
|
|
Anchor = Anchor.Centre,
|
|
CornerRadius = 2.5f,
|
|
Masking = true,
|
|
Height = 5,
|
|
BackgroundColour = Color4.White.Opacity(0.5f),
|
|
AccentColour = Color4Extensions.FromHex("#66FFCC"),
|
|
Length = percentage
|
|
}
|
|
},
|
|
new OsuSpriteText
|
|
{
|
|
Origin = Anchor.CentreRight,
|
|
Anchor = Anchor.CentreRight,
|
|
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: StatisticItem.FONT_SIZE),
|
|
Text = percentage.ToLocalisableString("0%"),
|
|
Colour = Colour4.White
|
|
}
|
|
};
|
|
}
|
|
|
|
protected override void Dispose(bool isDisposing)
|
|
{
|
|
cancellationTokenSource.Cancel();
|
|
cancellationTokenSource.Dispose();
|
|
|
|
base.Dispose(isDisposing);
|
|
}
|
|
}
|
|
}
|