1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-05 10:23:20 +08:00

Merge branch 'master' into improve-chat-test

This commit is contained in:
Dan Balasescu 2020-03-18 11:30:48 +09:00 committed by GitHub
commit 04991315f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 2517 additions and 1500 deletions

View File

@ -93,7 +93,7 @@ JetBrains ReSharper InspectCode is also used for wider rule sets. You can run it
We welcome all contributions, but keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention of having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time to ensure no effort is wasted.
If you're unsure of what you can help with, check out the [list of open issues](https://github.com/ppy/osu/issues) (especially those with the ["good first issue"](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22) label).
If you're unsure of what you can help with, check out the [list of open issues](https://github.com/ppy/osu/issues) (especially those with the ["good first issue"](https://github.com/ppy/osu/issues?q=is%3Aopen+label%3Agood-first-issue+sort%3Aupdated-desc) label).
Before starting, please make sure you are familiar with the [development and testing](https://github.com/ppy/osu-framework/wiki/Development-and-Testing) procedure we have set up. New component development, and where possible, bug fixing and debugging existing components **should always be done under VisualTests**.

View File

@ -52,6 +52,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.315.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.314.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.317.0" />
</ItemGroup>
</Project>

View File

@ -417,7 +417,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestImportWithDuplicateHashes()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportNestedStructure)))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithDuplicateHashes)))
{
try
{

View File

@ -27,6 +27,7 @@ using osu.Game.Screens;
using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Screens.Ranking;
using osu.Game.Screens.Select;
using osu.Game.Tests.Resources;
using osu.Game.Users;
@ -203,7 +204,7 @@ namespace osu.Game.Tests.Visual.Background
}
/// <summary>
/// Check if the visual settings container removes user dim when suspending <see cref="Player"/> for <see cref="SoloResults"/>
/// Check if the visual settings container removes user dim when suspending <see cref="Player"/> for <see cref="ResultsScreen"/>
/// </summary>
[Test]
public void TransitionTest()
@ -335,7 +336,7 @@ namespace osu.Game.Tests.Visual.Background
public bool IsBackgroundCurrent() => ((FadeAccessibleBackground)Background).IsCurrentScreen();
}
private class FadeAccessibleResults : SoloResults
private class FadeAccessibleResults : ResultsScreen
{
public FadeAccessibleResults(ScoreInfo score)
: base(score)

View File

@ -11,7 +11,7 @@ using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Game.Rulesets;
using osu.Game.Screens.Ranking.Pages;
using osu.Game.Screens.Ranking;
namespace osu.Game.Tests.Visual.Gameplay
{

View File

@ -1,106 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Scoring;
using osu.Game.Screens.Multi.Match.Components;
using osu.Game.Screens.Multi.Ranking;
using osu.Game.Screens.Multi.Ranking.Pages;
using osu.Game.Screens.Multi.Ranking.Types;
using osu.Game.Screens.Ranking;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchResults : MultiplayerTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(MatchResults),
typeof(RoomLeaderboardPageInfo),
typeof(RoomLeaderboardPage)
};
[Resolved]
private BeatmapManager beatmaps { get; set; }
[BackgroundDependencyLoader]
private void load()
{
var beatmapInfo = beatmaps.QueryBeatmap(b => b.RulesetID == 0);
if (beatmapInfo != null)
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
Room.RoomID.Value = 1;
Room.Name.Value = "an awesome room";
LoadScreen(new TestMatchResults(new ScoreInfo
{
User = new User { Id = 10 },
}));
}
private class TestMatchResults : MatchResults
{
public TestMatchResults(ScoreInfo score)
: base(score)
{
}
protected override IEnumerable<IResultPageInfo> CreateResultPages() => new[] { new TestRoomLeaderboardPageInfo(Score, Beatmap.Value) };
}
private class TestRoomLeaderboardPageInfo : RoomLeaderboardPageInfo
{
private readonly ScoreInfo score;
private readonly WorkingBeatmap beatmap;
public TestRoomLeaderboardPageInfo(ScoreInfo score, WorkingBeatmap beatmap)
: base(score, beatmap)
{
this.score = score;
this.beatmap = beatmap;
}
public override ResultsPage CreatePage() => new TestRoomLeaderboardPage(score, beatmap);
}
private class TestRoomLeaderboardPage : RoomLeaderboardPage
{
public TestRoomLeaderboardPage(ScoreInfo score, WorkingBeatmap beatmap)
: base(score, beatmap)
{
}
protected override MatchLeaderboard CreateLeaderboard() => new TestMatchLeaderboard();
}
private class TestMatchLeaderboard : RoomLeaderboardPage.ResultsMatchLeaderboard
{
protected override APIRequest FetchScores(Action<IEnumerable<APIUserScoreAggregate>> scoresCallback)
{
var scores = Enumerable.Range(0, 50).Select(createRoomScore).ToArray();
scoresCallback?.Invoke(scores);
ScoresLoaded?.Invoke(scores);
return null;
}
private APIUserScoreAggregate createRoomScore(int id) => new APIUserScoreAggregate
{
User = new User { Id = id, Username = $"User {id}" },
Accuracy = 0.98,
TotalScore = 987654,
TotalAttempts = 13,
CompletedBeatmaps = 5
};
}
}
}

View File

@ -0,0 +1,155 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
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.Expanded.Accuracy;
using osu.Game.Tests.Beatmaps;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Tests.Visual.Ranking
{
public class TestSceneAccuracyCircle : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(AccuracyCircle),
typeof(RankBadge),
typeof(RankNotch),
typeof(RankText),
typeof(SmoothCircularProgress)
};
[Test]
public void TestDRank()
{
var score = createScore();
score.Accuracy = 0.5;
score.Rank = ScoreRank.D;
addCircleStep(score);
}
[Test]
public void TestCRank()
{
var score = createScore();
score.Accuracy = 0.75;
score.Rank = ScoreRank.C;
addCircleStep(score);
}
[Test]
public void TestBRank()
{
var score = createScore();
score.Accuracy = 0.85;
score.Rank = ScoreRank.B;
addCircleStep(score);
}
[Test]
public void TestARank()
{
var score = createScore();
score.Accuracy = 0.925;
score.Rank = ScoreRank.A;
addCircleStep(score);
}
[Test]
public void TestSRank()
{
var score = createScore();
score.Accuracy = 0.975;
score.Rank = ScoreRank.S;
addCircleStep(score);
}
[Test]
public void TestAlmostSSRank()
{
var score = createScore();
score.Accuracy = 0.9999;
score.Rank = ScoreRank.S;
addCircleStep(score);
}
[Test]
public void TestSSRank()
{
var score = createScore();
score.Accuracy = 1;
score.Rank = ScoreRank.X;
addCircleStep(score);
}
private void addCircleStep(ScoreInfo score) => AddStep("add panel", () =>
{
Children = new Drawable[]
{
new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(500, 700),
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#555"), Color4Extensions.FromHex("#333"))
}
}
},
new AccuracyCircle(score)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(230)
}
};
});
private ScoreInfo createScore() => new ScoreInfo
{
User = new User
{
Id = 2,
Username = "peppy",
},
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 },
}
};
}
}

View File

@ -0,0 +1,130 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using 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.Expanded;
using osu.Game.Screens.Ranking.Expanded.Accuracy;
using osu.Game.Screens.Ranking.Expanded.Statistics;
using osu.Game.Tests.Beatmaps;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Tests.Visual.Ranking
{
public class TestSceneExpandedPanelMiddleContent : OsuTestScene
{
[Resolved]
private RulesetStore rulesetStore { get; set; }
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(ExpandedPanelMiddleContent),
typeof(AccuracyCircle),
typeof(AccuracyStatistic),
typeof(ComboStatistic),
typeof(CounterStatistic),
typeof(StarRatingDisplay),
typeof(StatisticDisplay),
typeof(TotalScoreCounter)
};
[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<OsuSpriteText>().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<OsuSpriteText>().All(spriteText => !containsAny(spriteText.Text, "mapped", "by")));
}
private void showPanel(WorkingBeatmap workingBeatmap, ScoreInfo score)
{
Child = new ExpandedPanelMiddleContentContainer(workingBeatmap, score);
}
private WorkingBeatmap createTestBeatmap(User author)
{
var beatmap = new TestBeatmap(rulesetStore.GetRuleset(0));
beatmap.Metadata.Author = author;
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
{
[Cached]
private Bindable<WorkingBeatmap> workingBeatmap { get; set; }
public ExpandedPanelMiddleContentContainer(WorkingBeatmap beatmap, ScoreInfo score)
{
workingBeatmap = new Bindable<WorkingBeatmap>(beatmap);
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
Size = new Vector2(500, 700);
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex("#444"),
},
new ExpandedPanelMiddleContent(score)
};
}
}
}
}

View File

@ -0,0 +1,35 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Screens.Ranking.Expanded;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Tests.Visual.Ranking
{
public class TestSceneExpandedPanelTopContent : OsuTestScene
{
public TestSceneExpandedPanelTopContent()
{
Child = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(500, 200),
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex("#444"),
},
new ExpandedPanelTopContent(new User { Id = 2, Username = "peppy" }),
}
};
}
}
}

View File

@ -10,29 +10,27 @@ using osu.Framework.Graphics;
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.Screens.Ranking.Pages;
using osu.Game.Tests.Beatmaps;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Gameplay
namespace osu.Game.Tests.Visual.Ranking
{
[TestFixture]
public class TestSceneResults : ScreenTestScene
public class TestSceneResultsScreen : ScreenTestScene
{
private BeatmapManager beatmaps;
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Results),
typeof(ResultsPage),
typeof(ScoreResultsPage),
typeof(ResultsScreen),
typeof(RetryButton),
typeof(ReplayDownloadButton),
typeof(LocalLeaderboardPage),
typeof(TestPlayer)
};
@ -65,6 +63,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{ HitResult.Meh, 50 },
{ HitResult.Miss, 1 }
},
Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo,
User = new User
{
Username = "peppy",
@ -119,7 +118,7 @@ namespace osu.Game.Tests.Visual.Gameplay
}
}
private class TestSoloResults : SoloResults
private class TestSoloResults : ResultsScreen
{
public HotkeyRetryOverlay RetryOverlay;

View File

@ -0,0 +1,143 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using 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.Screens.Ranking.Expanded;
using osu.Game.Tests.Beatmaps;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Ranking
{
public class TestSceneScorePanel : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(ScorePanel),
typeof(PanelState),
typeof(ExpandedPanelMiddleContent),
typeof(ExpandedPanelTopContent),
};
[Test]
public void TestDRank()
{
var score = createScore();
score.Accuracy = 0.5;
score.Rank = ScoreRank.D;
addPanelStep(score);
}
[Test]
public void TestCRank()
{
var score = createScore();
score.Accuracy = 0.75;
score.Rank = ScoreRank.C;
addPanelStep(score);
}
[Test]
public void TestBRank()
{
var score = createScore();
score.Accuracy = 0.85;
score.Rank = ScoreRank.B;
addPanelStep(score);
}
[Test]
public void TestARank()
{
var score = createScore();
score.Accuracy = 0.925;
score.Rank = ScoreRank.A;
addPanelStep(score);
}
[Test]
public void TestSRank()
{
var score = createScore();
score.Accuracy = 0.975;
score.Rank = ScoreRank.S;
addPanelStep(score);
}
[Test]
public void TestAlmostSSRank()
{
var score = createScore();
score.Accuracy = 0.9999;
score.Rank = ScoreRank.S;
addPanelStep(score);
}
[Test]
public void TestSSRank()
{
var score = createScore();
score.Accuracy = 1;
score.Rank = ScoreRank.X;
addPanelStep(score);
}
[Test]
public void TestAllHitResults()
{
var score = createScore();
score.Statistics[HitResult.Perfect] = 350;
score.Statistics[HitResult.Ok] = 200;
addPanelStep(score);
}
private void addPanelStep(ScoreInfo score) => AddStep("add panel", () =>
{
Child = new ScorePanel(score)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
State = PanelState.Expanded
};
});
private ScoreInfo createScore() => new ScoreInfo
{
User = new User
{
Id = 2,
Username = "peppy",
},
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 },
}
};
}
}

View File

@ -0,0 +1,32 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Screens.Ranking.Expanded;
namespace osu.Game.Tests.Visual.Ranking
{
public class TestSceneStarRatingDisplay : OsuTestScene
{
public TestSceneStarRatingDisplay()
{
Child = new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 1.23 }),
new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 2.34 }),
new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 3.45 }),
new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 4.56 }),
new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 5.67 }),
new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 6.78 }),
new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 10.11 }),
}
};
}
}
}

View File

@ -0,0 +1,40 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Game.Tournament.Components;
using osu.Game.Tournament.Models;
namespace osu.Game.Tournament.Tests.Components
{
public class TestSceneRoundDisplay : TournamentTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(DrawableTournamentHeaderText),
typeof(DrawableTournamentHeaderLogo),
};
public TestSceneRoundDisplay()
{
Children = new Drawable[]
{
new RoundDisplay(new TournamentMatch
{
Round =
{
Value = new TournamentRound
{
Name = { Value = "Test Round" }
}
}
})
{
Margin = new MarginPadding(20)
}
};
}
}
}

View File

@ -11,9 +11,13 @@ namespace osu.Game.Tournament.Components
{
public class DrawableTournamentHeaderText : CompositeDrawable
{
public DrawableTournamentHeaderText()
public DrawableTournamentHeaderText(bool center = true)
{
InternalChild = new TextSprite();
InternalChild = new TextSprite
{
Anchor = center ? Anchor.Centre : Anchor.TopLeft,
Origin = center ? Anchor.Centre : Anchor.TopLeft,
};
Height = 22;
RelativeSizeAxes = Axes.X;
@ -27,9 +31,6 @@ namespace osu.Game.Tournament.Components
RelativeSizeAxes = Axes.Both;
FillMode = FillMode.Fit;
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
Texture = textures.Get("header-text");
}
}

View File

@ -12,19 +12,27 @@ namespace osu.Game.Tournament.Components
{
public RoundDisplay(TournamentMatch match)
{
AutoSizeAxes = Axes.Both;
AutoSizeAxes = Axes.Y;
RelativeSizeAxes = Axes.X;
InternalChildren = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new DrawableTournamentHeaderText(),
new DrawableTournamentHeaderText(false)
{
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
},
new TournamentSpriteText
{
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
Text = match.Round.Value?.Name.Value ?? "Unknown Round",
Font = OsuFont.Torus.With(size: 26, weight: FontWeight.SemiBold)
},

View File

@ -35,7 +35,9 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
private void load(LadderInfo ladder)
{
currentMatch.BindTo(ladder.CurrentMatch);
currentMatch.BindValueChanged(matchChanged, true);
currentMatch.BindValueChanged(matchChanged);
updateMatch();
}
private void matchChanged(ValueChangedEvent<TournamentMatch> match)
@ -43,10 +45,19 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
currentTeamScore.UnbindBindings();
currentTeam.UnbindBindings();
if (match.NewValue != null)
Scheduler.AddOnce(updateMatch);
}
private void updateMatch()
{
var match = currentMatch.Value;
if (match != null)
{
currentTeamScore.BindTo(teamColour == TeamColour.Red ? match.NewValue.Team1Score : match.NewValue.Team2Score);
currentTeam.BindTo(teamColour == TeamColour.Red ? match.NewValue.Team1 : match.NewValue.Team2);
match.StartMatch();
currentTeamScore.BindTo(teamColour == TeamColour.Red ? match.Team1Score : match.Team2Score);
currentTeam.BindTo(teamColour == TeamColour.Red ? match.Team1 : match.Team2);
}
// team may change to same team, which means score is not in a good state.

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
@ -60,8 +61,9 @@ namespace osu.Game.Beatmaps
/// </summary>
/// <param name="ruleset">The <see cref="RulesetInfo"/> to create a playable <see cref="IBeatmap"/> for.</param>
/// <param name="mods">The <see cref="Mod"/>s to apply to the <see cref="IBeatmap"/>.</param>
/// <param name="timeout">The maximum length in milliseconds to wait for load to complete. Defaults to 10,000ms.</param>
/// <returns>The converted <see cref="IBeatmap"/>.</returns>
/// <exception cref="BeatmapInvalidForRulesetException">If <see cref="Beatmap"/> could not be converted to <paramref name="ruleset"/>.</exception>
IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList<Mod> mods = null);
IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList<Mod> mods = null, TimeSpan? timeout = null);
}
}

View File

@ -83,55 +83,81 @@ namespace osu.Game.Beatmaps
/// <returns>The applicable <see cref="IBeatmapConverter"/>.</returns>
protected virtual IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => ruleset.CreateBeatmapConverter(beatmap);
public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList<Mod> mods = null)
public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList<Mod> mods = null, TimeSpan? timeout = null)
{
mods ??= Array.Empty<Mod>();
var rulesetInstance = ruleset.CreateInstance();
IBeatmapConverter converter = CreateBeatmapConverter(Beatmap, rulesetInstance);
// Check if the beatmap can be converted
if (Beatmap.HitObjects.Count > 0 && !converter.CanConvert())
throw new BeatmapInvalidForRulesetException($"{nameof(Beatmaps.Beatmap)} can not be converted for the ruleset (ruleset: {ruleset.InstantiationInfo}, converter: {converter}).");
// Apply conversion mods
foreach (var mod in mods.OfType<IApplicableToBeatmapConverter>())
mod.ApplyToBeatmapConverter(converter);
// Convert
IBeatmap converted = converter.Convert();
// Apply difficulty mods
if (mods.Any(m => m is IApplicableToDifficulty))
using (var cancellationSource = new CancellationTokenSource(timeout ?? TimeSpan.FromSeconds(10)))
{
converted.BeatmapInfo = converted.BeatmapInfo.Clone();
converted.BeatmapInfo.BaseDifficulty = converted.BeatmapInfo.BaseDifficulty.Clone();
mods ??= Array.Empty<Mod>();
foreach (var mod in mods.OfType<IApplicableToDifficulty>())
mod.ApplyToDifficulty(converted.BeatmapInfo.BaseDifficulty);
}
var rulesetInstance = ruleset.CreateInstance();
IBeatmapProcessor processor = rulesetInstance.CreateBeatmapProcessor(converted);
IBeatmapConverter converter = CreateBeatmapConverter(Beatmap, rulesetInstance);
processor?.PreProcess();
// Check if the beatmap can be converted
if (Beatmap.HitObjects.Count > 0 && !converter.CanConvert())
throw new BeatmapInvalidForRulesetException($"{nameof(Beatmaps.Beatmap)} can not be converted for the ruleset (ruleset: {ruleset.InstantiationInfo}, converter: {converter}).");
// Compute default values for hitobjects, including creating nested hitobjects in-case they're needed
foreach (var obj in converted.HitObjects)
obj.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty);
// Apply conversion mods
foreach (var mod in mods.OfType<IApplicableToBeatmapConverter>())
{
if (cancellationSource.IsCancellationRequested)
throw new BeatmapLoadTimeoutException(BeatmapInfo);
foreach (var mod in mods.OfType<IApplicableToHitObject>())
{
mod.ApplyToBeatmapConverter(converter);
}
// Convert
IBeatmap converted = converter.Convert();
// Apply difficulty mods
if (mods.Any(m => m is IApplicableToDifficulty))
{
converted.BeatmapInfo = converted.BeatmapInfo.Clone();
converted.BeatmapInfo.BaseDifficulty = converted.BeatmapInfo.BaseDifficulty.Clone();
foreach (var mod in mods.OfType<IApplicableToDifficulty>())
{
if (cancellationSource.IsCancellationRequested)
throw new BeatmapLoadTimeoutException(BeatmapInfo);
mod.ApplyToDifficulty(converted.BeatmapInfo.BaseDifficulty);
}
}
IBeatmapProcessor processor = rulesetInstance.CreateBeatmapProcessor(converted);
processor?.PreProcess();
// Compute default values for hitobjects, including creating nested hitobjects in-case they're needed
foreach (var obj in converted.HitObjects)
mod.ApplyToHitObject(obj);
{
if (cancellationSource.IsCancellationRequested)
throw new BeatmapLoadTimeoutException(BeatmapInfo);
obj.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty);
}
foreach (var mod in mods.OfType<IApplicableToHitObject>())
{
foreach (var obj in converted.HitObjects)
{
if (cancellationSource.IsCancellationRequested)
throw new BeatmapLoadTimeoutException(BeatmapInfo);
mod.ApplyToHitObject(obj);
}
}
processor?.PostProcess();
foreach (var mod in mods.OfType<IApplicableToBeatmap>())
{
cancellationSource.Token.ThrowIfCancellationRequested();
mod.ApplyToBeatmap(converted);
}
return converted;
}
processor?.PostProcess();
foreach (var mod in mods.OfType<IApplicableToBeatmap>())
mod.ApplyToBeatmap(converted);
return converted;
}
private CancellationTokenSource loadCancellation = new CancellationTokenSource();
@ -297,5 +323,13 @@ namespace osu.Game.Beatmaps
private void recreate() => lazy = new Lazy<T>(valueFactory, LazyThreadSafetyMode.ExecutionAndPublication);
}
private class BeatmapLoadTimeoutException : TimeoutException
{
public BeatmapLoadTimeoutException(BeatmapInfo beatmapInfo)
: base($"Timed out while loading beatmap ({beatmapInfo}).")
{
}
}
}
}

View File

@ -3,6 +3,7 @@
using osu.Framework.Extensions.Color4Extensions;
using osu.Game.Beatmaps;
using osu.Game.Scoring;
using osuTK.Graphics;
namespace osu.Game.Graphics
@ -37,6 +38,35 @@ namespace osu.Game.Graphics
}
}
/// <summary>
/// Retrieves the colour for a <see cref="ScoreRank"/>.
/// </summary>
public static Color4 ForRank(ScoreRank rank)
{
switch (rank)
{
case ScoreRank.XH:
case ScoreRank.X:
return Color4Extensions.FromHex(@"ce1c9d");
case ScoreRank.SH:
case ScoreRank.S:
return Color4Extensions.FromHex(@"00a8b5");
case ScoreRank.A:
return Color4Extensions.FromHex(@"7cce14");
case ScoreRank.B:
return Color4Extensions.FromHex(@"e3b130");
case ScoreRank.C:
return Color4Extensions.FromHex(@"f18252");
default:
return Color4Extensions.FromHex(@"e95353");
}
}
// See https://github.com/ppy/osu-web/blob/master/resources/assets/less/colors.less
public readonly Color4 PurpleLighter = Color4Extensions.FromHex(@"eeeeff");
public readonly Color4 PurpleLight = Color4Extensions.FromHex(@"aa88ff");

View File

@ -43,6 +43,18 @@ namespace osu.Game.Graphics.Sprites
set => blurredText.Colour = value;
}
public Vector2 Spacing
{
get => spriteText.Spacing;
set => spriteText.Spacing = blurredText.Spacing = value;
}
public bool UseFullGlyphHeight
{
get => spriteText.UseFullGlyphHeight;
set => spriteText.UseFullGlyphHeight = blurredText.UseFullGlyphHeight = value;
}
public GlowingSpriteText()
{
AutoSizeAxes = Axes.Both;

View File

@ -28,7 +28,7 @@ namespace osu.Game.Online.Leaderboards
FillMode = FillMode.Fit;
FillAspectRatio = 2;
var rankColour = getRankColour();
var rankColour = OsuColour.ForRank(rank);
InternalChild = new DrawSizePreservingFillContainer
{
TargetDrawSize = new Vector2(64, 32),
@ -59,7 +59,7 @@ namespace osu.Game.Online.Leaderboards
Padding = new MarginPadding { Top = 5 },
Colour = getRankNameColour(),
Font = OsuFont.Numeric.With(size: 25),
Text = getRankName(),
Text = GetRankName(rank),
ShadowColour = Color4.Black.Opacity(0.3f),
ShadowOffset = new Vector2(0, 0.08f),
Shadow = true,
@ -69,36 +69,7 @@ namespace osu.Game.Online.Leaderboards
};
}
private string getRankName() => rank.GetDescription().TrimEnd('+');
/// <summary>
/// Retrieves the grade background colour.
/// </summary>
private Color4 getRankColour()
{
switch (rank)
{
case ScoreRank.XH:
case ScoreRank.X:
return Color4Extensions.FromHex(@"ce1c9d");
case ScoreRank.SH:
case ScoreRank.S:
return Color4Extensions.FromHex(@"00a8b5");
case ScoreRank.A:
return Color4Extensions.FromHex(@"7cce14");
case ScoreRank.B:
return Color4Extensions.FromHex(@"e3b130");
case ScoreRank.C:
return Color4Extensions.FromHex(@"f18252");
default:
return Color4Extensions.FromHex(@"e95353");
}
}
public static string GetRankName(ScoreRank rank) => rank.GetDescription().TrimEnd('+');
/// <summary>
/// Retrieves the grade text colour.

View File

@ -148,7 +148,7 @@ namespace osu.Game.Overlays.News
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = OsuFont.GetFont(Typeface.Torus, 12, FontWeight.Black, false, false),
Font = OsuFont.GetFont(Typeface.Torus, 12, FontWeight.Bold, false, false),
Text = date.ToString("d MMM yyy").ToUpper(),
Margin = new MarginPadding
{

View File

@ -84,13 +84,13 @@ namespace osu.Game.Overlays.Notifications
new OsuSpriteText
{
Text = titleText.ToUpperInvariant(),
Font = OsuFont.GetFont(weight: FontWeight.Black)
Font = OsuFont.GetFont(weight: FontWeight.Bold)
},
countDrawable = new OsuSpriteText
{
Text = "3",
Colour = colours.Yellow,
Font = OsuFont.GetFont(weight: FontWeight.Black)
Font = OsuFont.GetFont(weight: FontWeight.Bold)
},
}
},

View File

@ -53,7 +53,7 @@ namespace osu.Game.Overlays.OSD
{
Padding = new MarginPadding(10),
Name = "Description",
Font = OsuFont.GetFont(size: 14, weight: FontWeight.Black),
Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold),
Spacing = new Vector2(1, 0),
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,

View File

@ -59,7 +59,7 @@ namespace osu.Game.Overlays
new OsuSpriteText
{
Text = MainText,
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Black),
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold),
},
new OsuSpriteText
{

View File

@ -80,7 +80,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
{
Text = "ACCOUNT",
Margin = new MarginPadding { Bottom = 5 },
Font = OsuFont.GetFont(weight: FontWeight.Black),
Font = OsuFont.GetFont(weight: FontWeight.Bold),
},
form = new LoginForm
{

View File

@ -54,7 +54,7 @@ namespace osu.Game.Overlays.Settings
{
Text = Header.ToUpperInvariant(),
Margin = new MarginPadding { Bottom = 10, Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS },
Font = OsuFont.GetFont(weight: FontWeight.Black),
Font = OsuFont.GetFont(weight: FontWeight.Bold),
},
FlowContent
});

View File

@ -23,6 +23,7 @@ using osuTK.Input;
using System.Collections.Generic;
using osu.Framework;
using osu.Framework.Input.Bindings;
using osu.Framework.Logging;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Cursor;
using osu.Game.Input.Bindings;
@ -86,7 +87,18 @@ namespace osu.Game.Screens.Edit
// todo: remove caching of this and consume via editorBeatmap?
dependencies.Cache(beatDivisor);
playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset);
try
{
playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset);
}
catch (Exception e)
{
Logger.Error(e, "Could not load beatmap successfully!");
// couldn't load, hard abort!
this.Exit();
return;
}
AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap));
dependencies.CacheAs(editorBeatmap);

View File

@ -152,7 +152,7 @@ namespace osu.Game.Screens.Edit.Timing
public HeaderText(string text)
{
Text = text.ToUpper();
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Black);
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold);
}
}

View File

@ -14,9 +14,7 @@ using osu.Game.Online.API.Requests;
using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets;
using osu.Game.Scoring;
using osu.Game.Screens.Multi.Ranking;
using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking;
namespace osu.Game.Screens.Multi.Play
{
@ -115,7 +113,5 @@ namespace osu.Game.Screens.Multi.Play
Exited = null;
}
protected override Results CreateResults(ScoreInfo score) => new MatchResults(score);
}
}

View File

@ -1,26 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Game.Scoring;
using osu.Game.Screens.Multi.Ranking.Types;
using osu.Game.Screens.Ranking;
using osu.Game.Screens.Ranking.Types;
namespace osu.Game.Screens.Multi.Ranking
{
public class MatchResults : Results
{
public MatchResults(ScoreInfo score)
: base(score)
{
}
protected override IEnumerable<IResultPageInfo> CreateResultPages() => new IResultPageInfo[]
{
new ScoreOverviewPageInfo(Score, Beatmap.Value),
new LocalLeaderboardPageInfo(Score, Beatmap.Value),
new RoomLeaderboardPageInfo(Score, Beatmap.Value),
};
}
}

View File

@ -1,135 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Internal;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Lists;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Leaderboards;
using osu.Game.Online.Multiplayer;
using osu.Game.Scoring;
using osu.Game.Screens.Multi.Match.Components;
using osu.Game.Screens.Ranking;
namespace osu.Game.Screens.Multi.Ranking.Pages
{
public class RoomLeaderboardPage : ResultsPage
{
[Resolved]
private OsuColour colours { get; set; }
private TextFlowContainer rankText;
[Resolved(typeof(Room), nameof(Room.Name))]
private Bindable<string> name { get; set; }
public RoomLeaderboardPage(ScoreInfo score, WorkingBeatmap beatmap)
: base(score, beatmap)
{
}
[BackgroundDependencyLoader]
private void load()
{
MatchLeaderboard leaderboard;
Children = new Drawable[]
{
new Box
{
Colour = colours.Gray6,
RelativeSizeAxes = Axes.Both,
},
new BufferedContainer
{
RelativeSizeAxes = Axes.Both,
BackgroundColour = colours.Gray6,
Child = leaderboard = CreateLeaderboard()
},
rankText = new TextFlowContainer
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
Width = 0.5f,
AutoSizeAxes = Axes.Y,
Y = 50,
TextAnchor = Anchor.TopCentre
},
};
leaderboard.Origin = Anchor.Centre;
leaderboard.Anchor = Anchor.Centre;
leaderboard.RelativeSizeAxes = Axes.Both;
leaderboard.Height = 0.8f;
leaderboard.Y = 55;
leaderboard.ScoresLoaded = scoresLoaded;
}
private void scoresLoaded(IEnumerable<APIUserScoreAggregate> scores)
{
void gray(SpriteText s) => s.Colour = colours.GrayC;
void white(SpriteText s)
{
s.Font = s.Font.With(size: s.Font.Size * 1.4f);
s.Colour = colours.GrayF;
}
rankText.AddText(name + "\n", white);
rankText.AddText("You are placed ", gray);
int index = scores.IndexOf(new APIUserScoreAggregate { User = Score.User }, new FuncEqualityComparer<APIUserScoreAggregate>((s1, s2) => s1.User.Id.Equals(s2.User.Id)));
rankText.AddText($"#{index + 1} ", s =>
{
s.Font = s.Font.With(Typeface.Torus, weight: FontWeight.Bold);
s.Colour = colours.YellowDark;
});
rankText.AddText("in the room!", gray);
}
protected virtual MatchLeaderboard CreateLeaderboard() => new ResultsMatchLeaderboard();
public class ResultsMatchLeaderboard : MatchLeaderboard
{
protected override bool FadeTop => true;
protected override LeaderboardScore CreateDrawableScore(APIUserScoreAggregate model, int index)
=> new ResultsMatchLeaderboardScore(model, index);
protected override FillFlowContainer<LeaderboardScore> CreateScoreFlow()
{
var flow = base.CreateScoreFlow();
flow.Padding = new MarginPadding
{
Top = LeaderboardScore.HEIGHT * 2,
Bottom = LeaderboardScore.HEIGHT * 3,
};
return flow;
}
private class ResultsMatchLeaderboardScore : MatchLeaderboardScore
{
public ResultsMatchLeaderboardScore(APIUserScoreAggregate score, int rank)
: base(score, rank)
{
}
[BackgroundDependencyLoader]
private void load()
{
}
}
}
}
}

View File

@ -1,29 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Scoring;
using osu.Game.Screens.Multi.Ranking.Pages;
using osu.Game.Screens.Ranking;
namespace osu.Game.Screens.Multi.Ranking.Types
{
public class RoomLeaderboardPageInfo : IResultPageInfo
{
private readonly ScoreInfo score;
private readonly WorkingBeatmap beatmap;
public RoomLeaderboardPageInfo(ScoreInfo score, WorkingBeatmap beatmap)
{
this.score = score;
this.beatmap = beatmap;
}
public IconUsage Icon => FontAwesome.Solid.Users;
public string Name => "Room Leaderboard";
public virtual ResultsPage CreatePage() => new RoomLeaderboardPage(score, beatmap);
}
}

View File

@ -30,7 +30,7 @@ namespace osu.Game.Screens.Play.Break
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = "current progress".ToUpperInvariant(),
Font = OsuFont.GetFont(weight: FontWeight.Black, size: 15),
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 15),
},
new FillFlowContainer
{

View File

@ -401,14 +401,18 @@ namespace osu.Game.Screens.Play
protected virtual ScoreInfo CreateScore()
{
var score = DrawableRuleset.ReplayScore?.ScoreInfo ?? new ScoreInfo
var score = new ScoreInfo
{
Beatmap = Beatmap.Value.BeatmapInfo,
Ruleset = rulesetInfo,
Mods = Mods.Value.ToArray(),
User = api.LocalUser.Value,
};
if (DrawableRuleset.ReplayScore != null)
score.User = DrawableRuleset.ReplayScore.ScoreInfo?.User ?? new GuestUser();
else
score.User = api.LocalUser.Value;
ScoreProcessor.PopulateScore(score);
return score;
@ -416,7 +420,7 @@ namespace osu.Game.Screens.Play
protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !GameplayClockContainer.IsPaused.Value;
protected virtual Results CreateResults(ScoreInfo score) => new SoloResults(score);
protected virtual ResultsScreen CreateResults(ScoreInfo score) => new ResultsScreen(score);
#region Fail Logic

View File

@ -1,24 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Game.Scoring;
using osu.Game.Screens.Ranking;
using osu.Game.Screens.Ranking.Types;
namespace osu.Game.Screens.Play
{
public class SoloResults : Results
{
public SoloResults(ScoreInfo score)
: base(score)
{
}
protected override IEnumerable<IResultPageInfo> CreateResultPages() => new IResultPageInfo[]
{
new ScoreOverviewPageInfo(Score, Beatmap.Value),
new LocalLeaderboardPageInfo(Score, Beatmap.Value)
};
}
}

View File

@ -0,0 +1,253 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Utils;
using osu.Game.Graphics;
using osu.Game.Scoring;
using osuTK;
namespace osu.Game.Screens.Ranking.Expanded.Accuracy
{
/// <summary>
/// The component that displays the player's accuracy on the results screen.
/// </summary>
public class AccuracyCircle : CompositeDrawable
{
/// <summary>
/// Duration for the transforms causing this component to appear.
/// </summary>
public const double APPEAR_DURATION = 200;
/// <summary>
/// Delay before the accuracy circle starts filling.
/// </summary>
public const double ACCURACY_TRANSFORM_DELAY = 450;
/// <summary>
/// Duration for the accuracy circle fill.
/// </summary>
public const double ACCURACY_TRANSFORM_DURATION = 3000;
/// <summary>
/// Delay after <see cref="ACCURACY_TRANSFORM_DURATION"/> for the rank text (A/B/C/D/S/SS) to appear.
/// </summary>
public const double TEXT_APPEAR_DELAY = ACCURACY_TRANSFORM_DURATION / 2;
/// <summary>
/// Delay before the rank circles start filling.
/// </summary>
public const double RANK_CIRCLE_TRANSFORM_DELAY = 150;
/// <summary>
/// Duration for the rank circle fills.
/// </summary>
public const double RANK_CIRCLE_TRANSFORM_DURATION = 800;
/// <summary>
/// Relative width of the rank circles.
/// </summary>
public const float RANK_CIRCLE_RADIUS = 0.06f;
/// <summary>
/// Relative width of the circle showing the accuracy.
/// </summary>
private const float accuracy_circle_radius = 0.2f;
/// <summary>
/// SS is displayed as a 1% region, otherwise it would be invisible.
/// </summary>
private const double virtual_ss_percentage = 0.01;
/// <summary>
/// The easing for the circle filling transforms.
/// </summary>
public static readonly Easing ACCURACY_TRANSFORM_EASING = Easing.OutPow10;
private readonly ScoreInfo score;
private SmoothCircularProgress accuracyCircle;
private SmoothCircularProgress innerMask;
private Container<RankBadge> badges;
private RankText rankText;
public AccuracyCircle(ScoreInfo score)
{
this.score = score;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChildren = new Drawable[]
{
new SmoothCircularProgress
{
Name = "Background circle",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(47),
Alpha = 0.5f,
InnerRadius = accuracy_circle_radius + 0.01f, // Extends a little bit into the circle
Current = { Value = 1 },
},
accuracyCircle = new SmoothCircularProgress
{
Name = "Accuracy circle",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#7CF6FF"), Color4Extensions.FromHex("#BAFFA9")),
InnerRadius = accuracy_circle_radius,
},
new BufferedContainer
{
Name = "Graded circles",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.8f),
Padding = new MarginPadding(2),
Children = new Drawable[]
{
new SmoothCircularProgress
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex("#BE0089"),
InnerRadius = RANK_CIRCLE_RADIUS,
Current = { Value = 1 }
},
new SmoothCircularProgress
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex("#0096A2"),
InnerRadius = RANK_CIRCLE_RADIUS,
Current = { Value = 1 - virtual_ss_percentage }
},
new SmoothCircularProgress
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex("#72C904"),
InnerRadius = RANK_CIRCLE_RADIUS,
Current = { Value = 0.95f }
},
new SmoothCircularProgress
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex("#D99D03"),
InnerRadius = RANK_CIRCLE_RADIUS,
Current = { Value = 0.9f }
},
new SmoothCircularProgress
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex("#EA7948"),
InnerRadius = RANK_CIRCLE_RADIUS,
Current = { Value = 0.8f }
},
new SmoothCircularProgress
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex("#FF5858"),
InnerRadius = RANK_CIRCLE_RADIUS,
Current = { Value = 0.7f }
},
new RankNotch(0),
new RankNotch((float)(1 - virtual_ss_percentage)),
new RankNotch(0.95f),
new RankNotch(0.9f),
new RankNotch(0.8f),
new RankNotch(0.7f),
new BufferedContainer
{
Name = "Graded circle mask",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(1),
Blending = new BlendingParameters
{
Source = BlendingType.DstColor,
Destination = BlendingType.OneMinusSrcAlpha,
SourceAlpha = BlendingType.One,
DestinationAlpha = BlendingType.SrcAlpha
},
Child = innerMask = new SmoothCircularProgress
{
RelativeSizeAxes = Axes.Both,
InnerRadius = RANK_CIRCLE_RADIUS - 0.01f,
}
}
}
},
badges = new Container<RankBadge>
{
Name = "Rank badges",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Vertical = -15, Horizontal = -20 },
Children = new[]
{
new RankBadge(1f, ScoreRank.X),
new RankBadge(0.95f, ScoreRank.S),
new RankBadge(0.9f, ScoreRank.A),
new RankBadge(0.8f, ScoreRank.B),
new RankBadge(0.7f, ScoreRank.C),
}
},
rankText = new RankText(score.Rank)
};
}
protected override void LoadComplete()
{
base.LoadComplete();
this.ScaleTo(0).Then().ScaleTo(1, APPEAR_DURATION, Easing.OutQuint);
using (BeginDelayedSequence(RANK_CIRCLE_TRANSFORM_DELAY, true))
innerMask.FillTo(1f, RANK_CIRCLE_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING);
using (BeginDelayedSequence(ACCURACY_TRANSFORM_DELAY, true))
{
double targetAccuracy = score.Rank == ScoreRank.X || score.Rank == ScoreRank.XH ? 1 : Math.Min(1 - virtual_ss_percentage, score.Accuracy);
accuracyCircle.FillTo(targetAccuracy, ACCURACY_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING);
foreach (var badge in badges)
{
if (badge.Accuracy > score.Accuracy)
continue;
using (BeginDelayedSequence(inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(1 - virtual_ss_percentage, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION, true))
badge.Appear();
}
using (BeginDelayedSequence(TEXT_APPEAR_DELAY, true))
rankText.Appear();
}
}
private double inverseEasing(Easing easing, double targetValue)
{
double test = 0;
double result = 0;
int count = 2;
while (Math.Abs(result - targetValue) > 0.005)
{
int dir = Math.Sign(targetValue - result);
test += dir * 1.0 / count;
result = Interpolation.ApplyEasing(easing, test);
count++;
}
return test;
}
}
}

View File

@ -0,0 +1,99 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Online.Leaderboards;
using osu.Game.Scoring;
using osuTK;
namespace osu.Game.Screens.Ranking.Expanded.Accuracy
{
/// <summary>
/// Contains a <see cref="DrawableRank"/> that is positioned around the <see cref="AccuracyCircle"/>.
/// </summary>
public class RankBadge : CompositeDrawable
{
/// <summary>
/// The accuracy value corresponding to the <see cref="ScoreRank"/> displayed by this badge.
/// </summary>
public readonly float Accuracy;
private readonly ScoreRank rank;
private Drawable rankContainer;
private Drawable overlay;
/// <summary>
/// Creates a new <see cref="RankBadge"/>.
/// </summary>
/// <param name="accuracy">The accuracy value corresponding to <paramref name="rank"/>.</param>
/// <param name="rank">The <see cref="ScoreRank"/> to be displayed in this <see cref="RankBadge"/>.</param>
public RankBadge(float accuracy, ScoreRank rank)
{
Accuracy = accuracy;
this.rank = rank;
RelativeSizeAxes = Axes.Both;
Alpha = 0;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChild = rankContainer = new Container
{
Origin = Anchor.Centre,
Size = new Vector2(28, 14),
Children = new[]
{
new DrawableRank(rank),
overlay = new CircularContainer
{
RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive,
Masking = true,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = OsuColour.ForRank(rank).Opacity(0.2f),
Radius = 10,
},
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true,
}
}
}
};
}
/// <summary>
/// Shows this <see cref="RankBadge"/>.
/// </summary>
public void Appear()
{
this.FadeIn(50);
overlay.FadeIn().FadeOut(500, Easing.In);
}
protected override void Update()
{
base.Update();
// Starts at -90deg (top) and moves counter-clockwise by the accuracy
rankContainer.Position = circlePosition(-MathF.PI / 2 - (1 - Accuracy) * MathF.PI * 2);
}
private Vector2 circlePosition(float t)
=> DrawSize / 2 + new Vector2(MathF.Cos(t), MathF.Sin(t)) * DrawSize / 2;
}
}

View File

@ -0,0 +1,49 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osuTK;
namespace osu.Game.Screens.Ranking.Expanded.Accuracy
{
/// <summary>
/// A solid "notch" of the <see cref="AccuracyCircle"/> that appears at the ends of the rank circles to add separation.
/// </summary>
public class RankNotch : CompositeDrawable
{
private readonly float position;
public RankNotch(float position)
{
this.position = position;
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChild = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Rotation = position * 360f,
Child = new Box
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Y,
Height = AccuracyCircle.RANK_CIRCLE_RADIUS,
Width = 1f,
Colour = OsuColour.Gray(0.3f),
EdgeSmoothness = new Vector2(1f)
}
};
}
}
}

View File

@ -0,0 +1,137 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Leaderboards;
using osu.Game.Scoring;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Ranking.Expanded.Accuracy
{
/// <summary>
/// The text that appears in the middle of the <see cref="AccuracyCircle"/> displaying the user's rank.
/// </summary>
public class RankText : CompositeDrawable
{
private readonly ScoreRank rank;
private BufferedContainer flash;
private BufferedContainer superFlash;
private GlowingSpriteText rankText;
public RankText(ScoreRank rank)
{
this.rank = rank;
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
Alpha = 0;
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChildren = new Drawable[]
{
rankText = new GlowingSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
GlowColour = OsuColour.ForRank(rank),
Spacing = new Vector2(-15, 0),
Text = DrawableRank.GetRankName(rank),
Font = OsuFont.Numeric.With(size: 76),
UseFullGlyphHeight = false
},
superFlash = new BufferedContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
BlurSigma = new Vector2(85),
Size = new Vector2(600),
CacheDrawnFrameBuffer = true,
Blending = BlendingParameters.Additive,
Alpha = 0,
Children = new[]
{
new Box
{
Colour = Color4.White,
Size = new Vector2(150),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
},
},
flash = new BufferedContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
BlurSigma = new Vector2(35),
BypassAutoSizeAxes = Axes.Both,
RelativeSizeAxes = Axes.Both,
CacheDrawnFrameBuffer = true,
Blending = BlendingParameters.Additive,
Alpha = 0,
Size = new Vector2(2f), // increase buffer size to allow for scale
Scale = new Vector2(1.8f),
Children = new[]
{
new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Spacing = new Vector2(-15, 0),
Text = DrawableRank.GetRankName(rank),
Font = OsuFont.Numeric.With(size: 76),
UseFullGlyphHeight = false,
Shadow = false
},
},
},
};
}
public void Appear()
{
this.FadeIn();
if (rank < ScoreRank.A)
{
this
.MoveToOffset(new Vector2(0, -20))
.MoveToOffset(new Vector2(0, 20), 200, Easing.OutBounce);
if (rank <= ScoreRank.D)
{
this.Delay(700)
.RotateTo(5, 150, Easing.In)
.MoveToOffset(new Vector2(0, 3), 150, Easing.In);
}
this.FadeInFromZero(200, Easing.OutQuint);
return;
}
flash.Colour = OsuColour.ForRank(rank);
flash.FadeIn().Then().FadeOut(1200, Easing.OutQuint);
if (rank >= ScoreRank.S)
rankText.ScaleTo(1.05f).ScaleTo(1, 3000, Easing.OutQuint);
if (rank >= ScoreRank.X)
{
flash.FadeIn().Then().FadeOut(3000);
superFlash.FadeIn().Then().FadeOut(800, Easing.OutQuint);
}
}
}
}

View File

@ -0,0 +1,126 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using 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.Graphics.Transforms;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics;
using osuTK;
namespace osu.Game.Screens.Ranking.Expanded.Accuracy
{
/// <summary>
/// Contains a <see cref="CircularProgress"/> with smoothened edges.
/// </summary>
public class SmoothCircularProgress : CompositeDrawable
{
public Bindable<double> Current
{
get => progress.Current;
set => progress.Current = value;
}
public float InnerRadius
{
get => progress.InnerRadius;
set
{
progress.InnerRadius = value;
innerSmoothingContainer.Size = new Vector2(1 - value);
smoothingWedge.Height = value / 2;
}
}
private readonly CircularProgress progress;
private readonly Container innerSmoothingContainer;
private readonly Drawable smoothingWedge;
public SmoothCircularProgress()
{
Container smoothingWedgeContainer;
InternalChild = new BufferedContainer
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
progress = new CircularProgress { RelativeSizeAxes = Axes.Both },
smoothingWedgeContainer = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Child = smoothingWedge = new Box
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Y,
Width = 1f,
EdgeSmoothness = new Vector2(2, 0),
}
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(-1),
Child = new CircularContainer
{
RelativeSizeAxes = Axes.Both,
BorderThickness = 2,
Masking = true,
BorderColour = OsuColour.Gray(0.5f).Opacity(0.75f),
Blending = new BlendingParameters
{
AlphaEquation = BlendingEquation.ReverseSubtract,
},
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
}
}
},
innerSmoothingContainer = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Size = Vector2.Zero,
Padding = new MarginPadding(-1),
Child = new CircularContainer
{
RelativeSizeAxes = Axes.Both,
BorderThickness = 2,
BorderColour = OsuColour.Gray(0.5f).Opacity(0.75f),
Masking = true,
Blending = new BlendingParameters
{
AlphaEquation = BlendingEquation.ReverseSubtract,
},
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
}
}
},
}
};
Current.BindValueChanged(c =>
{
smoothingWedgeContainer.Alpha = c.NewValue > 0 ? 1 : 0;
smoothingWedgeContainer.Rotation = (float)(360 * c.NewValue);
}, true);
}
public TransformSequence<CircularProgress> FillTo(double newValue, double duration = 0, Easing easing = Easing.None)
=> progress.FillTo(newValue, duration, easing);
}
}

View File

@ -0,0 +1,237 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Scoring;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Ranking.Expanded.Accuracy;
using osu.Game.Screens.Ranking.Expanded.Statistics;
using osuTK;
namespace osu.Game.Screens.Ranking.Expanded
{
/// <summary>
/// The content that appears in the middle section of the <see cref="ScorePanel"/>.
/// </summary>
public class ExpandedPanelMiddleContent : CompositeDrawable
{
private readonly ScoreInfo score;
private readonly List<StatisticDisplay> statisticDisplays = new List<StatisticDisplay>();
private RollingCounter<long> scoreCounter;
/// <summary>
/// Creates a new <see cref="ExpandedPanelMiddleContent"/>.
/// </summary>
/// <param name="score">The score to display.</param>
public ExpandedPanelMiddleContent(ScoreInfo score)
{
this.score = score;
RelativeSizeAxes = Axes.Both;
Masking = true;
Padding = new MarginPadding { Vertical = 10, Horizontal = 10 };
}
[BackgroundDependencyLoader]
private void load(Bindable<WorkingBeatmap> working)
{
var beatmap = working.Value.BeatmapInfo;
var metadata = beatmap.Metadata;
var creator = metadata.Author?.Username;
var topStatistics = new List<StatisticDisplay>
{
new AccuracyStatistic(score.Accuracy),
new ComboStatistic(score.MaxCombo, true),
new CounterStatistic("pp", (int)(score.PP ?? 0)),
};
var bottomStatistics = new List<StatisticDisplay>();
foreach (var stat in score.SortedStatistics)
bottomStatistics.Add(new CounterStatistic(stat.Key.GetDescription(), stat.Value));
statisticDisplays.AddRange(topStatistics);
statisticDisplays.AddRange(bottomStatistics);
InternalChild = new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(20),
Children = new Drawable[]
{
new FillFlowContainer
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = new LocalisedString((metadata.TitleUnicode, metadata.Title)),
Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold),
},
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist)),
Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold)
},
new Container
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Margin = new MarginPadding { Top = 40 },
RelativeSizeAxes = Axes.X,
Height = 230,
Child = new AccuracyCircle(score)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
}
},
scoreCounter = new TotalScoreCounter
{
Margin = new MarginPadding { Top = 0, Bottom = 5 },
Current = { Value = 0 },
Alpha = 0,
AlwaysPresent = true
},
new FillFlowContainer
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new StarRatingDisplay(beatmap)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft
},
new ModDisplay
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
DisplayUnrankedText = false,
ExpansionMode = ExpansionMode.AlwaysExpanded,
Scale = new Vector2(0.5f),
Current = { Value = score.Mods }
}
}
},
new FillFlowContainer
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = beatmap.Version,
Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold),
},
new OsuTextFlowContainer(s => s.Font = OsuFont.Torus.With(size: 12))
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
}.With(t =>
{
if (!string.IsNullOrEmpty(creator))
{
t.AddText("mapped by ");
t.AddText(creator, s => s.Font = s.Font.With(weight: FontWeight.SemiBold));
}
})
}
},
}
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5),
Children = new Drawable[]
{
new GridContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Content = new[] { topStatistics.Cast<Drawable>().ToArray() },
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
}
},
new GridContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Content = new[] { bottomStatistics.Cast<Drawable>().ToArray() },
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
}
}
}
}
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
// Score counter value setting must be scheduled so it isn't transferred instantaneously
ScheduleAfterChildren(() =>
{
using (BeginDelayedSequence(AccuracyCircle.ACCURACY_TRANSFORM_DELAY, true))
{
scoreCounter.FadeIn();
scoreCounter.Current.Value = score.TotalScore;
double delay = 0;
foreach (var stat in statisticDisplays)
{
using (BeginDelayedSequence(delay, true))
stat.Appear();
delay += 200;
}
}
});
}
}
}

View File

@ -0,0 +1,64 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Users;
using osu.Game.Users.Drawables;
using osuTK;
namespace osu.Game.Screens.Ranking.Expanded
{
/// <summary>
/// The content that appears in the middle section of the <see cref="ScorePanel"/>.
/// </summary>
public class ExpandedPanelTopContent : CompositeDrawable
{
private readonly User user;
/// <summary>
/// Creates a new <see cref="ExpandedPanelTopContent"/>.
/// </summary>
/// <param name="user">The <see cref="User"/> to display.</param>
public ExpandedPanelTopContent(User user)
{
this.user = user;
Anchor = Anchor.TopCentre;
Origin = Anchor.Centre;
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new UpdateableAvatar(user)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Size = new Vector2(80),
CornerRadius = 20,
CornerExponent = 2.5f,
Masking = true,
},
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = user.Username,
Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold)
}
}
};
}
}
}

View File

@ -0,0 +1,108 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Globalization;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Ranking.Expanded
{
/// <summary>
/// A pill that displays the star rating of a <see cref="BeatmapInfo"/>.
/// </summary>
public class StarRatingDisplay : CompositeDrawable
{
private readonly BeatmapInfo beatmap;
/// <summary>
/// Creates a new <see cref="StarRatingDisplay"/>.
/// </summary>
/// <param name="beatmap">The <see cref="BeatmapInfo"/> to display the star difficulty of.</param>
public StarRatingDisplay(BeatmapInfo beatmap)
{
this.beatmap = beatmap;
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
var starRatingParts = beatmap.StarDifficulty.ToString("0.00", CultureInfo.InvariantCulture).Split('.');
string wholePart = starRatingParts[0];
string fractionPart = starRatingParts[1];
string separator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
ColourInfo backgroundColour = beatmap.DifficultyRating == DifficultyRating.ExpertPlus
? ColourInfo.GradientVertical(Color4Extensions.FromHex("#C1C1C1"), Color4Extensions.FromHex("#595959"))
: (ColourInfo)colours.ForDifficultyRating(beatmap.DifficultyRating);
InternalChildren = new Drawable[]
{
new CircularContainer
{
RelativeSizeAxes = Axes.Both,
Masking = true,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = backgroundColour
},
}
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = 8, Vertical = 4 },
Direction = FillDirection.Horizontal,
Spacing = new Vector2(2, 0),
Children = new Drawable[]
{
new SpriteIcon
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Size = new Vector2(7),
Icon = FontAwesome.Solid.Star,
Colour = Color4.Black
},
new OsuTextFlowContainer(s => s.Font = OsuFont.Numeric.With(weight: FontWeight.Black))
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
TextAnchor = Anchor.BottomLeft,
}.With(t =>
{
t.AddText($"{wholePart}", s =>
{
s.Colour = Color4.Black;
s.Font = s.Font.With(size: 14);
s.UseFullGlyphHeight = false;
});
t.AddText($"{separator}{fractionPart}", s =>
{
s.Colour = Color4.Black;
s.Font = s.Font.With(size: 7);
s.UseFullGlyphHeight = false;
});
})
}
}
};
}
}
}

View File

@ -0,0 +1,58 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Ranking.Expanded.Accuracy;
using osu.Game.Utils;
using osuTK;
namespace osu.Game.Screens.Ranking.Expanded.Statistics
{
/// <summary>
/// A <see cref="StatisticDisplay"/> to display the player's accuracy.
/// </summary>
public class AccuracyStatistic : StatisticDisplay
{
private readonly double accuracy;
private RollingCounter<double> counter;
/// <summary>
/// Creates a new <see cref="AccuracyStatistic"/>.
/// </summary>
/// <param name="accuracy">The accuracy to display.</param>
public AccuracyStatistic(double accuracy)
: base("accuracy")
{
this.accuracy = accuracy;
}
public override void Appear()
{
base.Appear();
counter.Current.Value = accuracy;
}
protected override Drawable CreateContent() => counter = new Counter();
private class Counter : RollingCounter<double>
{
protected override double RollingDuration => AccuracyCircle.ACCURACY_TRANSFORM_DURATION;
protected override Easing RollingEasing => AccuracyCircle.ACCURACY_TRANSFORM_EASING;
public Counter()
{
DisplayedCountSpriteText.Font = OsuFont.Torus.With(size: 20, fixedWidth: true);
DisplayedCountSpriteText.Spacing = new Vector2(-2, 0);
}
protected override string FormatCount(double count) => count.FormatAccuracy();
public override void Increment(double amount)
=> Current.Value += amount;
}
}
}

View File

@ -0,0 +1,71 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Screens.Ranking.Expanded.Accuracy;
using osuTK;
namespace osu.Game.Screens.Ranking.Expanded.Statistics
{
/// <summary>
/// A <see cref="StatisticDisplay"/> to display the player's combo.
/// </summary>
public class ComboStatistic : CounterStatistic
{
private readonly bool isPerfect;
private Drawable perfectText;
/// <summary>
/// Creates a new <see cref="ComboStatistic"/>.
/// </summary>
/// <param name="combo">The combo to be displayed.</param>
/// <param name="isPerfect">Whether this is a perfect combo.</param>
public ComboStatistic(int combo, bool isPerfect)
: base("combo", combo)
{
this.isPerfect = isPerfect;
}
public override void Appear()
{
base.Appear();
if (isPerfect)
{
using (BeginDelayedSequence(AccuracyCircle.ACCURACY_TRANSFORM_DURATION / 2, true))
perfectText.FadeIn(50);
}
}
protected override Drawable CreateContent() => new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10, 0),
Children = new[]
{
base.CreateContent().With(d =>
{
Anchor = Anchor.CentreLeft;
Origin = Anchor.CentreLeft;
}),
perfectText = new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Text = "PERFECT",
Font = OsuFont.Torus.With(size: 11, weight: FontWeight.SemiBold),
Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#66FFCC"), Color4Extensions.FromHex("#FF9AD7")),
Alpha = 0,
UseFullGlyphHeight = false,
}
}
};
}
}

View File

@ -0,0 +1,56 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Ranking.Expanded.Accuracy;
using osuTK;
namespace osu.Game.Screens.Ranking.Expanded.Statistics
{
/// <summary>
/// A <see cref="StatisticDisplay"/> to display general numeric values.
/// </summary>
public class CounterStatistic : StatisticDisplay
{
private readonly int count;
private RollingCounter<int> counter;
/// <summary>
/// Creates a new <see cref="CounterStatistic"/>.
/// </summary>
/// <param name="header">The name of the statistic.</param>
/// <param name="count">The value to display.</param>
public CounterStatistic(string header, int count)
: base(header)
{
this.count = count;
}
public override void Appear()
{
base.Appear();
counter.Current.Value = count;
}
protected override Drawable CreateContent() => counter = new Counter();
private class Counter : RollingCounter<int>
{
protected override double RollingDuration => AccuracyCircle.ACCURACY_TRANSFORM_DURATION;
protected override Easing RollingEasing => AccuracyCircle.ACCURACY_TRANSFORM_EASING;
public Counter()
{
DisplayedCountSpriteText.Font = OsuFont.Torus.With(size: 20, fixedWidth: true);
DisplayedCountSpriteText.Spacing = new Vector2(-2, 0);
}
public override void Increment(int amount)
=> Current.Value += amount;
}
}
}

View File

@ -0,0 +1,95 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Screens.Ranking.Expanded.Statistics
{
/// <summary>
/// A statistic from the score to be displayed in the <see cref="ExpandedPanelMiddleContent"/>.
/// </summary>
public abstract class StatisticDisplay : CompositeDrawable
{
private readonly string header;
private Drawable content;
/// <summary>
/// Creates a new <see cref="StatisticDisplay"/>.
/// </summary>
/// <param name="header">The name of the statistic.</param>
protected StatisticDisplay(string header)
{
this.header = header;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChild = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new[]
{
new CircularContainer
{
RelativeSizeAxes = Axes.X,
Height = 12,
Masking = true,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex("#222")
},
new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold),
Text = header.ToUpperInvariant(),
}
}
},
new Container
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
Children = new[]
{
content = CreateContent().With(d =>
{
d.Anchor = Anchor.TopCentre;
d.Origin = Anchor.TopCentre;
d.Alpha = 0;
d.AlwaysPresent = true;
}),
}
}
}
};
}
/// <summary>
/// Shows the statistic value.
/// </summary>
public virtual void Appear() => content.FadeIn(100);
/// <summary>
/// Creates the content for this <see cref="StatisticDisplay"/>.
/// </summary>
protected abstract Drawable CreateContent();
}
}

View File

@ -0,0 +1,38 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Ranking.Expanded.Accuracy;
using osuTK;
namespace osu.Game.Screens.Ranking.Expanded
{
/// <summary>
/// A counter for the player's total score to be displayed in the <see cref="ExpandedPanelMiddleContent"/>.
/// </summary>
public class TotalScoreCounter : RollingCounter<long>
{
protected override double RollingDuration => AccuracyCircle.ACCURACY_TRANSFORM_DURATION;
protected override Easing RollingEasing => AccuracyCircle.ACCURACY_TRANSFORM_EASING;
public TotalScoreCounter()
{
// Todo: AutoSize X removed here due to https://github.com/ppy/osu-framework/issues/3369
AutoSizeAxes = Axes.Y;
RelativeSizeAxes = Axes.X;
DisplayedCountSpriteText.Anchor = Anchor.TopCentre;
DisplayedCountSpriteText.Origin = Anchor.TopCentre;
DisplayedCountSpriteText.Font = OsuFont.Torus.With(size: 60, weight: FontWeight.Light, fixedWidth: true);
DisplayedCountSpriteText.Spacing = new Vector2(-5, 0);
}
protected override string FormatCount(long count) => count.ToString("N0");
public override void Increment(long amount)
=> Current.Value += amount;
}
}

View File

@ -1,43 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Scoring;
using osu.Game.Screens.Select.Leaderboards;
using osuTK;
namespace osu.Game.Screens.Ranking.Pages
{
public class LocalLeaderboardPage : ResultsPage
{
public LocalLeaderboardPage(ScoreInfo score, WorkingBeatmap beatmap = null)
: base(score, beatmap)
{
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Children = new Drawable[]
{
new Box
{
Colour = colours.Gray6,
RelativeSizeAxes = Axes.Both,
},
new BeatmapLeaderboard
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Beatmap = Beatmap.BeatmapInfo ?? Score.Beatmap,
Scale = new Vector2(0.7f)
}
};
}
}
}

View File

@ -1,428 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using 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.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Leaderboards;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Users;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Ranking.Pages
{
public class ScoreResultsPage : ResultsPage
{
private Container scoreContainer;
private ScoreCounter scoreCounter;
private readonly ScoreInfo score;
public ScoreResultsPage(ScoreInfo score, WorkingBeatmap beatmap)
: base(score, beatmap)
{
this.score = score;
}
private FillFlowContainer<DrawableScoreStatistic> statisticsContainer;
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
const float user_header_height = 120;
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = user_header_height },
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
},
}
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new DelayedLoadWrapper(new UserHeader(Score.User)
{
RelativeSizeAxes = Axes.Both,
})
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
Height = user_header_height,
},
new UpdateableRank(Score.Rank)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Size = new Vector2(150, 60),
Margin = new MarginPadding(20),
},
scoreContainer = new Container
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
Height = 60,
Children = new Drawable[]
{
new SongProgressGraph
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.5f,
Objects = Beatmap.Beatmap.HitObjects,
},
scoreCounter = new SlowScoreCounter(6)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Colour = colours.PinkDarker,
Y = 10,
TextSize = 56,
},
}
},
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Colour = colours.PinkDarker,
Shadow = false,
Font = OsuFont.GetFont(weight: FontWeight.Bold),
Text = "total score",
Margin = new MarginPadding { Bottom = 15 },
},
new BeatmapDetails(Beatmap.BeatmapInfo)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Margin = new MarginPadding { Bottom = 10 },
},
new DateTimeDisplay(Score.Date.LocalDateTime)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
},
new Container
{
RelativeSizeAxes = Axes.X,
Size = new Vector2(0.75f, 1),
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Margin = new MarginPadding { Top = 10, Bottom = 10 },
Children = new Drawable[]
{
new Box
{
Colour = ColourInfo.GradientHorizontal(
colours.GrayC.Opacity(0),
colours.GrayC.Opacity(0.9f)),
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.5f, 1),
},
new Box
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Colour = ColourInfo.GradientHorizontal(
colours.GrayC.Opacity(0.9f),
colours.GrayC.Opacity(0)),
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.5f, 1),
},
}
},
statisticsContainer = new FillFlowContainer<DrawableScoreStatistic>
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Direction = FillDirection.Horizontal,
LayoutDuration = 200,
LayoutEasing = Easing.OutQuint
},
},
},
new FillFlowContainer
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Margin = new MarginPadding { Bottom = 10 },
Spacing = new Vector2(5),
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
new ReplayDownloadButton(score),
new RetryButton()
}
},
};
statisticsContainer.ChildrenEnumerable = Score.SortedStatistics.Select(s => new DrawableScoreStatistic(s));
}
protected override void LoadComplete()
{
base.LoadComplete();
Schedule(() =>
{
scoreCounter.Increment(Score.TotalScore);
int delay = 0;
foreach (var s in statisticsContainer.Children)
{
s.FadeOut()
.Then(delay += 200)
.FadeIn(300 + delay, Easing.Out);
}
});
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
scoreCounter.Scale = new Vector2(Math.Min(1f, (scoreContainer.DrawWidth - 20) / scoreCounter.DrawWidth));
}
private class DrawableScoreStatistic : Container
{
private readonly KeyValuePair<HitResult, int> statistic;
public DrawableScoreStatistic(KeyValuePair<HitResult, int> statistic)
{
this.statistic = statistic;
AutoSizeAxes = Axes.Both;
Margin = new MarginPadding { Left = 5, Right = 5 };
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Children = new Drawable[]
{
new OsuSpriteText
{
Text = statistic.Value.ToString().PadLeft(4, '0'),
Colour = colours.Gray7,
Font = OsuFont.GetFont(size: 30),
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
},
new OsuSpriteText
{
Text = statistic.Key.GetDescription(),
Colour = colours.Gray7,
Font = OsuFont.GetFont(weight: FontWeight.Bold),
Y = 26,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
},
};
}
}
private class DateTimeDisplay : Container
{
private readonly DateTime date;
public DateTimeDisplay(DateTime date)
{
this.date = date;
AutoSizeAxes = Axes.Both;
Masking = true;
CornerRadius = 5;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colours.Gray6,
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Padding = new MarginPadding { Horizontal = 10, Vertical = 5 },
Spacing = new Vector2(10),
Children = new[]
{
new OsuSpriteText
{
Text = date.ToShortDateString(),
Colour = Color4.White,
},
new OsuSpriteText
{
Text = date.ToShortTimeString(),
Colour = Color4.White,
}
}
},
};
}
}
private class BeatmapDetails : Container
{
private readonly BeatmapInfo beatmap;
private readonly OsuSpriteText title;
private readonly OsuSpriteText artist;
private readonly OsuSpriteText versionMapper;
public BeatmapDetails(BeatmapInfo beatmap)
{
this.beatmap = beatmap;
AutoSizeAxes = Axes.Both;
Children = new Drawable[]
{
new FillFlowContainer
{
Direction = FillDirection.Vertical,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
title = new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Shadow = false,
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 24, italics: true),
},
artist = new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Shadow = false,
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 20, italics: true),
},
versionMapper = new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Shadow = false,
Font = OsuFont.GetFont(weight: FontWeight.Bold),
},
}
}
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
title.Colour = artist.Colour = colours.BlueDarker;
versionMapper.Colour = colours.Gray8;
var creator = beatmap.Metadata.Author?.Username;
if (!string.IsNullOrEmpty(creator))
{
versionMapper.Text = $"mapped by {creator}";
if (!string.IsNullOrEmpty(beatmap.Version))
versionMapper.Text = $"{beatmap.Version} - " + versionMapper.Text;
}
title.Text = new LocalisedString((beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title));
artist.Text = new LocalisedString((beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist));
}
}
[LongRunningLoad]
private class UserHeader : Container
{
private readonly User user;
private readonly Sprite cover;
public UserHeader(User user)
{
this.user = user;
Children = new Drawable[]
{
cover = new Sprite
{
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fill,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new OsuSpriteText
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Text = user.Username,
Font = OsuFont.GetFont(size: 30, weight: FontWeight.Regular, italics: true),
Padding = new MarginPadding { Bottom = 10 },
}
};
}
[BackgroundDependencyLoader]
private void load(LargeTextureStore textures)
{
if (!string.IsNullOrEmpty(user.CoverUrl))
cover.Texture = textures.Get(user.CoverUrl);
}
}
private class SlowScoreCounter : ScoreCounter
{
protected override double RollingDuration => 3000;
protected override Easing RollingEasing => Easing.OutPow10;
public SlowScoreCounter(uint leading = 0)
: base(leading)
{
DisplayedCountSpriteText.Shadow = false;
DisplayedCountSpriteText.Font = DisplayedCountSpriteText.Font.With(Typeface.Venera, weight: FontWeight.Light);
UseCommaSeparator = true;
}
}
}
}

View File

@ -1,16 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
namespace osu.Game.Screens.Ranking
{
public interface IResultPageInfo
public enum PanelState
{
IconUsage Icon { get; }
string Name { get; }
ResultsPage CreatePage();
Expanded,
Contracted
}
}

View File

@ -9,7 +9,7 @@ using osu.Game.Online;
using osu.Game.Scoring;
using osuTK;
namespace osu.Game.Screens.Ranking.Pages
namespace osu.Game.Screens.Ranking
{
public class ReplayDownloadButton : DownloadTrackingComposite<ScoreInfo, ScoreManager>
{

View File

@ -1,97 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
namespace osu.Game.Screens.Ranking
{
public class ResultModeButton : TabItem<IResultPageInfo>, IHasTooltip
{
private readonly IconUsage icon;
private Color4 activeColour;
private Color4 inactiveColour;
private CircularContainer colouredPart;
public ResultModeButton(IResultPageInfo mode)
: base(mode)
{
icon = mode.Icon;
TooltipText = mode.Name;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Size = new Vector2(50);
Masking = true;
CornerRadius = 25;
CornerExponent = 2;
activeColour = colours.PinkDarker;
inactiveColour = OsuColour.Gray(0.8f);
EdgeEffect = new EdgeEffectParameters
{
Colour = Color4.Black.Opacity(0.4f),
Type = EdgeEffectType.Shadow,
Radius = 5,
};
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
},
colouredPart = new CircularContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.8f),
BorderThickness = 4,
BorderColour = Color4.White,
Colour = inactiveColour,
Children = new Drawable[]
{
new Box
{
AlwaysPresent = true, //for border rendering
RelativeSizeAxes = Axes.Both,
Colour = Color4.Transparent,
},
new SpriteIcon
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Shadow = false,
Colour = OsuColour.Gray(0.95f),
Icon = icon,
Size = new Vector2(20),
}
}
}
};
}
protected override void OnActivated() => colouredPart.FadeColour(activeColour, 200, Easing.OutQuint);
protected override void OnDeactivated() => colouredPart.FadeColour(inactiveColour, 200, Easing.OutQuint);
public string TooltipText { get; }
}
}

View File

@ -1,30 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Framework.Graphics.UserInterface;
using osuTK;
namespace osu.Game.Screens.Ranking
{
public class ResultModeTabControl : TabControl<IResultPageInfo>
{
public ResultModeTabControl()
{
TabContainer.Anchor = Anchor.BottomCentre;
TabContainer.Origin = Anchor.BottomCentre;
TabContainer.Spacing = new Vector2(15);
TabContainer.Masking = false;
TabContainer.Padding = new MarginPadding(5);
}
protected override Dropdown<IResultPageInfo> CreateDropdown() => null;
protected override TabItem<IResultPageInfo> CreateTabItem(IResultPageInfo value) => new ResultModeButton(value)
{
Anchor = TabContainer.Anchor,
Origin = TabContainer.Origin
};
}
}

View File

@ -1,291 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Screens;
using osu.Game.Graphics.Containers;
using osu.Game.Screens.Backgrounds;
using osuTK;
using osuTK.Graphics;
using osu.Game.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.Sprites;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
namespace osu.Game.Screens.Ranking
{
public abstract class Results : OsuScreen
{
protected const float BACKGROUND_BLUR = 20;
private Container circleOuterBackground;
private Container circleOuter;
private Container circleInner;
private ParallaxContainer backgroundParallax;
private ResultModeTabControl modeChangeButtons;
[Resolved(canBeNull: true)]
private Player player { get; set; }
public override bool DisallowExternalBeatmapRulesetChanges => true;
protected readonly ScoreInfo Score;
private Container currentPage;
protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value);
private const float overscan = 1.3f;
private const float circle_outer_scale = 0.96f;
protected Results(ScoreInfo score)
{
Score = score;
}
private const float transition_time = 800;
private IEnumerable<Drawable> allCircles => new Drawable[] { circleOuterBackground, circleInner, circleOuter };
public override void OnEntering(IScreen last)
{
base.OnEntering(last);
((BackgroundScreenBeatmap)Background).BlurAmount.Value = BACKGROUND_BLUR;
Background.ScaleTo(1.1f, transition_time, Easing.OutQuint);
allCircles.ForEach(c =>
{
c.FadeOut();
c.ScaleTo(0);
});
backgroundParallax.FadeOut();
modeChangeButtons.FadeOut();
currentPage?.FadeOut();
circleOuterBackground
.FadeIn(transition_time, Easing.OutQuint)
.ScaleTo(1, transition_time, Easing.OutQuint);
using (BeginDelayedSequence(transition_time * 0.25f, true))
{
circleOuter
.FadeIn(transition_time, Easing.OutQuint)
.ScaleTo(1, transition_time, Easing.OutQuint);
using (BeginDelayedSequence(transition_time * 0.3f, true))
{
backgroundParallax.FadeIn(transition_time, Easing.OutQuint);
circleInner
.FadeIn(transition_time, Easing.OutQuint)
.ScaleTo(1, transition_time, Easing.OutQuint);
using (BeginDelayedSequence(transition_time * 0.4f, true))
{
modeChangeButtons.FadeIn(transition_time, Easing.OutQuint);
currentPage?.FadeIn(transition_time, Easing.OutQuint);
}
}
}
}
public override bool OnExiting(IScreen next)
{
allCircles.ForEach(c => c.ScaleTo(0, transition_time, Easing.OutSine));
Background.ScaleTo(1f, transition_time / 4, Easing.OutQuint);
this.FadeOut(transition_time / 4);
return base.OnExiting(next);
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
InternalChild = new AspectContainer
{
RelativeSizeAxes = Axes.Y,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Height = overscan,
Children = new Drawable[]
{
circleOuterBackground = new CircularContainer
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Masking = true,
Children = new Drawable[]
{
new Box
{
Alpha = 0.2f,
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
}
}
},
circleOuter = new CircularContainer
{
Size = new Vector2(circle_outer_scale),
EdgeEffect = new EdgeEffectParameters
{
Colour = Color4.Black.Opacity(0.4f),
Type = EdgeEffectType.Shadow,
Radius = 15,
},
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Masking = true,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
},
backgroundParallax = new ParallaxContainer
{
RelativeSizeAxes = Axes.Both,
ParallaxAmount = 0.01f,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Sprite
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.2f,
Texture = Beatmap.Value.Background,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
FillMode = FillMode.Fill
}
}
},
modeChangeButtons = new ResultModeTabControl
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
RelativeSizeAxes = Axes.X,
Height = 50,
Margin = new MarginPadding { Bottom = 110 },
},
new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.BottomCentre,
Text = $"{Score.MaxCombo}x",
RelativePositionAxes = Axes.X,
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 40),
X = 0.1f,
Colour = colours.BlueDarker,
},
new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.TopCentre,
Text = "max combo",
Font = OsuFont.GetFont(size: 20),
RelativePositionAxes = Axes.X,
X = 0.1f,
Colour = colours.Gray6,
},
new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.BottomCentre,
Text = Score.DisplayAccuracy,
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 40),
RelativePositionAxes = Axes.X,
X = 0.9f,
Colour = colours.BlueDarker,
},
new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.TopCentre,
Text = "accuracy",
Font = OsuFont.GetFont(size: 20),
RelativePositionAxes = Axes.X,
X = 0.9f,
Colour = colours.Gray6,
},
}
},
circleInner = new CircularContainer
{
Size = new Vector2(0.6f),
EdgeEffect = new EdgeEffectParameters
{
Colour = Color4.Black.Opacity(0.4f),
Type = EdgeEffectType.Shadow,
Radius = 15,
},
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Masking = true,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
},
}
}
}
};
if (player != null)
{
AddInternal(new HotkeyRetryOverlay
{
Action = () =>
{
if (!this.IsCurrentScreen()) return;
player?.Restart();
},
});
}
var pages = CreateResultPages();
foreach (var p in pages)
modeChangeButtons.AddItem(p);
modeChangeButtons.Current.Value = pages.FirstOrDefault();
modeChangeButtons.Current.BindValueChanged(page =>
{
currentPage?.FadeOut();
currentPage?.Expire();
currentPage = page.NewValue?.CreatePage();
if (currentPage != null)
LoadComponentAsync(currentPage, circleInner.Add);
}, true);
}
protected abstract IEnumerable<IResultPageInfo> CreateResultPages();
}
}

View File

@ -1,92 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Scoring;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Ranking
{
public abstract class ResultsPage : Container
{
protected readonly ScoreInfo Score;
protected readonly WorkingBeatmap Beatmap;
private CircularContainer content;
private Box fill;
protected override Container<Drawable> Content => content;
protected ResultsPage(ScoreInfo score, WorkingBeatmap beatmap)
{
Score = score;
Beatmap = beatmap;
RelativeSizeAxes = Axes.Both;
}
protected override void LoadComplete()
{
base.LoadComplete();
fill.Delay(400).FadeInFromZero(600);
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
AddRangeInternal(new Drawable[]
{
fill = new Box
{
Alpha = 0,
RelativeSizeAxes = Axes.Both,
Colour = colours.Gray6
},
new CircularContainer
{
EdgeEffect = new EdgeEffectParameters
{
Colour = colours.GrayF.Opacity(0.8f),
Type = EdgeEffectType.Shadow,
Radius = 1,
},
RelativeSizeAxes = Axes.Both,
Masking = true,
BorderThickness = 20,
BorderColour = Color4.White,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
},
}
},
content = new CircularContainer
{
EdgeEffect = new EdgeEffectParameters
{
Colour = Color4.Black.Opacity(0.2f),
Type = EdgeEffectType.Shadow,
Radius = 15,
},
RelativeSizeAxes = Axes.Both,
Masking = true,
Size = new Vector2(0.88f),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
});
}
}
}

View File

@ -0,0 +1,142 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Screens;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Scoring;
using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Play;
using osuTK;
namespace osu.Game.Screens.Ranking
{
public class ResultsScreen : OsuScreen
{
protected const float BACKGROUND_BLUR = 20;
public override bool DisallowExternalBeatmapRulesetChanges => true;
// Temporary for now to stop dual transitions. Should respect the current toolbar mode, but there's no way to do so currently.
public override bool HideOverlaysOnEnter => true;
protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value);
[Resolved(CanBeNull = true)]
private Player player { get; set; }
private readonly ScoreInfo score;
private Drawable bottomPanel;
public ResultsScreen(ScoreInfo score)
{
this.score = score;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChildren = new[]
{
new ResultsScrollContainer
{
Child = new ScorePanel(score)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
State = PanelState.Expanded
},
},
bottomPanel = new Container
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
Height = TwoLayerButton.SIZE_EXTENDED.Y,
Alpha = 0,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex("#333")
},
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 },
new RetryButton { Width = 300 },
}
}
}
}
};
if (player != null)
{
AddInternal(new HotkeyRetryOverlay
{
Action = () =>
{
if (!this.IsCurrentScreen()) return;
player?.Restart();
},
});
}
}
public override void OnEntering(IScreen last)
{
base.OnEntering(last);
((BackgroundScreenBeatmap)Background).BlurAmount.Value = BACKGROUND_BLUR;
Background.FadeTo(0.5f, 250);
bottomPanel.FadeTo(1, 250);
}
public override bool OnExiting(IScreen next)
{
Background.FadeTo(1, 250);
return base.OnExiting(next);
}
private class ResultsScrollContainer : OsuScrollContainer
{
private readonly Container content;
protected override Container<Drawable> Content => content;
public ResultsScrollContainer()
{
base.Content.Add(content = new Container
{
RelativeSizeAxes = Axes.X
});
RelativeSizeAxes = Axes.Both;
ScrollbarVisible = false;
}
protected override void Update()
{
base.Update();
content.Height = DrawHeight;
}
}
}
}

View File

@ -10,7 +10,7 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Play;
using osuTK;
namespace osu.Game.Screens.Ranking.Pages
namespace osu.Game.Screens.Ranking
{
public class RetryButton : OsuAnimatedButton
{

View File

@ -0,0 +1,223 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Scoring;
using osu.Game.Screens.Ranking.Expanded;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Ranking
{
public class ScorePanel : CompositeDrawable, IStateful<PanelState>
{
/// <summary>
/// Width of the panel when contracted.
/// </summary>
private const float contracted_width = 160;
/// <summary>
/// Height of the panel when contracted.
/// </summary>
private const float contracted_height = 320;
/// <summary>
/// Width of the panel when expanded.
/// </summary>
private const float expanded_width = 360;
/// <summary>
/// Height of the panel when expanded.
/// </summary>
private const float expanded_height = 560;
/// <summary>
/// Height of the top layer when the panel is expanded.
/// </summary>
private const float expanded_top_layer_height = 53;
/// <summary>
/// Height of the top layer when the panel is contracted.
/// </summary>
private const float contracted_top_layer_height = 40;
/// <summary>
/// Duration for the panel to resize into its expanded/contracted size.
/// </summary>
private const double resize_duration = 200;
/// <summary>
/// Delay after <see cref="resize_duration"/> before the top layer is expanded.
/// </summary>
private const double top_layer_expand_delay = 100;
/// <summary>
/// Duration for the top layer expansion.
/// </summary>
private const double top_layer_expand_duration = 200;
/// <summary>
/// Duration for the panel contents to fade in.
/// </summary>
private const double content_fade_duration = 50;
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");
public event Action<PanelState> StateChanged;
private readonly ScoreInfo score;
private Container topLayerContainer;
private Drawable topLayerBackground;
private Container topLayerContentContainer;
private Drawable topLayerContent;
private Container middleLayerContainer;
private Drawable middleLayerBackground;
private Container middleLayerContentContainer;
private Drawable middleLayerContent;
public ScorePanel(ScoreInfo score)
{
this.score = score;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChildren = new Drawable[]
{
topLayerContainer = new Container
{
Name = "Top layer",
RelativeSizeAxes = Axes.X,
Height = 120,
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
{
Name = "Middle layer",
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
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 }
}
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
if (state == PanelState.Expanded)
{
topLayerBackground.FadeColour(expanded_top_layer_colour);
middleLayerBackground.FadeColour(expanded_middle_layer_colour);
}
else
{
topLayerBackground.FadeColour(contracted_top_layer_colour);
middleLayerBackground.FadeColour(contracted_middle_layer_colour);
}
updateState();
}
private PanelState state = PanelState.Contracted;
public PanelState State
{
get => state;
set
{
if (state == value)
return;
state = value;
if (LoadState >= LoadState.Ready)
updateState();
StateChanged?.Invoke(value);
}
}
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();
switch (state)
{
case PanelState.Expanded:
this.ResizeTo(new Vector2(expanded_width, expanded_height), resize_duration, Easing.OutQuint);
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));
break;
case PanelState.Contracted:
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);
break;
}
using (BeginDelayedSequence(resize_duration + top_layer_expand_delay, true))
{
switch (state)
{
case PanelState.Expanded:
topLayerContainer.MoveToY(-expanded_top_layer_height / 2, top_layer_expand_duration, Easing.OutQuint);
middleLayerContainer.MoveToY(expanded_top_layer_height / 2, top_layer_expand_duration, Easing.OutQuint);
break;
case PanelState.Contracted:
topLayerContainer.MoveToY(-contracted_top_layer_height / 2, top_layer_expand_duration, Easing.OutQuint);
middleLayerContainer.MoveToY(contracted_top_layer_height / 2, top_layer_expand_duration, Easing.OutQuint);
break;
}
topLayerContent?.FadeIn(content_fade_duration);
middleLayerContent?.FadeIn(content_fade_duration);
}
}
}
}

View File

@ -1,28 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Scoring;
using osu.Game.Screens.Ranking.Pages;
namespace osu.Game.Screens.Ranking.Types
{
public class LocalLeaderboardPageInfo : IResultPageInfo
{
private readonly ScoreInfo score;
private readonly WorkingBeatmap beatmap;
public LocalLeaderboardPageInfo(ScoreInfo score, WorkingBeatmap beatmap)
{
this.score = score;
this.beatmap = beatmap;
}
public IconUsage Icon => FontAwesome.Solid.User;
public string Name => @"Local Leaderboard";
public ResultsPage CreatePage() => new LocalLeaderboardPage(score, beatmap);
}
}

View File

@ -1,30 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Scoring;
using osu.Game.Screens.Ranking.Pages;
namespace osu.Game.Screens.Ranking.Types
{
public class ScoreOverviewPageInfo : IResultPageInfo
{
public IconUsage Icon => FontAwesome.Solid.Asterisk;
public string Name => "Overview";
private readonly ScoreInfo score;
private readonly WorkingBeatmap beatmap;
public ScoreOverviewPageInfo(ScoreInfo score, WorkingBeatmap beatmap)
{
this.score = score;
this.beatmap = beatmap;
}
public ResultsPage CreatePage()
{
return new ScoreResultsPage(score, beatmap);
}
}
}

View File

@ -23,6 +23,7 @@ using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Framework.Logging;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
@ -311,20 +312,27 @@ namespace osu.Game.Screens.Select
Content = getBPMRange(b),
}));
IBeatmap playableBeatmap;
try
{
// Try to get the beatmap with the user's ruleset
playableBeatmap = beatmap.GetPlayableBeatmap(ruleset, Array.Empty<Mod>());
}
catch (BeatmapInvalidForRulesetException)
{
// Can't be converted to the user's ruleset, so use the beatmap's own ruleset
playableBeatmap = beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset, Array.Empty<Mod>());
}
IBeatmap playableBeatmap;
labels.AddRange(playableBeatmap.GetStatistics().Select(s => new InfoLabel(s)));
try
{
// Try to get the beatmap with the user's ruleset
playableBeatmap = beatmap.GetPlayableBeatmap(ruleset, Array.Empty<Mod>());
}
catch (BeatmapInvalidForRulesetException)
{
// Can't be converted to the user's ruleset, so use the beatmap's own ruleset
playableBeatmap = beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset, Array.Empty<Mod>());
}
labels.AddRange(playableBeatmap.GetStatistics().Select(s => new InfoLabel(s)));
}
catch (Exception e)
{
Logger.Error(e, "Could not load beatmap successfully!");
}
}
return labels.ToArray();

View File

@ -8,6 +8,7 @@ using osu.Framework.Input.Events;
using osu.Framework.Screens;
using osu.Game.Graphics;
using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking;
using osu.Game.Users;
using osuTK.Input;
@ -31,7 +32,7 @@ namespace osu.Game.Screens.Select
Edit();
}, Key.Number4);
((PlayBeatmapDetailArea)BeatmapDetails).Leaderboard.ScoreSelected += score => this.Push(new SoloResults(score));
((PlayBeatmapDetailArea)BeatmapDetails).Leaderboard.ScoreSelected += score => this.Push(new ResultsScreen(score));
}
protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea();

View File

@ -23,7 +23,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.315.0" />
<PackageReference Include="ppy.osu.Framework" Version="2020.314.0" />
<PackageReference Include="ppy.osu.Framework" Version="2020.317.0" />
<PackageReference Include="Sentry" Version="2.1.0" />
<PackageReference Include="SharpCompress" Version="0.24.0" />
<PackageReference Include="NUnit" Version="3.12.0" />

View File

@ -71,7 +71,7 @@
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.315.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.314.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.317.0" />
</ItemGroup>
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
<ItemGroup Label="Transitive Dependencies">
@ -79,7 +79,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2020.314.0" />
<PackageReference Include="ppy.osu.Framework" Version="2020.317.0" />
<PackageReference Include="SharpCompress" Version="0.24.0" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" />