From 2bde4fc3eed85600b421a2cb30e83e310a5f68de Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 16 May 2020 18:17:32 +0900 Subject: [PATCH 01/33] Initial implementation of contracted score panel --- .../Scoring/OsuScoreProcessor.cs | 2 +- .../TestSceneContractedPanelMiddleContent.cs | 118 ++++++++ .../ContractedPanelMiddleContent.cs | 255 ++++++++++++++++++ osu.Game/Screens/Ranking/ScorePanel.cs | 11 +- 4 files changed, 381 insertions(+), 5 deletions(-) create mode 100644 osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs create mode 100644 osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 1de7d488f3..79a6ea7e92 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -8,7 +8,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Scoring { - internal class OsuScoreProcessor : ScoreProcessor + public class OsuScoreProcessor : ScoreProcessor { protected override JudgementResult CreateResult(HitObject hitObject, Judgement judgement) => new OsuJudgementResult(hitObject, judgement); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs new file mode 100644 index 0000000000..af3d13777c --- /dev/null +++ b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs @@ -0,0 +1,118 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking; +using osu.Game.Screens.Ranking.Contracted; +using osu.Game.Tests.Beatmaps; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Tests.Visual.Ranking +{ + public class TestSceneContractedPanelMiddleContent : OsuTestScene + { + [Resolved] + private RulesetStore rulesetStore { get; set; } + + [Test] + public void TestMapWithKnownMapper() + { + var author = new User { Username = "mapper_name" }; + + AddStep("show example score", () => showPanel(createTestBeatmap(author), createTestScore())); + + AddAssert("mapper name present", () => this.ChildrenOfType().Any(spriteText => spriteText.Text == "mapper_name")); + } + + [Test] + public void TestMapWithUnknownMapper() + { + AddStep("show example score", () => showPanel(createTestBeatmap(null), createTestScore())); + + AddAssert("mapped by text not present", () => + this.ChildrenOfType().All(spriteText => !containsAny(spriteText.Text, "mapped", "by"))); + } + + private void showPanel(WorkingBeatmap workingBeatmap, ScoreInfo score) + { + Child = new ContractedPanelMiddleContentContainer(workingBeatmap, score); + } + + private WorkingBeatmap createTestBeatmap(User author) + { + var beatmap = new TestBeatmap(rulesetStore.GetRuleset(0)); + beatmap.Metadata.Author = author; + beatmap.Metadata.Title = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap title"; + beatmap.Metadata.Artist = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap artist"; + + return new TestWorkingBeatmap(beatmap); + } + + private ScoreInfo createTestScore() => new ScoreInfo + { + User = new User + { + Id = 2, + Username = "peppy", + }, + Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, + Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, + TotalScore = 999999, + Accuracy = 0.95, + MaxCombo = 999, + Rank = ScoreRank.S, + Date = DateTimeOffset.Now, + Statistics = + { + { HitResult.Miss, 1 }, + { HitResult.Meh, 50 }, + { HitResult.Good, 100 }, + { HitResult.Great, 300 }, + } + }; + + private bool containsAny(string text, params string[] stringsToMatch) => stringsToMatch.Any(text.Contains); + + private class ContractedPanelMiddleContentContainer : Container + { + [Cached] + private Bindable workingBeatmap { get; set; } + + public ContractedPanelMiddleContentContainer(WorkingBeatmap beatmap, ScoreInfo score) + { + workingBeatmap = new Bindable(beatmap); + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Size = new Vector2(ScorePanel.CONTRACTED_WIDTH, 700); + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#353535"), + }, + new ContractedPanelMiddleContent(score), + }; + } + } + } +} diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs new file mode 100644 index 0000000000..5ecb3fbd0b --- /dev/null +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs @@ -0,0 +1,255 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Leaderboards; +using osu.Game.Scoring; +using osu.Game.Screens.Play.HUD; +using osu.Game.Users; +using osu.Game.Users.Drawables; +using osu.Game.Utils; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Ranking.Contracted +{ + /// + /// The content that appears in the middle of a contracted . + /// + public class ContractedPanelMiddleContent : CompositeDrawable + { + private readonly ScoreInfo score; + + /// + /// Creates a new . + /// + /// The to display. + public ContractedPanelMiddleContent(ScoreInfo score) + { + this.score = score; + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Container + { + Name = "Background", + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerExponent = 2.5f, + CornerRadius = 20, + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0.25f), + Type = EdgeEffectType.Shadow, + Radius = 1, + Offset = new Vector2(0, 4) + }, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("444") + }, + new UserCoverBackground + { + RelativeSizeAxes = Axes.Both, + User = score.User, + }, + } + }, + new Container + { + Name = "Background overlay", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Bottom = -1 }, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerExponent = 2.5f, + CornerRadius = 20, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.5f), Color4Extensions.FromHex("#444")) + } + } + }, + new Container + { + Name = "Foreground", + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10), + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + new UpdateableAvatar(score.User) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Size = new Vector2(140), + Masking = true, + CornerExponent = 2.5f, + CornerRadius = 20, + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0.25f), + Type = EdgeEffectType.Shadow, + Radius = 8, + Offset = new Vector2(0, 4), + } + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = score.UserString, + Font = OsuFont.GetFont(size: 16, weight: FontWeight.SemiBold) + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + ChildrenEnumerable = score.SortedStatistics.Select(s => createStatistic(s.Key.GetDescription(), s.Value.ToString())) + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = 10 }, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + Children = new[] + { + createStatistic("Max Combo", $"x{score.MaxCombo}"), + createStatistic("Accuracy", $"{score.Accuracy.FormatAccuracy()}"), + } + }, + new ModDisplay + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + ExpansionMode = ExpansionMode.AlwaysExpanded, + DisplayUnrankedText = false, + Current = { Value = score.Mods }, + Scale = new Vector2(0.5f), + } + } + } + } + } + } + }, + }, + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Vertical = 5 }, + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = score.TotalScore.ToString(), + Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, fixedWidth: true), + Spacing = new Vector2(-1, 0) + }, + }, + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 2 }, + Child = new DrawableRank(score.Rank) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + } + }, + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + } + } + } + }, + }, + RowDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, 45), + } + }; + } + + private Drawable createStatistic(string key, string value) => new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = key, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold) + }, + new OsuSpriteText + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Text = value, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), + Colour = Color4Extensions.FromHex("#FFDD55") + } + } + }; + } +} diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index c055df7ccc..bf57cb4dd9 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Scoring; +using osu.Game.Screens.Ranking.Contracted; using osu.Game.Screens.Ranking.Expanded; using osuTK; using osuTK.Graphics; @@ -21,12 +22,12 @@ namespace osu.Game.Screens.Ranking /// /// Width of the panel when contracted. /// - private const float contracted_width = 160; + public const float CONTRACTED_WIDTH = 160; /// /// Height of the panel when contracted. /// - private const float contracted_height = 320; + private const float contracted_height = 385; /// /// Width of the panel when expanded. @@ -71,7 +72,7 @@ namespace osu.Game.Screens.Ranking private static readonly ColourInfo expanded_top_layer_colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#444"), Color4Extensions.FromHex("#333")); private static readonly ColourInfo expanded_middle_layer_colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#555"), Color4Extensions.FromHex("#333")); private static readonly Color4 contracted_top_layer_colour = Color4Extensions.FromHex("#353535"); - private static readonly Color4 contracted_middle_layer_colour = Color4Extensions.FromHex("#444"); + private static readonly Color4 contracted_middle_layer_colour = Color4Extensions.FromHex("#353535"); public event Action StateChanged; @@ -193,10 +194,12 @@ namespace osu.Game.Screens.Ranking break; case PanelState.Contracted: - this.ResizeTo(new Vector2(contracted_width, contracted_height), resize_duration, Easing.OutQuint); + this.ResizeTo(new Vector2(CONTRACTED_WIDTH, contracted_height), resize_duration, Easing.OutQuint); topLayerBackground.FadeColour(contracted_top_layer_colour, resize_duration, Easing.OutQuint); middleLayerBackground.FadeColour(contracted_middle_layer_colour, resize_duration, Easing.OutQuint); + + middleLayerContentContainer.Add(topLayerContent = new ContractedPanelMiddleContent(score).With(d => d.Alpha = 0)); break; } From 3df92925ee704dce60d127c04557d3aaac15c9c3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 16 May 2020 18:22:07 +0900 Subject: [PATCH 02/33] Add score panel test --- .../Visual/Ranking/TestSceneScorePanel.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs index 1e55885385..7431002c02 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs @@ -107,13 +107,23 @@ namespace osu.Game.Tests.Visual.Ranking addPanelStep(score); } - private void addPanelStep(ScoreInfo score) => AddStep("add panel", () => + [Test] + public void TestContractedPanel() + { + var score = createScore(); + score.Accuracy = 0.925; + score.Rank = ScoreRank.A; + + addPanelStep(score, PanelState.Contracted); + } + + private void addPanelStep(ScoreInfo score, PanelState state = PanelState.Expanded) => AddStep("add panel", () => { Child = new ScorePanel(score) { Anchor = Anchor.Centre, Origin = Anchor.Centre, - State = PanelState.Expanded + State = state }; }); From 9b7b1ef605aa3fc7dbe22682dae4bc496974d28c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 16 May 2020 18:23:18 +0900 Subject: [PATCH 03/33] Add cover urls --- .../Visual/Ranking/TestSceneContractedPanelMiddleContent.cs | 1 + osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs index af3d13777c..f7694c10ec 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs @@ -72,6 +72,7 @@ namespace osu.Game.Tests.Visual.Ranking { Id = 2, Username = "peppy", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }, Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs index 7431002c02..27905f95fd 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs @@ -133,6 +133,7 @@ namespace osu.Game.Tests.Visual.Ranking { Id = 2, Username = "peppy", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }, Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, From 8c5ccf574b3e6fbf6e26a85491713f3acf71ab7a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 16 May 2020 18:28:15 +0900 Subject: [PATCH 04/33] Add better fix for 1px bleed --- .../ContractedPanelMiddleContent.cs | 153 ++++++++---------- 1 file changed, 66 insertions(+), 87 deletions(-) diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs index 5ecb3fbd0b..1d7d5c4130 100644 --- a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs @@ -53,22 +53,22 @@ namespace osu.Game.Screens.Ranking.Contracted new Container { RelativeSizeAxes = Axes.Both, + Masking = true, + CornerExponent = 2.5f, + CornerRadius = 20, + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0.25f), + Type = EdgeEffectType.Shadow, + Radius = 1, + Offset = new Vector2(0, 4) + }, Children = new Drawable[] { - new Container + // Buffered container is used to prevent 1px bleed outside the masking region + new BufferedContainer { - Name = "Background", RelativeSizeAxes = Axes.Both, - Masking = true, - CornerExponent = 2.5f, - CornerRadius = 20, - EdgeEffect = new EdgeEffectParameters - { - Colour = Color4.Black.Opacity(0.25f), - Type = EdgeEffectType.Shadow, - Radius = 1, - Offset = new Vector2(0, 4) - }, Children = new Drawable[] { new Box @@ -81,95 +81,74 @@ namespace osu.Game.Screens.Ranking.Contracted RelativeSizeAxes = Axes.Both, User = score.User, }, - } - }, - new Container - { - Name = "Background overlay", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Bottom = -1 }, - Child = new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerExponent = 2.5f, - CornerRadius = 20, - Child = new Box + new Box { RelativeSizeAxes = Axes.Both, Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.5f), Color4Extensions.FromHex("#444")) - } + }, } }, - new Container + new FillFlowContainer { - Name = "Foreground", RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10), + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), Children = new Drawable[] { + new UpdateableAvatar(score.User) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Size = new Vector2(140), + Masking = true, + CornerExponent = 2.5f, + CornerRadius = 20, + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0.25f), + Type = EdgeEffectType.Shadow, + Radius = 8, + Offset = new Vector2(0, 4), + } + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = score.UserString, + Font = OsuFont.GetFont(size: 16, weight: FontWeight.SemiBold) + }, new FillFlowContainer { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(10), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10), - Children = new Drawable[] + Spacing = new Vector2(0, 5), + ChildrenEnumerable = score.SortedStatistics.Select(s => createStatistic(s.Key.GetDescription(), s.Value.ToString())) + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = 10 }, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + Children = new[] { - new UpdateableAvatar(score.User) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Size = new Vector2(140), - Masking = true, - CornerExponent = 2.5f, - CornerRadius = 20, - EdgeEffect = new EdgeEffectParameters - { - Colour = Color4.Black.Opacity(0.25f), - Type = EdgeEffectType.Shadow, - Radius = 8, - Offset = new Vector2(0, 4), - } - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = score.UserString, - Font = OsuFont.GetFont(size: 16, weight: FontWeight.SemiBold) - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), - ChildrenEnumerable = score.SortedStatistics.Select(s => createStatistic(s.Key.GetDescription(), s.Value.ToString())) - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Top = 10 }, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), - Children = new[] - { - createStatistic("Max Combo", $"x{score.MaxCombo}"), - createStatistic("Accuracy", $"{score.Accuracy.FormatAccuracy()}"), - } - }, - new ModDisplay - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Both, - ExpansionMode = ExpansionMode.AlwaysExpanded, - DisplayUnrankedText = false, - Current = { Value = score.Mods }, - Scale = new Vector2(0.5f), - } + createStatistic("Max Combo", $"x{score.MaxCombo}"), + createStatistic("Accuracy", $"{score.Accuracy.FormatAccuracy()}"), } + }, + new ModDisplay + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + ExpansionMode = ExpansionMode.AlwaysExpanded, + DisplayUnrankedText = false, + Current = { Value = score.Mods }, + Scale = new Vector2(0.5f), } } } From cfa5a81e7844eb136f7e1fed6a04b3374788709e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 16 May 2020 18:28:25 +0900 Subject: [PATCH 05/33] Cleanup testscene --- osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs index 3b8ce7d837..0dbafb18bc 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs @@ -10,7 +10,6 @@ using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Ranking; -using osu.Game.Screens.Ranking.Expanded; using osu.Game.Tests.Beatmaps; using osu.Game.Users; @@ -18,7 +17,6 @@ namespace osu.Game.Tests.Visual.Ranking { public class TestSceneScorePanel : OsuTestScene { - [Test] public void TestDRank() { From e3c1112b5ab10e981ce59c1c2a851ecdcf7a0dbb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 16 May 2020 19:00:20 +0900 Subject: [PATCH 06/33] Initial integration into results screen --- osu.Game/Screens/Ranking/ResultsScreen.cs | 57 +++++++++++++++++++++-- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index cfba1e6e3e..f2458d9f1f 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -10,6 +11,9 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Rulesets; using osu.Game.Scoring; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Play; @@ -31,11 +35,18 @@ namespace osu.Game.Screens.Ranking [Resolved(CanBeNull = true)] private Player player { get; set; } + [Resolved] + private IAPIProvider api { get; set; } + + [Resolved] + private RulesetStore rulesets { get; set; } + public readonly ScoreInfo Score; private readonly bool allowRetry; private Drawable bottomPanel; + private Container contractedPanels; public ResultsScreen(ScoreInfo score, bool allowRetry = true) { @@ -52,12 +63,29 @@ namespace osu.Game.Screens.Ranking { new ResultsScrollContainer { - Child = new ScorePanel(Score) + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - State = PanelState.Expanded - }, + new ScorePanel(Score) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + State = PanelState.Expanded + }, + new OsuScrollContainer(Direction.Horizontal) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + ScrollbarVisible = false, + Child = contractedPanels = new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both + } + } + } }, bottomPanel = new Container { @@ -105,6 +133,25 @@ namespace osu.Game.Screens.Ranking } } + protected override void LoadComplete() + { + base.LoadComplete(); + + var req = new GetScoresRequest(Score.Beatmap, Score.Ruleset); + + req.Success += r => + { + contractedPanels.ChildrenEnumerable = r.Scores.Select(s => s.CreateScoreInfo(rulesets)).Select(s => new ScorePanel(s) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + State = PanelState.Contracted + }); + }; + + api.Queue(req); + } + public override void OnEntering(IScreen last) { base.OnEntering(last); From 1b8d657eade8b02ace6f42d5d97d45aa55e320c1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 May 2020 23:46:47 +0900 Subject: [PATCH 07/33] Implement score panel list --- .../Visual/Ranking/TestSceneScorePanelList.cs | 69 ++++++++++++++++ osu.Game/Screens/Ranking/ScorePanel.cs | 20 +++-- osu.Game/Screens/Ranking/ScorePanelList.cs | 80 +++++++++++++++++++ 3 files changed, 163 insertions(+), 6 deletions(-) create mode 100644 osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs create mode 100644 osu.Game/Screens/Ranking/ScorePanelList.cs diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs new file mode 100644 index 0000000000..4964af8784 --- /dev/null +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -0,0 +1,69 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking; +using osu.Game.Tests.Beatmaps; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Ranking +{ + public class TestSceneScorePanelList : OsuTestScene + { + public TestSceneScorePanelList() + { + var list = new ScorePanelList + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + + Add(list); + + list.AddScore(createScore()); + list.AddScore(createScore()); + list.AddScore(createScore()); + list.AddScore(createScore()); + list.AddScore(createScore()); + list.AddScore(createScore()); + list.AddScore(createScore()); + list.AddScore(createScore()); + list.AddScore(createScore()); + list.AddScore(createScore()); + list.AddScore(createScore()); + list.AddScore(createScore()); + list.AddScore(createScore()); + list.AddScore(createScore()); + } + + private ScoreInfo createScore() => new ScoreInfo + { + User = new User + { + Id = 2, + Username = "peppy", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }, + Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, + Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, + TotalScore = 2845370, + Accuracy = 0.95, + MaxCombo = 999, + Rank = ScoreRank.S, + Date = DateTimeOffset.Now, + Statistics = + { + { HitResult.Miss, 1 }, + { HitResult.Meh, 50 }, + { HitResult.Good, 100 }, + { HitResult.Great, 300 }, + } + }; + } +} diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index bf57cb4dd9..baca2fd9e1 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Contracted; using osu.Game.Screens.Ranking.Expanded; @@ -75,8 +76,7 @@ namespace osu.Game.Screens.Ranking private static readonly Color4 contracted_middle_layer_colour = Color4Extensions.FromHex("#353535"); public event Action StateChanged; - - private readonly ScoreInfo score; + public readonly ScoreInfo Score; private Container topLayerContainer; private Drawable topLayerBackground; @@ -90,7 +90,7 @@ namespace osu.Game.Screens.Ranking public ScorePanel(ScoreInfo score) { - this.score = score; + Score = score; } [BackgroundDependencyLoader] @@ -189,8 +189,8 @@ namespace osu.Game.Screens.Ranking topLayerBackground.FadeColour(expanded_top_layer_colour, resize_duration, Easing.OutQuint); middleLayerBackground.FadeColour(expanded_middle_layer_colour, resize_duration, Easing.OutQuint); - topLayerContentContainer.Add(middleLayerContent = new ExpandedPanelTopContent(score.User).With(d => d.Alpha = 0)); - middleLayerContentContainer.Add(topLayerContent = new ExpandedPanelMiddleContent(score).With(d => d.Alpha = 0)); + topLayerContentContainer.Add(middleLayerContent = new ExpandedPanelTopContent(Score.User).With(d => d.Alpha = 0)); + middleLayerContentContainer.Add(topLayerContent = new ExpandedPanelMiddleContent(Score).With(d => d.Alpha = 0)); break; case PanelState.Contracted: @@ -199,7 +199,7 @@ namespace osu.Game.Screens.Ranking topLayerBackground.FadeColour(contracted_top_layer_colour, resize_duration, Easing.OutQuint); middleLayerBackground.FadeColour(contracted_middle_layer_colour, resize_duration, Easing.OutQuint); - middleLayerContentContainer.Add(topLayerContent = new ContractedPanelMiddleContent(score).With(d => d.Alpha = 0)); + middleLayerContentContainer.Add(topLayerContent = new ContractedPanelMiddleContent(Score).With(d => d.Alpha = 0)); break; } @@ -222,5 +222,13 @@ namespace osu.Game.Screens.Ranking middleLayerContent?.FadeIn(content_fade_duration); } } + + protected override bool OnClick(ClickEvent e) + { + if (State == PanelState.Contracted) + State = PanelState.Expanded; + + return true; + } } } diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs new file mode 100644 index 0000000000..cc6842b2dd --- /dev/null +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -0,0 +1,80 @@ +// Copyright (c) ppy Pty Ltd . 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 osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; +using osu.Game.Scoring; +using osuTK; + +namespace osu.Game.Screens.Ranking +{ + public class ScorePanelList : CompositeDrawable + { + private readonly Flow panels; + private ScorePanel expandedPanel; + + public ScorePanelList() + { + RelativeSizeAxes = Axes.Both; + + InternalChild = panels = new Flow + { + Anchor = Anchor.Centre, + Origin = Anchor.Custom, + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + }; + } + + public void AddScore(ScoreInfo score) + { + var panel = new ScorePanel(score) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }; + + panel.StateChanged += s => onPanelStateChanged(panel, s); + + // Todo: Temporary + panel.State = expandedPanel == null ? PanelState.Expanded : PanelState.Contracted; + + panels.Add(panel); + } + + public void RemoveScore(ScoreInfo score) => panels.RemoveAll(p => p.Score == score); + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + if (expandedPanel != null) + { + var firstPanel = panels.FlowingChildren.First(); + var target = expandedPanel.DrawPosition.X - firstPanel.DrawPosition.X + expandedPanel.DrawSize.X / 2; + + panels.OriginPosition = new Vector2((float)Interpolation.Lerp(panels.OriginPosition.X, target, Math.Clamp(Math.Abs(Time.Elapsed) / 80, 0, 1)), panels.DrawHeight / 2); + } + } + + private void onPanelStateChanged(ScorePanel panel, PanelState state) + { + if (state == PanelState.Contracted) + return; + + if (expandedPanel != null) + expandedPanel.State = PanelState.Contracted; + expandedPanel = panel; + } + + private class Flow : FillFlowContainer + { + // Todo: Order is wrong. + public override IEnumerable FlowingChildren => AliveInternalChildren.OfType().OrderBy(s => s.Score.TotalScore); + } + } +} From acba1f3ad667fdeae03a1708329e4c49f2ce4006 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 May 2020 23:46:54 +0900 Subject: [PATCH 08/33] Integrate score panel list into results screen --- osu.Game/Screens/Ranking/ResultsScreen.cs | 38 +++++++---------------- 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index f2458d9f1f..652d158fbb 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Ranking private readonly bool allowRetry; private Drawable bottomPanel; - private Container contractedPanels; + private ScorePanelList panels; public ResultsScreen(ScoreInfo score, bool allowRetry = true) { @@ -63,28 +63,9 @@ namespace osu.Game.Screens.Ranking { new ResultsScrollContainer { - Children = new Drawable[] + Child = panels = new ScorePanelList { - new ScorePanel(Score) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - State = PanelState.Expanded - }, - new OsuScrollContainer(Direction.Horizontal) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - Child = contractedPanels = new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both - } - } + RelativeSizeAxes = Axes.Both, } }, bottomPanel = new Container @@ -117,6 +98,8 @@ namespace osu.Game.Screens.Ranking } }; + panels.AddScore(Score); + if (player != null && allowRetry) { buttons.Add(new RetryButton { Width = 300 }); @@ -141,12 +124,13 @@ namespace osu.Game.Screens.Ranking req.Success += r => { - contractedPanels.ChildrenEnumerable = r.Scores.Select(s => s.CreateScoreInfo(rulesets)).Select(s => new ScorePanel(s) + foreach (var s in r.Scores.Select(s => s.CreateScoreInfo(rulesets))) { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - State = PanelState.Contracted - }); + if (s.OnlineScoreID == Score.OnlineScoreID) + continue; + + panels.AddScore(s); + } }; api.Queue(req); From 700b5e0c73c75566a9b73267977111c819e6737c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 May 2020 17:47:14 +0900 Subject: [PATCH 09/33] Adjust design --- .../ContractedPanelMiddleContent.cs | 30 ++++++------------- osu.Game/Screens/Ranking/ScorePanel.cs | 6 ++-- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs index 1d7d5c4130..a263a03a77 100644 --- a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs @@ -65,28 +65,16 @@ namespace osu.Game.Screens.Ranking.Contracted }, Children = new Drawable[] { - // Buffered container is used to prevent 1px bleed outside the masking region - new BufferedContainer + new Box { RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("444") - }, - new UserCoverBackground - { - RelativeSizeAxes = Axes.Both, - User = score.User, - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.5f), Color4Extensions.FromHex("#444")) - }, - } + Colour = Color4Extensions.FromHex("444") + }, + new UserCoverBackground + { + RelativeSizeAxes = Axes.Both, + User = score.User, + Colour = ColourInfo.GradientVertical(Color4.White.Opacity(0.5f), Color4Extensions.FromHex("#444").Opacity(0)) }, new FillFlowContainer { @@ -100,7 +88,7 @@ namespace osu.Game.Screens.Ranking.Contracted { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Size = new Vector2(140), + Size = new Vector2(110), Masking = true, CornerExponent = 2.5f, CornerRadius = 20, diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index baca2fd9e1..305d4ee921 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -23,12 +23,12 @@ namespace osu.Game.Screens.Ranking /// /// Width of the panel when contracted. /// - public const float CONTRACTED_WIDTH = 160; + public const float CONTRACTED_WIDTH = 130; /// /// Height of the panel when contracted. /// - private const float contracted_height = 385; + private const float contracted_height = 355; /// /// Width of the panel when expanded. @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Ranking /// /// Height of the top layer when the panel is contracted. /// - private const float contracted_top_layer_height = 40; + private const float contracted_top_layer_height = 30; /// /// Duration for the panel to resize into its expanded/contracted size. From b6a1d1a2fc5d9a1927b4994861b23c6f46f09fd4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 May 2020 18:07:31 +0900 Subject: [PATCH 10/33] Improve transforms between state changes --- osu.Game/Screens/Ranking/ScorePanel.cs | 8 ++++---- osu.Game/Screens/Ranking/ScorePanelList.cs | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 305d4ee921..2f6146a5e7 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -175,9 +175,6 @@ namespace osu.Game.Screens.Ranking private void updateState() { - topLayerContainer.MoveToY(0, resize_duration, Easing.OutQuint); - middleLayerContainer.MoveToY(0, resize_duration, Easing.OutQuint); - topLayerContent?.FadeOut(content_fade_duration).Expire(); middleLayerContent?.FadeOut(content_fade_duration).Expire(); @@ -203,7 +200,10 @@ namespace osu.Game.Screens.Ranking break; } - using (BeginDelayedSequence(resize_duration + top_layer_expand_delay, true)) + bool topLayerExpanded = topLayerContainer.Y < 0; + + // If the top layer was already expanded, then we don't need to wait for the resize and can instead transform immediately. This looks better when changing the panel state. + using (BeginDelayedSequence(topLayerExpanded ? 0 : resize_duration + top_layer_expand_delay, true)) { switch (state) { diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index cc6842b2dd..894be7e775 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -68,6 +68,7 @@ namespace osu.Game.Screens.Ranking if (expandedPanel != null) expandedPanel.State = PanelState.Contracted; + expandedPanel = panel; } From 9f868be872f0d55d9209536489008606f5dc171d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 May 2020 18:39:22 +0900 Subject: [PATCH 11/33] Create common TestScoreInfo type --- .../TestSceneContractedPanelMiddleContent.cs | 32 +-------- .../TestSceneExpandedPanelMiddleContent.cs | 31 +-------- .../TestSceneExpandedPanelTopContent.cs | 4 +- .../Visual/Ranking/TestSceneResultsScreen.cs | 26 +------- .../Visual/Ranking/TestSceneScorePanel.cs | 65 +++---------------- .../Visual/Ranking/TestSceneScorePanelList.cs | 59 ++++------------- osu.Game/Tests/TestScoreInfo.cs | 50 ++++++++++++++ 7 files changed, 80 insertions(+), 187 deletions(-) create mode 100644 osu.Game/Tests/TestScoreInfo.cs diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs index f7694c10ec..e1e00e3c2b 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -14,10 +13,7 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking.Contracted; @@ -37,7 +33,7 @@ namespace osu.Game.Tests.Visual.Ranking { var author = new User { Username = "mapper_name" }; - AddStep("show example score", () => showPanel(createTestBeatmap(author), createTestScore())); + AddStep("show example score", () => showPanel(createTestBeatmap(author), new TestScoreInfo(new OsuRuleset().RulesetInfo))); AddAssert("mapper name present", () => this.ChildrenOfType().Any(spriteText => spriteText.Text == "mapper_name")); } @@ -45,7 +41,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestMapWithUnknownMapper() { - AddStep("show example score", () => showPanel(createTestBeatmap(null), createTestScore())); + AddStep("show example score", () => showPanel(createTestBeatmap(null), new TestScoreInfo(new OsuRuleset().RulesetInfo))); AddAssert("mapped by text not present", () => this.ChildrenOfType().All(spriteText => !containsAny(spriteText.Text, "mapped", "by"))); @@ -66,30 +62,6 @@ namespace osu.Game.Tests.Visual.Ranking return new TestWorkingBeatmap(beatmap); } - private ScoreInfo createTestScore() => new ScoreInfo - { - User = new User - { - Id = 2, - Username = "peppy", - CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", - }, - Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, - Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, - TotalScore = 999999, - Accuracy = 0.95, - MaxCombo = 999, - Rank = ScoreRank.S, - Date = DateTimeOffset.Now, - Statistics = - { - { HitResult.Miss, 1 }, - { HitResult.Meh, 50 }, - { HitResult.Good, 100 }, - { HitResult.Great, 300 }, - } - }; - private bool containsAny(string text, params string[] stringsToMatch) => stringsToMatch.Any(text.Contains); private class ContractedPanelMiddleContentContainer : Container diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs index 106b4187ee..69511b85c0 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -14,10 +13,7 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking.Expanded; @@ -37,7 +33,7 @@ namespace osu.Game.Tests.Visual.Ranking { var author = new User { Username = "mapper_name" }; - AddStep("show example score", () => showPanel(createTestBeatmap(author), createTestScore())); + AddStep("show example score", () => showPanel(createTestBeatmap(author), new TestScoreInfo(new OsuRuleset().RulesetInfo))); AddAssert("mapper name present", () => this.ChildrenOfType().Any(spriteText => spriteText.Text == "mapper_name")); } @@ -45,7 +41,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestMapWithUnknownMapper() { - AddStep("show example score", () => showPanel(createTestBeatmap(null), createTestScore())); + AddStep("show example score", () => showPanel(createTestBeatmap(null), new TestScoreInfo(new OsuRuleset().RulesetInfo))); AddAssert("mapped by text not present", () => this.ChildrenOfType().All(spriteText => !containsAny(spriteText.Text, "mapped", "by"))); @@ -66,29 +62,6 @@ namespace osu.Game.Tests.Visual.Ranking return new TestWorkingBeatmap(beatmap); } - private ScoreInfo createTestScore() => new ScoreInfo - { - User = new User - { - Id = 2, - Username = "peppy", - }, - Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, - Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, - TotalScore = 999999, - Accuracy = 0.95, - MaxCombo = 999, - Rank = ScoreRank.S, - Date = DateTimeOffset.Now, - Statistics = - { - { HitResult.Miss, 1 }, - { HitResult.Meh, 50 }, - { HitResult.Good, 100 }, - { HitResult.Great, 300 }, - } - }; - private bool containsAny(string text, params string[] stringsToMatch) => stringsToMatch.Any(text.Contains); private class ExpandedPanelMiddleContentContainer : Container diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelTopContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelTopContent.cs index afaa607099..a32bcbe7f0 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelTopContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelTopContent.cs @@ -5,8 +5,8 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Osu; using osu.Game.Screens.Ranking.Expanded; -using osu.Game.Users; using osuTK; namespace osu.Game.Tests.Visual.Ranking @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Ranking RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("#444"), }, - new ExpandedPanelTopContent(new User { Id = 2, Username = "peppy" }), + new ExpandedPanelTopContent(new TestScoreInfo(new OsuRuleset().RulesetInfo).User), } }; } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index aa0ce89d93..242766ad4b 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . 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 NUnit.Framework; using osu.Framework.Allocation; @@ -11,13 +9,10 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; -using osu.Game.Tests.Beatmaps; -using osu.Game.Users; namespace osu.Game.Tests.Visual.Ranking { @@ -41,26 +36,7 @@ namespace osu.Game.Tests.Visual.Ranking Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); } - private TestSoloResults createResultsScreen() => new TestSoloResults(new ScoreInfo - { - TotalScore = 2845370, - Accuracy = 0.98, - MaxCombo = 123, - Rank = ScoreRank.A, - Date = DateTimeOffset.Now, - Statistics = new Dictionary - { - { HitResult.Great, 50 }, - { HitResult.Good, 20 }, - { HitResult.Meh, 50 }, - { HitResult.Miss, 1 } - }, - Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, - User = new User - { - Username = "peppy", - } - }); + private TestSoloResults createResultsScreen() => new TestSoloResults(new TestScoreInfo(new OsuRuleset().RulesetInfo)); [Test] public void ResultsWithoutPlayer() diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs index 0dbafb18bc..fdb77c14a3 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs @@ -1,17 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using NUnit.Framework; using osu.Framework.Graphics; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Ranking; -using osu.Game.Tests.Beatmaps; -using osu.Game.Users; namespace osu.Game.Tests.Visual.Ranking { @@ -20,9 +15,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestDRank() { - var score = createScore(); - score.Accuracy = 0.5; - score.Rank = ScoreRank.D; + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.5, Rank = ScoreRank.D }; addPanelStep(score); } @@ -30,9 +23,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestCRank() { - var score = createScore(); - score.Accuracy = 0.75; - score.Rank = ScoreRank.C; + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.75, Rank = ScoreRank.C }; addPanelStep(score); } @@ -40,9 +31,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestBRank() { - var score = createScore(); - score.Accuracy = 0.85; - score.Rank = ScoreRank.B; + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.85, Rank = ScoreRank.B }; addPanelStep(score); } @@ -50,9 +39,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestARank() { - var score = createScore(); - score.Accuracy = 0.925; - score.Rank = ScoreRank.A; + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A }; addPanelStep(score); } @@ -60,9 +47,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestSRank() { - var score = createScore(); - score.Accuracy = 0.975; - score.Rank = ScoreRank.S; + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.975, Rank = ScoreRank.S }; addPanelStep(score); } @@ -70,9 +55,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAlmostSSRank() { - var score = createScore(); - score.Accuracy = 0.9999; - score.Rank = ScoreRank.S; + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.9999, Rank = ScoreRank.S }; addPanelStep(score); } @@ -80,9 +63,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestSSRank() { - var score = createScore(); - score.Accuracy = 1; - score.Rank = ScoreRank.X; + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 1, Rank = ScoreRank.X }; addPanelStep(score); } @@ -90,9 +71,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAllHitResults() { - var score = createScore(); - score.Statistics[HitResult.Perfect] = 350; - score.Statistics[HitResult.Ok] = 200; + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Statistics = { [HitResult.Perfect] = 350, [HitResult.Ok] = 200 } }; addPanelStep(score); } @@ -100,9 +79,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestContractedPanel() { - var score = createScore(); - score.Accuracy = 0.925; - score.Rank = ScoreRank.A; + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A }; addPanelStep(score, PanelState.Contracted); } @@ -116,29 +93,5 @@ namespace osu.Game.Tests.Visual.Ranking State = state }; }); - - private ScoreInfo createScore() => new ScoreInfo - { - User = new User - { - Id = 2, - Username = "peppy", - CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", - }, - Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, - Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, - TotalScore = 2845370, - Accuracy = 0.95, - MaxCombo = 999, - Rank = ScoreRank.S, - Date = DateTimeOffset.Now, - Statistics = - { - { HitResult.Miss, 1 }, - { HitResult.Meh, 50 }, - { HitResult.Good, 100 }, - { HitResult.Great, 300 }, - } - }; } } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs index 4964af8784..81a9b22992 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -1,16 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Graphics; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; using osu.Game.Screens.Ranking; -using osu.Game.Tests.Beatmaps; -using osu.Game.Users; namespace osu.Game.Tests.Visual.Ranking { @@ -26,44 +19,20 @@ namespace osu.Game.Tests.Visual.Ranking Add(list); - list.AddScore(createScore()); - list.AddScore(createScore()); - list.AddScore(createScore()); - list.AddScore(createScore()); - list.AddScore(createScore()); - list.AddScore(createScore()); - list.AddScore(createScore()); - list.AddScore(createScore()); - list.AddScore(createScore()); - list.AddScore(createScore()); - list.AddScore(createScore()); - list.AddScore(createScore()); - list.AddScore(createScore()); - list.AddScore(createScore()); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); } - - private ScoreInfo createScore() => new ScoreInfo - { - User = new User - { - Id = 2, - Username = "peppy", - CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", - }, - Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, - Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, - TotalScore = 2845370, - Accuracy = 0.95, - MaxCombo = 999, - Rank = ScoreRank.S, - Date = DateTimeOffset.Now, - Statistics = - { - { HitResult.Miss, 1 }, - { HitResult.Meh, 50 }, - { HitResult.Good, 100 }, - { HitResult.Great, 300 }, - } - }; } } diff --git a/osu.Game/Tests/TestScoreInfo.cs b/osu.Game/Tests/TestScoreInfo.cs new file mode 100644 index 0000000000..155129e181 --- /dev/null +++ b/osu.Game/Tests/TestScoreInfo.cs @@ -0,0 +1,50 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Tests.Beatmaps; +using osu.Game.Users; + +namespace osu.Game.Tests +{ + public class TestScoreInfo : ScoreInfo + { + public TestScoreInfo(RulesetInfo ruleset) + { + User = new User + { + Id = 2, + Username = "peppy", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }; + + Beatmap = new TestBeatmap(ruleset).BeatmapInfo; + Mods = new Mod[] { new TestModHardRock(), new TestModDoubleTime() }; + + TotalScore = 2845370; + Accuracy = 0.95; + MaxCombo = 999; + Rank = ScoreRank.S; + Date = DateTimeOffset.Now; + + Statistics[HitResult.Miss] = 1; + Statistics[HitResult.Meh] = 50; + Statistics[HitResult.Good] = 100; + Statistics[HitResult.Great] = 300; + } + + private class TestModHardRock : ModHardRock + { + public override double ScoreMultiplier => 1; + } + + private class TestModDoubleTime : ModDoubleTime + { + public override double ScoreMultiplier => 1; + } + } +} From 45b59f574dcd09ba389adc85ec2619a177f92e5e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 May 2020 18:43:12 +0900 Subject: [PATCH 12/33] Fix TestSceneResultsScreen crashing --- osu.Game/Tests/TestScoreInfo.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Tests/TestScoreInfo.cs b/osu.Game/Tests/TestScoreInfo.cs index 155129e181..1193a29d70 100644 --- a/osu.Game/Tests/TestScoreInfo.cs +++ b/osu.Game/Tests/TestScoreInfo.cs @@ -23,6 +23,8 @@ namespace osu.Game.Tests }; Beatmap = new TestBeatmap(ruleset).BeatmapInfo; + Ruleset = ruleset; + RulesetID = ruleset.ID ?? 0; Mods = new Mod[] { new TestModHardRock(), new TestModDoubleTime() }; TotalScore = 2845370; From 717869225e55f0ff2a6cd27d9f797ffb9f62b868 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 May 2020 19:51:36 +0900 Subject: [PATCH 13/33] Rework list to use a scroll container + add spacing --- osu.Game/Screens/Ranking/ScorePanelList.cs | 60 ++++++++++++++-------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 894be7e775..52a9f27db8 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -1,12 +1,11 @@ // Copyright (c) ppy Pty Ltd . 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 osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Utils; +using osu.Game.Graphics.Containers; using osu.Game.Scoring; using osuTK; @@ -14,19 +13,36 @@ namespace osu.Game.Screens.Ranking { public class ScorePanelList : CompositeDrawable { - private readonly Flow panels; + /// + /// Normal spacing between all panels. + /// + private const float panel_spacing = 5; + + /// + /// Spacing around both sides of the expanded panel. This is added on top of . + /// + private const float expanded_panel_spacing = 15; + + private readonly Flow flow; + private readonly ScrollContainer scroll; + private ScorePanel expandedPanel; public ScorePanelList() { RelativeSizeAxes = Axes.Both; - InternalChild = panels = new Flow + InternalChild = scroll = new OsuScrollContainer(Direction.Horizontal) { - Anchor = Anchor.Centre, - Origin = Anchor.Custom, - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.Both, + Child = flow = new Flow + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(panel_spacing, 0), + AutoSizeAxes = Axes.Both, + } }; } @@ -34,8 +50,8 @@ namespace osu.Game.Screens.Ranking { var panel = new ScorePanel(score) { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, }; panel.StateChanged += s => onPanelStateChanged(panel, s); @@ -43,22 +59,14 @@ namespace osu.Game.Screens.Ranking // Todo: Temporary panel.State = expandedPanel == null ? PanelState.Expanded : PanelState.Contracted; - panels.Add(panel); + flow.Add(panel); } - public void RemoveScore(ScoreInfo score) => panels.RemoveAll(p => p.Score == score); - - protected override void UpdateAfterChildren() + protected override void Update() { - base.UpdateAfterChildren(); + base.Update(); - if (expandedPanel != null) - { - var firstPanel = panels.FlowingChildren.First(); - var target = expandedPanel.DrawPosition.X - firstPanel.DrawPosition.X + expandedPanel.DrawSize.X / 2; - - panels.OriginPosition = new Vector2((float)Interpolation.Lerp(panels.OriginPosition.X, target, Math.Clamp(Math.Abs(Time.Elapsed) / 80, 0, 1)), panels.DrawHeight / 2); - } + flow.Padding = new MarginPadding { Horizontal = DrawWidth / 2f - ScorePanel.EXPANDED_WIDTH / 2f - expanded_panel_spacing }; } private void onPanelStateChanged(ScorePanel panel, PanelState state) @@ -67,9 +75,17 @@ namespace osu.Game.Screens.Ranking return; if (expandedPanel != null) + { + expandedPanel.Margin = new MarginPadding(0); expandedPanel.State = PanelState.Contracted; + } expandedPanel = panel; + expandedPanel.Margin = new MarginPadding { Horizontal = expanded_panel_spacing }; + + float panelOffset = flow.IndexOf(expandedPanel) * (ScorePanel.CONTRACTED_WIDTH + panel_spacing); + + scroll.ScrollTo(panelOffset); } private class Flow : FillFlowContainer From 7b82a5d792d4fae9103bff89de4db4e81a8e5063 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 May 2020 20:48:08 +0900 Subject: [PATCH 14/33] Fix score order --- osu.Game/Screens/Ranking/ScorePanelList.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 52a9f27db8..0e0ed4f60d 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -90,8 +90,7 @@ namespace osu.Game.Screens.Ranking private class Flow : FillFlowContainer { - // Todo: Order is wrong. - public override IEnumerable FlowingChildren => AliveInternalChildren.OfType().OrderBy(s => s.Score.TotalScore); + public override IEnumerable FlowingChildren => AliveInternalChildren.OfType().OrderByDescending(s => s.Score.TotalScore).ThenByDescending(s => s.Score.OnlineScoreID); } } } From d0f74c2b683e949ba780c7d6e8011a184b5468d6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 May 2020 20:48:25 +0900 Subject: [PATCH 15/33] Refactor initial state --- .../Visual/Ranking/TestSceneScorePanelList.cs | 47 ++++++++++------ osu.Game/Screens/Ranking/ResultsScreen.cs | 4 +- osu.Game/Screens/Ranking/ScorePanelList.cs | 54 +++++++++---------- 3 files changed, 58 insertions(+), 47 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs index 81a9b22992..f00bf7e151 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -1,38 +1,51 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Ranking; +using osuTK.Graphics; namespace osu.Game.Tests.Visual.Ranking { public class TestSceneScorePanelList : OsuTestScene { - public TestSceneScorePanelList() + private ScorePanelList list; + + [SetUp] + public void Setup() => Schedule(() => { - var list = new ScorePanelList + Child = list = new ScorePanelList(new TestScoreInfo(new OsuRuleset().RulesetInfo)) { Anchor = Anchor.Centre, Origin = Anchor.Centre, }; - Add(list); + Add(new Box + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = 1, + Colour = Color4.Red + }); + }); - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + [Test] + public void TestSingleScore() + { + } + + [Test] + public void TestManyScores() + { + AddStep("add many scores", () => + { + for (int i = 0; i < 20; i++) + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + }); } } } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 652d158fbb..cdceaa939e 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -63,7 +63,7 @@ namespace osu.Game.Screens.Ranking { new ResultsScrollContainer { - Child = panels = new ScorePanelList + Child = panels = new ScorePanelList(Score) { RelativeSizeAxes = Axes.Both, } @@ -98,8 +98,6 @@ namespace osu.Game.Screens.Ranking } }; - panels.AddScore(Score); - if (player != null && allowRetry) { buttons.Add(new RetryButton { Width = 300 }); diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 0e0ed4f60d..c2fd487767 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Ranking private ScorePanel expandedPanel; - public ScorePanelList() + public ScorePanelList(ScoreInfo initialScore) { RelativeSizeAxes = Axes.Both; @@ -44,48 +44,48 @@ namespace osu.Game.Screens.Ranking AutoSizeAxes = Axes.Both, } }; + + AddScore(initialScore); + ShowScore(initialScore); } public void AddScore(ScoreInfo score) { - var panel = new ScorePanel(score) + flow.Add(new ScorePanel(score) { Anchor = Anchor.Centre, Origin = Anchor.Centre, - }; + }.With(p => + { + p.StateChanged += s => + { + if (s == PanelState.Expanded) + ShowScore(score); + }; + })); + } - panel.StateChanged += s => onPanelStateChanged(panel, s); + public void ShowScore(ScoreInfo score) + { + foreach (var p in flow.Where(p => p.Score != score)) + p.State = PanelState.Contracted; - // Todo: Temporary - panel.State = expandedPanel == null ? PanelState.Expanded : PanelState.Contracted; + if (expandedPanel != null) + expandedPanel.Margin = new MarginPadding(0); - flow.Add(panel); + expandedPanel = flow.Single(p => p.Score == score); + expandedPanel.State = PanelState.Expanded; + expandedPanel.Margin = new MarginPadding { Horizontal = expanded_panel_spacing }; + + float scrollOffset = flow.IndexOf(expandedPanel) * (ScorePanel.CONTRACTED_WIDTH + panel_spacing); + scroll.ScrollTo(scrollOffset); } protected override void Update() { base.Update(); - flow.Padding = new MarginPadding { Horizontal = DrawWidth / 2f - ScorePanel.EXPANDED_WIDTH / 2f - expanded_panel_spacing }; - } - - private void onPanelStateChanged(ScorePanel panel, PanelState state) - { - if (state == PanelState.Contracted) - return; - - if (expandedPanel != null) - { - expandedPanel.Margin = new MarginPadding(0); - expandedPanel.State = PanelState.Contracted; - } - - expandedPanel = panel; - expandedPanel.Margin = new MarginPadding { Horizontal = expanded_panel_spacing }; - - float panelOffset = flow.IndexOf(expandedPanel) * (ScorePanel.CONTRACTED_WIDTH + panel_spacing); - - scroll.ScrollTo(panelOffset); + flow.Padding = new MarginPadding { Horizontal = DrawWidth / 2f - expandedPanel.DrawWidth / 2f - expanded_panel_spacing }; } private class Flow : FillFlowContainer From 45244683de150597b66234e5ee78b78c0f718189 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 May 2020 22:07:06 +0900 Subject: [PATCH 16/33] Fix scrolling (1-frame + maintain scroll position) --- .../Visual/Ranking/TestSceneScorePanelList.cs | 49 +++++++++++- osu.Game/Screens/Ranking/ScorePanel.cs | 71 +++++++++------- osu.Game/Screens/Ranking/ScorePanelList.cs | 80 ++++++++++++++++--- 3 files changed, 154 insertions(+), 46 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs index f00bf7e151..89aef377c8 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -1,10 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Rulesets.Osu; +using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osuTK.Graphics; @@ -12,12 +16,13 @@ namespace osu.Game.Tests.Visual.Ranking { public class TestSceneScorePanelList : OsuTestScene { + private ScoreInfo initialScore; private ScorePanelList list; [SetUp] public void Setup() => Schedule(() => { - Child = list = new ScorePanelList(new TestScoreInfo(new OsuRuleset().RulesetInfo)) + Child = list = new ScorePanelList(initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo)) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -36,16 +41,52 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestSingleScore() { + assertPanelCentred(); } [Test] - public void TestManyScores() + public void TestAddManyScoresAfter() { - AddStep("add many scores", () => + AddStep("add scores", () => { for (int i = 0; i < 20; i++) - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore - i - 1 }); }); + + assertPanelCentred(); } + + [Test] + public void TestAddManyScoresBefore() + { + AddStep("add scores", () => + { + for (int i = 0; i < 20; i++) + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore + i + 1 }); + }); + + assertPanelCentred(); + } + + [Test] + public void TestAddManyPanelsOnBothSides() + { + AddStep("add scores after", () => + { + for (int i = 0; i < 20; i++) + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore - i - 1 }); + + for (int i = 0; i < 20; i++) + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore + i + 1 }); + }); + + assertPanelCentred(); + } + + private void assertPanelCentred() => AddUntilStep("expanded panel centred", () => + { + var expandedPanel = list.ChildrenOfType().Single(p => p.State == PanelState.Expanded); + return Precision.AlmostEquals(expandedPanel.ScreenSpaceDrawQuad.Centre.X, list.ScreenSpaceDrawQuad.Centre.X, 1); + }); } } diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 2f6146a5e7..2933bbddd1 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -78,6 +78,8 @@ namespace osu.Game.Screens.Ranking public event Action StateChanged; public readonly ScoreInfo Score; + private Container content; + private Container topLayerContainer; private Drawable topLayerBackground; private Container topLayerContentContainer; @@ -96,41 +98,46 @@ namespace osu.Game.Screens.Ranking [BackgroundDependencyLoader] private void load() { - InternalChildren = new Drawable[] + InternalChild = content = new Container { - topLayerContainer = new Container + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] { - Name = "Top layer", - RelativeSizeAxes = Axes.X, - Height = 120, - Children = new Drawable[] + topLayerContainer = new Container { - new Container + Name = "Top layer", + RelativeSizeAxes = Axes.X, + Height = 120, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - CornerRadius = 20, - CornerExponent = 2.5f, - Masking = true, - Child = topLayerBackground = new Box { RelativeSizeAxes = Axes.Both } - }, - topLayerContentContainer = new Container { RelativeSizeAxes = Axes.Both } - } - }, - middleLayerContainer = new Container - { - Name = "Middle layer", - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + new Container + { + RelativeSizeAxes = Axes.Both, + CornerRadius = 20, + CornerExponent = 2.5f, + Masking = true, + Child = topLayerBackground = new Box { RelativeSizeAxes = Axes.Both } + }, + topLayerContentContainer = new Container { RelativeSizeAxes = Axes.Both } + } + }, + middleLayerContainer = new Container { - new Container + Name = "Middle layer", + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - CornerRadius = 20, - CornerExponent = 2.5f, - Masking = true, - Child = middleLayerBackground = new Box { RelativeSizeAxes = Axes.Both } - }, - middleLayerContentContainer = new Container { RelativeSizeAxes = Axes.Both } + new Container + { + RelativeSizeAxes = Axes.Both, + CornerRadius = 20, + CornerExponent = 2.5f, + Masking = true, + Child = middleLayerBackground = new Box { RelativeSizeAxes = Axes.Both } + }, + middleLayerContentContainer = new Container { RelativeSizeAxes = Axes.Both } + } } } }; @@ -181,7 +188,7 @@ namespace osu.Game.Screens.Ranking switch (state) { case PanelState.Expanded: - this.ResizeTo(new Vector2(EXPANDED_WIDTH, expanded_height), resize_duration, Easing.OutQuint); + Size = new Vector2(EXPANDED_WIDTH, expanded_height); topLayerBackground.FadeColour(expanded_top_layer_colour, resize_duration, Easing.OutQuint); middleLayerBackground.FadeColour(expanded_middle_layer_colour, resize_duration, Easing.OutQuint); @@ -191,7 +198,7 @@ namespace osu.Game.Screens.Ranking break; case PanelState.Contracted: - this.ResizeTo(new Vector2(CONTRACTED_WIDTH, contracted_height), resize_duration, Easing.OutQuint); + Size = new Vector2(CONTRACTED_WIDTH, contracted_height); topLayerBackground.FadeColour(contracted_top_layer_colour, resize_duration, Easing.OutQuint); middleLayerBackground.FadeColour(contracted_middle_layer_colour, resize_duration, Easing.OutQuint); @@ -200,6 +207,8 @@ namespace osu.Game.Screens.Ranking break; } + content.ResizeTo(Size, resize_duration, Easing.OutQuint); + bool topLayerExpanded = topLayerContainer.Y < 0; // If the top layer was already expanded, then we don't need to wait for the resize and can instead transform immediately. This looks better when changing the panel state. diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index c2fd487767..6dd21ec49d 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Ranking private const float expanded_panel_spacing = 15; private readonly Flow flow; - private readonly ScrollContainer scroll; + private readonly Scroll scroll; private ScorePanel expandedPanel; @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Ranking { RelativeSizeAxes = Axes.Both; - InternalChild = scroll = new OsuScrollContainer(Direction.Horizontal) + InternalChild = scroll = new Scroll { RelativeSizeAxes = Axes.Both, Child = flow = new Flow @@ -46,9 +46,13 @@ namespace osu.Game.Screens.Ranking }; AddScore(initialScore); - ShowScore(initialScore); + presentScore(initialScore); } + /// + /// Adds a to this list. + /// + /// The to add. public void AddScore(ScoreInfo score) { flow.Add(new ScorePanel(score) @@ -60,24 +64,45 @@ namespace osu.Game.Screens.Ranking p.StateChanged += s => { if (s == PanelState.Expanded) - ShowScore(score); + presentScore(score); }; })); + + // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. + // But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel. + if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score)) + { + // A somewhat hacky property is used here because we need to: + // 1) Scroll after the scroll container's visible range is updated. + // 2) Scroll before the scroll container's scroll position is updated. + // Without this, we would have a 1-frame positioning error which looks very jarring. + scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing; + } } - public void ShowScore(ScoreInfo score) + /// + /// Brings a to the centre of the screen and expands it. + /// + /// The to present. + private void presentScore(ScoreInfo score) { + // Contract the old panel. foreach (var p in flow.Where(p => p.Score != score)) + { p.State = PanelState.Contracted; + p.Margin = new MarginPadding(); + } - if (expandedPanel != null) - expandedPanel.Margin = new MarginPadding(0); - + // Expand the new panel. expandedPanel = flow.Single(p => p.Score == score); expandedPanel.State = PanelState.Expanded; expandedPanel.Margin = new MarginPadding { Horizontal = expanded_panel_spacing }; - float scrollOffset = flow.IndexOf(expandedPanel) * (ScorePanel.CONTRACTED_WIDTH + panel_spacing); + // Scroll to the new panel. This is done manually since we need: + // 1) To scroll after the scroll container's visible range is updated. + // 2) To account for the centre anchor/origins of panels. + // In the end, it's easier to compute the scroll position manually. + float scrollOffset = flow.GetPanelIndex(expandedPanel.Score) * (ScorePanel.CONTRACTED_WIDTH + panel_spacing); scroll.ScrollTo(scrollOffset); } @@ -85,12 +110,45 @@ namespace osu.Game.Screens.Ranking { base.Update(); - flow.Padding = new MarginPadding { Horizontal = DrawWidth / 2f - expandedPanel.DrawWidth / 2f - expanded_panel_spacing }; + // Add padding to both sides such that the centre of an expanded panel on either side is in the middle of the screen. + flow.Padding = new MarginPadding { Horizontal = DrawWidth / 2f - ScorePanel.EXPANDED_WIDTH / 2f - expanded_panel_spacing }; } private class Flow : FillFlowContainer { - public override IEnumerable FlowingChildren => AliveInternalChildren.OfType().OrderByDescending(s => s.Score.TotalScore).ThenByDescending(s => s.Score.OnlineScoreID); + public override IEnumerable FlowingChildren => applySorting(AliveInternalChildren); + + public int GetPanelIndex(ScoreInfo score) => applySorting(Children).OfType().TakeWhile(s => s.Score != score).Count(); + + private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() + .OrderByDescending(s => s.Score.TotalScore) + .ThenByDescending(s => s.Score.OnlineScoreID); + } + + private class Scroll : OsuScrollContainer + { + public new float Target => base.Target; + + public Scroll() + : base(Direction.Horizontal) + { + } + + /// + /// The target that will be scrolled to instantaneously next frame. + /// + public float? InstantScrollTarget; + + protected override void UpdateAfterChildren() + { + if (InstantScrollTarget != null) + { + ScrollTo(InstantScrollTarget.Value, false); + InstantScrollTarget = null; + } + + base.UpdateAfterChildren(); + } } } } From f5c80ac2d5c654794219db95fb5e2c8d48b5a7ce Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 May 2020 22:07:24 +0900 Subject: [PATCH 17/33] Remove vertical line --- .../Visual/Ranking/TestSceneScorePanelList.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs index 89aef377c8..b32b3afbda 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -4,13 +4,11 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking; -using osuTK.Graphics; namespace osu.Game.Tests.Visual.Ranking { @@ -27,15 +25,6 @@ namespace osu.Game.Tests.Visual.Ranking Anchor = Anchor.Centre, Origin = Anchor.Centre, }; - - Add(new Box - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Width = 1, - Colour = Color4.Red - }); }); [Test] From 899b9f8060928a5ed60984bc5eb073314ab63692 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 May 2020 22:26:04 +0900 Subject: [PATCH 18/33] Fix incorrect sorting order --- osu.Game/Screens/Ranking/ScorePanelList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 6dd21ec49d..ed6d07d078 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -122,7 +122,7 @@ namespace osu.Game.Screens.Ranking private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() .OrderByDescending(s => s.Score.TotalScore) - .ThenByDescending(s => s.Score.OnlineScoreID); + .ThenBy(s => s.Score.OnlineScoreID); } private class Scroll : OsuScrollContainer From 50059860498d63d2dbe8cd77b5840f2bf628a9da Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 22 May 2020 20:18:47 +0900 Subject: [PATCH 19/33] Cleanup test --- .../TestSceneContractedPanelMiddleContent.cs | 25 +++---------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs index e1e00e3c2b..972ac26b84 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs @@ -9,16 +9,13 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking.Contracted; using osu.Game.Tests.Beatmaps; -using osu.Game.Users; using osuTK; namespace osu.Game.Tests.Visual.Ranking @@ -29,22 +26,9 @@ namespace osu.Game.Tests.Visual.Ranking private RulesetStore rulesetStore { get; set; } [Test] - public void TestMapWithKnownMapper() + public void TestShowPanel() { - var author = new User { Username = "mapper_name" }; - - AddStep("show example score", () => showPanel(createTestBeatmap(author), new TestScoreInfo(new OsuRuleset().RulesetInfo))); - - AddAssert("mapper name present", () => this.ChildrenOfType().Any(spriteText => spriteText.Text == "mapper_name")); - } - - [Test] - public void TestMapWithUnknownMapper() - { - AddStep("show example score", () => showPanel(createTestBeatmap(null), new TestScoreInfo(new OsuRuleset().RulesetInfo))); - - AddAssert("mapped by text not present", () => - this.ChildrenOfType().All(spriteText => !containsAny(spriteText.Text, "mapped", "by"))); + AddStep("show example score", () => showPanel(createTestBeatmap(), new TestScoreInfo(new OsuRuleset().RulesetInfo))); } private void showPanel(WorkingBeatmap workingBeatmap, ScoreInfo score) @@ -52,10 +36,9 @@ namespace osu.Game.Tests.Visual.Ranking Child = new ContractedPanelMiddleContentContainer(workingBeatmap, score); } - private WorkingBeatmap createTestBeatmap(User author) + private WorkingBeatmap createTestBeatmap() { var beatmap = new TestBeatmap(rulesetStore.GetRuleset(0)); - beatmap.Metadata.Author = author; beatmap.Metadata.Title = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap title"; beatmap.Metadata.Artist = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap artist"; @@ -75,7 +58,7 @@ namespace osu.Game.Tests.Visual.Ranking Anchor = Anchor.Centre; Origin = Anchor.Centre; - Size = new Vector2(ScorePanel.CONTRACTED_WIDTH, 700); + Size = new Vector2(ScorePanel.CONTRACTED_WIDTH, 460); Children = new Drawable[] { new Box From 6bcc4c95cc0794260e82a7d70cfe06aa31e7c33f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 22 May 2020 20:19:23 +0900 Subject: [PATCH 20/33] Schedule api callback --- osu.Game/Screens/Ranking/ResultsScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index cdceaa939e..af748d8336 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -120,7 +120,7 @@ namespace osu.Game.Screens.Ranking var req = new GetScoresRequest(Score.Beatmap, Score.Ruleset); - req.Success += r => + req.Success += r => Schedule(() => { foreach (var s in r.Scores.Select(s => s.CreateScoreInfo(rulesets))) { @@ -129,7 +129,7 @@ namespace osu.Game.Screens.Ranking panels.AddScore(s); } - }; + }); api.Queue(req); } From 80388feac4f6bbc7f87b617476c142f55511fa78 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 22 May 2020 20:39:02 +0900 Subject: [PATCH 21/33] Disable scroll user scroll controls in list --- osu.Game/Screens/Ranking/ResultsScreen.cs | 69 +++++++++++++--------- osu.Game/Screens/Ranking/ScorePanelList.cs | 4 ++ 2 files changed, 46 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index af748d8336..133bdcca4a 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -59,42 +59,57 @@ namespace osu.Game.Screens.Ranking { FillFlowContainer buttons; - InternalChildren = new[] + InternalChild = new GridContainer { - new ResultsScrollContainer + RelativeSizeAxes = Axes.Both, + Content = new[] { - Child = panels = new ScorePanelList(Score) + new Drawable[] { - RelativeSizeAxes = Axes.Both, - } - }, - bottomPanel = new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - Height = TwoLayerButton.SIZE_EXTENDED.Y, - Alpha = 0, - Children = new Drawable[] + new ResultsScrollContainer + { + Child = panels = new ScorePanelList(Score) + { + RelativeSizeAxes = Axes.Both, + } + } + }, + new[] { - new Box + bottomPanel = new Container { - RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("#333") - }, - buttons = new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(5), - Direction = FillDirection.Horizontal, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = TwoLayerButton.SIZE_EXTENDED.Y, + Alpha = 0, Children = new Drawable[] { - new ReplayDownloadButton(Score) { Width = 300 }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#333") + }, + buttons = new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(5), + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new ReplayDownloadButton(Score) { Width = 300 }, + } + } } } } + }, + RowDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize) } }; @@ -171,7 +186,7 @@ namespace osu.Game.Screens.Ranking protected override void Update() { base.Update(); - content.Height = Math.Max(768, DrawHeight); + content.Height = Math.Max(768 - TwoLayerButton.SIZE_EXTENDED.Y, DrawHeight); } } } diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index ed6d07d078..97a132b9ff 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -149,6 +149,10 @@ namespace osu.Game.Screens.Ranking base.UpdateAfterChildren(); } + + public override bool HandlePositionalInput => false; + + public override bool HandleNonPositionalInput => false; } } } From aaf5596f9c0f619fc9eb63ab046f21a03885bb45 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 26 May 2020 15:54:07 +0900 Subject: [PATCH 22/33] Cleanup test --- .../TestSceneContractedPanelMiddleContent.cs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs index 972ac26b84..76cfe75b59 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -15,7 +14,6 @@ using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking.Contracted; -using osu.Game.Tests.Beatmaps; using osuTK; namespace osu.Game.Tests.Visual.Ranking @@ -28,7 +26,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestShowPanel() { - AddStep("show example score", () => showPanel(createTestBeatmap(), new TestScoreInfo(new OsuRuleset().RulesetInfo))); + AddStep("show example score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), new TestScoreInfo(new OsuRuleset().RulesetInfo))); } private void showPanel(WorkingBeatmap workingBeatmap, ScoreInfo score) @@ -36,17 +34,6 @@ namespace osu.Game.Tests.Visual.Ranking Child = new ContractedPanelMiddleContentContainer(workingBeatmap, score); } - private WorkingBeatmap createTestBeatmap() - { - var beatmap = new TestBeatmap(rulesetStore.GetRuleset(0)); - beatmap.Metadata.Title = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap title"; - beatmap.Metadata.Artist = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap artist"; - - return new TestWorkingBeatmap(beatmap); - } - - private bool containsAny(string text, params string[] stringsToMatch) => stringsToMatch.Any(text.Contains); - private class ContractedPanelMiddleContentContainer : Container { [Cached] From 1768cbd1319ff8a6ab1da72846c2ec4c0a2ab65d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 26 May 2020 15:56:56 +0900 Subject: [PATCH 23/33] Format score same as expanded panel --- .../Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs index a263a03a77..8cd0e7025e 100644 --- a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs @@ -160,7 +160,7 @@ namespace osu.Game.Screens.Ranking.Contracted { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = score.TotalScore.ToString(), + Text = score.TotalScore.ToString("N0"), Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, fixedWidth: true), Spacing = new Vector2(-1, 0) }, From 906a317a3d35151baae2339a77671fbd82f34231 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 26 May 2020 16:26:53 +0900 Subject: [PATCH 24/33] Reduce casting --- osu.Game/Screens/Ranking/ScorePanelList.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 97a132b9ff..df2c66203b 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -118,11 +118,11 @@ namespace osu.Game.Screens.Ranking { public override IEnumerable FlowingChildren => applySorting(AliveInternalChildren); - public int GetPanelIndex(ScoreInfo score) => applySorting(Children).OfType().TakeWhile(s => s.Score != score).Count(); + public int GetPanelIndex(ScoreInfo score) => applySorting(Children).TakeWhile(s => s.Score != score).Count(); - private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() - .OrderByDescending(s => s.Score.TotalScore) - .ThenBy(s => s.Score.OnlineScoreID); + private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() + .OrderByDescending(s => s.Score.TotalScore) + .ThenBy(s => s.Score.OnlineScoreID); } private class Scroll : OsuScrollContainer From a1ece4f308d51317b5d2c6d25df1555a434f9f00 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 26 May 2020 16:26:58 +0900 Subject: [PATCH 25/33] Add expansion/contraction test --- .../Visual/Ranking/TestSceneScorePanel.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs index fdb77c14a3..250fdc5ebd 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs @@ -12,6 +12,8 @@ namespace osu.Game.Tests.Visual.Ranking { public class TestSceneScorePanel : OsuTestScene { + private ScorePanel panel; + [Test] public void TestDRank() { @@ -84,9 +86,24 @@ namespace osu.Game.Tests.Visual.Ranking addPanelStep(score, PanelState.Contracted); } + [Test] + public void TestExpandAndContract() + { + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A }; + + addPanelStep(score, PanelState.Contracted); + AddWaitStep("wait for transition", 10); + + AddStep("expand panel", () => panel.State = PanelState.Expanded); + AddWaitStep("wait for transition", 10); + + AddStep("contract panel", () => panel.State = PanelState.Contracted); + AddWaitStep("wait for transition", 10); + } + private void addPanelStep(ScoreInfo score, PanelState state = PanelState.Expanded) => AddStep("add panel", () => { - Child = new ScorePanel(score) + Child = panel = new ScorePanel(score) { Anchor = Anchor.Centre, Origin = Anchor.Centre, From c86a003ef94eccc990c6e13f7d0ea7159de03ec6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 26 May 2020 16:27:41 +0900 Subject: [PATCH 26/33] Adjust transition for smaller sizes --- osu.Game/Screens/Ranking/ScorePanel.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 2933bbddd1..a99b48e8f0 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -102,12 +102,14 @@ namespace osu.Game.Screens.Ranking { Anchor = Anchor.Centre, Origin = Anchor.Centre, + Size = new Vector2(40), Children = new Drawable[] { topLayerContainer = new Container { Name = "Top layer", RelativeSizeAxes = Axes.X, + Alpha = 0, Height = 120, Children = new Drawable[] { @@ -214,6 +216,8 @@ namespace osu.Game.Screens.Ranking // If the top layer was already expanded, then we don't need to wait for the resize and can instead transform immediately. This looks better when changing the panel state. using (BeginDelayedSequence(topLayerExpanded ? 0 : resize_duration + top_layer_expand_delay, true)) { + topLayerContainer.FadeIn(); + switch (state) { case PanelState.Expanded: From de0b6ec9f1941c64e19ab486748484351871074b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 26 May 2020 17:00:41 +0900 Subject: [PATCH 27/33] Create abstract implementation --- osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/Screens/Play/ReplayPlayer.cs | 2 +- osu.Game/Screens/Ranking/ResultsScreen.cs | 27 ++++++++-------- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 32 +++++++++++++++++++ osu.Game/Screens/Select/PlaySongSelect.cs | 2 +- 5 files changed, 49 insertions(+), 16 deletions(-) create mode 100644 osu.Game/Screens/Ranking/SoloResultsScreen.cs diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 3d4b20bec4..36198bcc65 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -479,7 +479,7 @@ namespace osu.Game.Screens.Play protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !GameplayClockContainer.IsPaused.Value; - protected virtual ResultsScreen CreateResults(ScoreInfo score) => new ResultsScreen(score); + protected virtual ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score); #region Fail Logic diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index f0c76163f1..b443603128 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -30,7 +30,7 @@ namespace osu.Game.Screens.Play this.Push(CreateResults(DrawableRuleset.ReplayScore.ScoreInfo)); } - protected override ResultsScreen CreateResults(ScoreInfo score) => new ResultsScreen(score, false); + protected override ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, false); protected override ScoreInfo CreateScore() => score.ScoreInfo; } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 133bdcca4a..8db9cdc547 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -12,8 +12,6 @@ using osu.Framework.Screens; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; -using osu.Game.Online.API.Requests; -using osu.Game.Rulesets; using osu.Game.Scoring; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Play; @@ -21,7 +19,7 @@ using osuTK; namespace osu.Game.Screens.Ranking { - public class ResultsScreen : OsuScreen + public abstract class ResultsScreen : OsuScreen { protected const float BACKGROUND_BLUR = 20; @@ -38,9 +36,6 @@ namespace osu.Game.Screens.Ranking [Resolved] private IAPIProvider api { get; set; } - [Resolved] - private RulesetStore rulesets { get; set; } - public readonly ScoreInfo Score; private readonly bool allowRetry; @@ -133,22 +128,28 @@ namespace osu.Game.Screens.Ranking { base.LoadComplete(); - var req = new GetScoresRequest(Score.Beatmap, Score.Ruleset); - - req.Success += r => Schedule(() => + var req = FetchScores(scores => Schedule(() => { - foreach (var s in r.Scores.Select(s => s.CreateScoreInfo(rulesets))) + foreach (var s in scores) { if (s.OnlineScoreID == Score.OnlineScoreID) continue; panels.AddScore(s); } - }); + })); - api.Queue(req); + if (req != null) + api.Queue(req); } + /// + /// Performs a fetch/refresh of scores to be displayed. + /// + /// A callback which should be called when fetching is completed. Scheduling is not required. + /// An responsible for the fetch operation. This will be queued and performed automatically. + protected virtual APIRequest FetchScores(Action> scoresCallback) => null; + public override void OnEntering(IScreen last) { base.OnEntering(last); diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs new file mode 100644 index 0000000000..2b00748ed8 --- /dev/null +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . 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 osu.Framework.Allocation; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Rulesets; +using osu.Game.Scoring; + +namespace osu.Game.Screens.Ranking +{ + public class SoloResultsScreen : ResultsScreen + { + [Resolved] + private RulesetStore rulesets { get; set; } + + public SoloResultsScreen(ScoreInfo score, bool allowRetry = true) + : base(score, allowRetry) + { + } + + protected override APIRequest FetchScores(Action> scoresCallback) + { + var req = new GetScoresRequest(Score.Beatmap, Score.Ruleset); + req.Success += r => scoresCallback?.Invoke(r.Scores.Select(s => s.CreateScoreInfo(rulesets))); + return req; + } + } +} diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 21ddc5685d..0a4c0e2085 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Select } protected void PresentScore(ScoreInfo score) => - FinaliseSelection(score.Beatmap, score.Ruleset, () => this.Push(new ResultsScreen(score))); + FinaliseSelection(score.Beatmap, score.Ruleset, () => this.Push(new SoloResultsScreen(score))); protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); From c07a33b24fc02d33a6dd15da10605d16bb958005 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 26 May 2020 17:31:50 +0900 Subject: [PATCH 28/33] Fix ctor accessibility --- osu.Game/Screens/Ranking/ResultsScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 8db9cdc547..97b56d22eb 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Ranking private Drawable bottomPanel; private ScorePanelList panels; - public ResultsScreen(ScoreInfo score, bool allowRetry = true) + protected ResultsScreen(ScoreInfo score, bool allowRetry = true) { Score = score; this.allowRetry = allowRetry; From a55ce261307b560ab303db9cbcf183e6c50bca43 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 28 May 2020 20:46:17 +0900 Subject: [PATCH 29/33] Allow null score --- .../Visual/Ranking/TestSceneScorePanelList.cs | 38 ++++++++++++++++++- osu.Game/Screens/Ranking/ScorePanelList.cs | 7 +++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs index b32b3afbda..a204d2bcbc 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -9,10 +9,11 @@ using osu.Framework.Utils; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking; +using osuTK.Input; namespace osu.Game.Tests.Visual.Ranking { - public class TestSceneScorePanelList : OsuTestScene + public class TestSceneScorePanelList : OsuManualInputManagerTestScene { private ScoreInfo initialScore; private ScorePanelList list; @@ -72,6 +73,41 @@ namespace osu.Game.Tests.Visual.Ranking assertPanelCentred(); } + [Test] + public void TestNullScore() + { + AddStep("create panel with null score", () => + { + Child = list = new ScorePanelList(null) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + }); + + AddStep("add many panels", () => + { + for (int i = 0; i < 20; i++) + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore - i - 1 }); + + for (int i = 0; i < 20; i++) + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore + i + 1 }); + }); + + AddWaitStep("wait for panel animation", 5); + + AddAssert("no panel selected", () => list.ChildrenOfType().All(p => p.State != PanelState.Expanded)); + + AddStep("expand second panel", () => + { + var expandedPanel = list.ChildrenOfType().OrderBy(p => p.DrawPosition.X).ElementAt(1); + InputManager.MoveMouseTo(expandedPanel); + InputManager.Click(MouseButton.Left); + }); + + assertPanelCentred(); + } + private void assertPanelCentred() => AddUntilStep("expanded panel centred", () => { var expandedPanel = list.ChildrenOfType().Single(p => p.State == PanelState.Expanded); diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index df2c66203b..a30d911f04 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -45,8 +45,11 @@ namespace osu.Game.Screens.Ranking } }; - AddScore(initialScore); - presentScore(initialScore); + if (initialScore != null) + { + AddScore(initialScore); + presentScore(initialScore); + } } /// From 666cbd0f40f2cf59a78d644caa3c1cb6a53b9588 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 28 May 2020 21:08:47 +0900 Subject: [PATCH 30/33] Allow selected score to be programmatically changed --- .../Visual/Ranking/TestSceneScorePanelList.cs | 128 ++++++++++++------ osu.Game/Screens/Ranking/ResultsScreen.cs | 3 +- osu.Game/Screens/Ranking/ScorePanelList.cs | 51 ++++--- 3 files changed, 120 insertions(+), 62 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs index a204d2bcbc..588cddea7d 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; @@ -9,58 +10,121 @@ using osu.Framework.Utils; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking; -using osuTK.Input; namespace osu.Game.Tests.Visual.Ranking { public class TestSceneScorePanelList : OsuManualInputManagerTestScene { - private ScoreInfo initialScore; private ScorePanelList list; - [SetUp] - public void Setup() => Schedule(() => + [Test] + public void TestEmptyList() { - Child = list = new ScorePanelList(initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo)) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }; - }); + createListStep(() => new ScorePanelList()); + } [Test] - public void TestSingleScore() + public void TestEmptyListWithSelectedScore() { + createListStep(() => new ScorePanelList + { + SelectedScore = { Value = new TestScoreInfo(new OsuRuleset().RulesetInfo) } + }); + } + + [Test] + public void TestAddPanelAfterSelectingScore() + { + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo); + + createListStep(() => new ScorePanelList + { + SelectedScore = { Value = score } + }); + + AddStep("add panel", () => list.AddScore(score)); + + assertScoreState(score, true); + assertPanelCentred(); + } + + [Test] + public void TestAddPanelBeforeSelectingScore() + { + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo); + + createListStep(() => new ScorePanelList()); + + AddStep("add panel", () => list.AddScore(score)); + + assertScoreState(score, false); + assertPanelCentred(); + + AddStep("select score", () => list.SelectedScore.Value = score); + + assertScoreState(score, true); assertPanelCentred(); } [Test] public void TestAddManyScoresAfter() { - AddStep("add scores", () => + var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); + + createListStep(() => new ScorePanelList()); + + AddStep("add initial panel and select", () => + { + list.AddScore(initialScore); + list.SelectedScore.Value = initialScore; + }); + + AddStep("add many scores", () => { for (int i = 0; i < 20; i++) list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore - i - 1 }); }); + assertScoreState(initialScore, true); assertPanelCentred(); } [Test] public void TestAddManyScoresBefore() { + var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); + + createListStep(() => new ScorePanelList()); + + AddStep("add initial panel and select", () => + { + list.AddScore(initialScore); + list.SelectedScore.Value = initialScore; + }); + AddStep("add scores", () => { for (int i = 0; i < 20; i++) list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore + i + 1 }); }); + assertScoreState(initialScore, true); assertPanelCentred(); } [Test] public void TestAddManyPanelsOnBothSides() { + var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); + + createListStep(() => new ScorePanelList()); + + AddStep("add initial panel and select", () => + { + list.AddScore(initialScore); + list.SelectedScore.Value = initialScore; + }); + AddStep("add scores after", () => { for (int i = 0; i < 20; i++) @@ -70,42 +134,19 @@ namespace osu.Game.Tests.Visual.Ranking list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore + i + 1 }); }); + assertScoreState(initialScore, true); assertPanelCentred(); } - [Test] - public void TestNullScore() + private void createListStep(Func creationFunc) { - AddStep("create panel with null score", () => + AddStep("create list", () => Child = list = creationFunc().With(d => { - Child = list = new ScorePanelList(null) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }; - }); + d.Anchor = Anchor.Centre; + d.Origin = Anchor.Centre; + })); - AddStep("add many panels", () => - { - for (int i = 0; i < 20; i++) - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore - i - 1 }); - - for (int i = 0; i < 20; i++) - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore + i + 1 }); - }); - - AddWaitStep("wait for panel animation", 5); - - AddAssert("no panel selected", () => list.ChildrenOfType().All(p => p.State != PanelState.Expanded)); - - AddStep("expand second panel", () => - { - var expandedPanel = list.ChildrenOfType().OrderBy(p => p.DrawPosition.X).ElementAt(1); - InputManager.MoveMouseTo(expandedPanel); - InputManager.Click(MouseButton.Left); - }); - - assertPanelCentred(); + AddUntilStep("wait for load", () => list.IsLoaded); } private void assertPanelCentred() => AddUntilStep("expanded panel centred", () => @@ -113,5 +154,8 @@ namespace osu.Game.Tests.Visual.Ranking var expandedPanel = list.ChildrenOfType().Single(p => p.State == PanelState.Expanded); return Precision.AlmostEquals(expandedPanel.ScreenSpaceDrawQuad.Centre.X, list.ScreenSpaceDrawQuad.Centre.X, 1); }); + + private void assertScoreState(ScoreInfo score, bool expanded) + => AddUntilStep($"correct score expanded = {expanded}", () => (list.ChildrenOfType().Single(p => p.Score == score).State == PanelState.Expanded) == expanded); } } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 97b56d22eb..a4d1a3c26b 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -63,9 +63,10 @@ namespace osu.Game.Screens.Ranking { new ResultsScrollContainer { - Child = panels = new ScorePanelList(Score) + Child = panels = new ScorePanelList { RelativeSizeAxes = Axes.Both, + SelectedScore = { Value = Score } } } }, diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index a30d911f04..89f5a47ee8 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; @@ -23,12 +24,13 @@ namespace osu.Game.Screens.Ranking /// private const float expanded_panel_spacing = 15; + public readonly Bindable SelectedScore = new Bindable(); + private readonly Flow flow; private readonly Scroll scroll; - private ScorePanel expandedPanel; - public ScorePanelList(ScoreInfo initialScore) + public ScorePanelList() { RelativeSizeAxes = Axes.Both; @@ -44,12 +46,13 @@ namespace osu.Game.Screens.Ranking AutoSizeAxes = Axes.Both, } }; + } - if (initialScore != null) - { - AddScore(initialScore); - presentScore(initialScore); - } + protected override void LoadComplete() + { + base.LoadComplete(); + + SelectedScore.BindValueChanged(selectedScoreChanged, true); } /// @@ -67,19 +70,24 @@ namespace osu.Game.Screens.Ranking p.StateChanged += s => { if (s == PanelState.Expanded) - presentScore(score); + SelectedScore.Value = p.Score; }; })); - // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. - // But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel. - if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score)) + if (SelectedScore.Value == score) + selectedScoreChanged(new ValueChangedEvent(SelectedScore.Value, SelectedScore.Value)); + else { - // A somewhat hacky property is used here because we need to: - // 1) Scroll after the scroll container's visible range is updated. - // 2) Scroll before the scroll container's scroll position is updated. - // Without this, we would have a 1-frame positioning error which looks very jarring. - scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing; + // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. + // But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel. + if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score)) + { + // A somewhat hacky property is used here because we need to: + // 1) Scroll after the scroll container's visible range is updated. + // 2) Scroll before the scroll container's scroll position is updated. + // Without this, we would have a 1-frame positioning error which looks very jarring. + scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing; + } } } @@ -87,17 +95,22 @@ namespace osu.Game.Screens.Ranking /// Brings a to the centre of the screen and expands it. /// /// The to present. - private void presentScore(ScoreInfo score) + private void selectedScoreChanged(ValueChangedEvent score) { // Contract the old panel. - foreach (var p in flow.Where(p => p.Score != score)) + foreach (var p in flow.Where(p => p.Score != score.OldValue)) { p.State = PanelState.Contracted; p.Margin = new MarginPadding(); } + // Find the panel corresponding to the new score. + expandedPanel = flow.SingleOrDefault(p => p.Score == score.NewValue); + + if (expandedPanel == null) + return; + // Expand the new panel. - expandedPanel = flow.Single(p => p.Score == score); expandedPanel.State = PanelState.Expanded; expandedPanel.Margin = new MarginPadding { Horizontal = expanded_panel_spacing }; From ad99d854682af9e1404ff4a4e6f1d38e41a10ab1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 28 May 2020 21:29:16 +0900 Subject: [PATCH 31/33] Resolve several positioning errors --- .../Visual/Ranking/TestSceneScorePanelList.cs | 69 ++++++++++++++++--- osu.Game/Screens/Ranking/ScorePanelList.cs | 15 +++- 2 files changed, 71 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs index 588cddea7d..e65dcb19b1 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -10,6 +10,7 @@ using osu.Framework.Utils; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking; +using osuTK.Input; namespace osu.Game.Tests.Visual.Ranking { @@ -45,7 +46,7 @@ namespace osu.Game.Tests.Visual.Ranking AddStep("add panel", () => list.AddScore(score)); assertScoreState(score, true); - assertPanelCentred(); + assertExpandedPanelCentred(); } [Test] @@ -58,16 +59,30 @@ namespace osu.Game.Tests.Visual.Ranking AddStep("add panel", () => list.AddScore(score)); assertScoreState(score, false); - assertPanelCentred(); + assertFirstPanelCentred(); AddStep("select score", () => list.SelectedScore.Value = score); assertScoreState(score, true); - assertPanelCentred(); + assertExpandedPanelCentred(); } [Test] - public void TestAddManyScoresAfter() + public void TestAddManyNonExpandedPanels() + { + createListStep(() => new ScorePanelList()); + + AddStep("add many scores", () => + { + for (int i = 0; i < 20; i++) + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + }); + + assertFirstPanelCentred(); + } + + [Test] + public void TestAddManyScoresAfterExpandedPanel() { var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); @@ -86,11 +101,11 @@ namespace osu.Game.Tests.Visual.Ranking }); assertScoreState(initialScore, true); - assertPanelCentred(); + assertExpandedPanelCentred(); } [Test] - public void TestAddManyScoresBefore() + public void TestAddManyScoresBeforeExpandedPanel() { var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); @@ -109,11 +124,11 @@ namespace osu.Game.Tests.Visual.Ranking }); assertScoreState(initialScore, true); - assertPanelCentred(); + assertExpandedPanelCentred(); } [Test] - public void TestAddManyPanelsOnBothSides() + public void TestAddManyPanelsOnBothSidesOfExpandedPanel() { var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); @@ -135,7 +150,36 @@ namespace osu.Game.Tests.Visual.Ranking }); assertScoreState(initialScore, true); - assertPanelCentred(); + assertExpandedPanelCentred(); + } + + [Test] + public void TestSelectMultipleScores() + { + var firstScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); + var secondScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); + + createListStep(() => new ScorePanelList()); + + AddStep("add scores and select first", () => + { + list.AddScore(firstScore); + list.AddScore(secondScore); + list.SelectedScore.Value = firstScore; + }); + + assertScoreState(firstScore, true); + assertScoreState(secondScore, false); + + AddStep("select second score", () => + { + InputManager.MoveMouseTo(list.ChildrenOfType().Single(p => p.Score == secondScore)); + InputManager.Click(MouseButton.Left); + }); + + assertScoreState(firstScore, false); + assertScoreState(secondScore, true); + assertExpandedPanelCentred(); } private void createListStep(Func creationFunc) @@ -149,13 +193,16 @@ namespace osu.Game.Tests.Visual.Ranking AddUntilStep("wait for load", () => list.IsLoaded); } - private void assertPanelCentred() => AddUntilStep("expanded panel centred", () => + private void assertExpandedPanelCentred() => AddUntilStep("expanded panel centred", () => { var expandedPanel = list.ChildrenOfType().Single(p => p.State == PanelState.Expanded); return Precision.AlmostEquals(expandedPanel.ScreenSpaceDrawQuad.Centre.X, list.ScreenSpaceDrawQuad.Centre.X, 1); }); + private void assertFirstPanelCentred() + => AddUntilStep("first panel centred", () => Precision.AlmostEquals(list.ChildrenOfType().First().ScreenSpaceDrawQuad.Centre.X, list.ScreenSpaceDrawQuad.Centre.X, 1)); + private void assertScoreState(ScoreInfo score, bool expanded) - => AddUntilStep($"correct score expanded = {expanded}", () => (list.ChildrenOfType().Single(p => p.Score == score).State == PanelState.Expanded) == expanded); + => AddUntilStep($"score expanded = {expanded}", () => (list.ChildrenOfType().Single(p => p.Score == score).State == PanelState.Expanded) == expanded); } } diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 89f5a47ee8..18db3f2af4 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -98,7 +98,7 @@ namespace osu.Game.Screens.Ranking private void selectedScoreChanged(ValueChangedEvent score) { // Contract the old panel. - foreach (var p in flow.Where(p => p.Score != score.OldValue)) + foreach (var p in flow.Where(p => p.Score == score.OldValue)) { p.State = PanelState.Contracted; p.Margin = new MarginPadding(); @@ -126,8 +126,19 @@ namespace osu.Game.Screens.Ranking { base.Update(); + float offset = DrawWidth / 2f; + // Add padding to both sides such that the centre of an expanded panel on either side is in the middle of the screen. - flow.Padding = new MarginPadding { Horizontal = DrawWidth / 2f - ScorePanel.EXPANDED_WIDTH / 2f - expanded_panel_spacing }; + + if (SelectedScore.Value != null) + { + // The expanded panel has extra padding applied to it, so it needs to be included into the offset. + offset -= ScorePanel.EXPANDED_WIDTH / 2f + expanded_panel_spacing; + } + else + offset -= ScorePanel.CONTRACTED_WIDTH / 2f; + + flow.Padding = new MarginPadding { Horizontal = offset }; } private class Flow : FillFlowContainer From 47d5974f04c77d1a4e1b59beeb2bb85892fbba33 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 28 May 2020 21:40:01 +0900 Subject: [PATCH 32/33] Improve results screen behaviour when changing selected score --- .../Screens/Ranking/ReplayDownloadButton.cs | 3 ++ osu.Game/Screens/Ranking/ResultsScreen.cs | 28 +++++++++++-------- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 2 +- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs index a36c86eafc..347fcb5f6e 100644 --- a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -13,6 +14,8 @@ namespace osu.Game.Screens.Ranking { public class ReplayDownloadButton : DownloadTrackingComposite { + public Bindable Score => Model; + private DownloadButton button; private ShakeContainer shakeContainer; diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index a4d1a3c26b..fbb9b95478 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -30,16 +31,17 @@ namespace osu.Game.Screens.Ranking protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value); + public readonly Bindable SelectedScore = new Bindable(); + + public readonly ScoreInfo Score; + private readonly bool allowRetry; + [Resolved(CanBeNull = true)] private Player player { get; set; } [Resolved] private IAPIProvider api { get; set; } - public readonly ScoreInfo Score; - - private readonly bool allowRetry; - private Drawable bottomPanel; private ScorePanelList panels; @@ -47,6 +49,8 @@ namespace osu.Game.Screens.Ranking { Score = score; this.allowRetry = allowRetry; + + SelectedScore.Value = score; } [BackgroundDependencyLoader] @@ -66,7 +70,7 @@ namespace osu.Game.Screens.Ranking Child = panels = new ScorePanelList { RelativeSizeAxes = Axes.Both, - SelectedScore = { Value = Score } + SelectedScore = { BindTarget = SelectedScore } } } }, @@ -95,7 +99,11 @@ namespace osu.Game.Screens.Ranking Direction = FillDirection.Horizontal, Children = new Drawable[] { - new ReplayDownloadButton(Score) { Width = 300 }, + new ReplayDownloadButton(null) + { + Score = { BindTarget = SelectedScore }, + Width = 300 + }, } } } @@ -109,6 +117,9 @@ namespace osu.Game.Screens.Ranking } }; + if (Score != null) + panels.AddScore(Score); + if (player != null && allowRetry) { buttons.Add(new RetryButton { Width = 300 }); @@ -132,12 +143,7 @@ namespace osu.Game.Screens.Ranking var req = FetchScores(scores => Schedule(() => { foreach (var s in scores) - { - if (s.OnlineScoreID == Score.OnlineScoreID) - continue; - panels.AddScore(s); - } })); if (req != null) diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 2b00748ed8..3ae723683a 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Ranking protected override APIRequest FetchScores(Action> scoresCallback) { var req = new GetScoresRequest(Score.Beatmap, Score.Ruleset); - req.Success += r => scoresCallback?.Invoke(r.Scores.Select(s => s.CreateScoreInfo(rulesets))); + req.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineScoreID != Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets))); return req; } } From 013461377e89730ee381499d816c3e9bd3c4887d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 28 May 2020 21:46:02 +0900 Subject: [PATCH 33/33] Fix potential nullref --- .../Gameplay/TestSceneReplayDownloadButton.cs | 17 +++++++++++++++++ .../Screens/Ranking/ReplayDownloadButton.cs | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index a35437a286..1809332bce 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -28,6 +28,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep(@"locally available state", () => downloadButton.SetDownloadState(DownloadState.LocallyAvailable)); AddStep(@"not downloaded state", () => downloadButton.SetDownloadState(DownloadState.NotDownloaded)); createButton(false); + createButtonNoScore(); } private void createButton(bool withReplay) @@ -40,6 +41,22 @@ namespace osu.Game.Tests.Visual.Gameplay Origin = Anchor.Centre, }; }); + + AddUntilStep("wait for load", () => downloadButton.IsLoaded); + } + + private void createButtonNoScore() + { + AddStep("create button with null score", () => + { + Child = downloadButton = new TestReplayDownloadButton(null) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + }); + + AddUntilStep("wait for load", () => downloadButton.IsLoaded); } private ScoreInfo getScoreInfo(bool replayAvailable) diff --git a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs index 347fcb5f6e..9d4e3af230 100644 --- a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Ranking if (State.Value == DownloadState.LocallyAvailable) return ReplayAvailability.Local; - if (!string.IsNullOrEmpty(Model.Value.Hash)) + if (!string.IsNullOrEmpty(Model.Value?.Hash)) return ReplayAvailability.Online; return ReplayAvailability.NotAvailable;