diff --git a/osu-framework b/osu-framework index 2bd341b29d..e24d24ae70 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 2bd341b29d6a7ed864aa9c1c5fad4668dafe03a4 +Subproject commit e24d24ae70a78cea5a11635c37d2808d29233e96 diff --git a/osu-resources b/osu-resources index f6042e1cb3..a4418111f8 160000 --- a/osu-resources +++ b/osu-resources @@ -1 +1 @@ -Subproject commit f6042e1cb37cfad6c879d0e1245f7880c7fcd5f5 +Subproject commit a4418111f8ed2350a6fd46fe69258884f0757745 diff --git a/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj b/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj index c6474eae5a..c090342a4b 100644 --- a/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj +++ b/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj @@ -9,7 +9,7 @@ Properties osu.Desktop.Deploy osu.Desktop.Deploy - v4.5.2 + v4.6.1 512 true diff --git a/osu.Desktop.Tests/Visual/OsuTestCase.cs b/osu.Desktop.Tests/Visual/OsuTestCase.cs index e366aecb21..1b48ded4e2 100644 --- a/osu.Desktop.Tests/Visual/OsuTestCase.cs +++ b/osu.Desktop.Tests/Visual/OsuTestCase.cs @@ -1,17 +1,14 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using NUnit.Framework; using osu.Framework.Desktop.Platform; using osu.Framework.Testing; using osu.Game; namespace osu.Desktop.Tests.Visual { - [TestFixture] public abstract class OsuTestCase : TestCase { - [Test] public override void RunTest() { using (var host = new HeadlessGameHost(realtime: false)) diff --git a/osu.Desktop.Tests/Visual/TestCaseBeatmapDetails.cs b/osu.Desktop.Tests/Visual/TestCaseBeatmapDetails.cs index 11a15cf56f..d0f631201a 100644 --- a/osu.Desktop.Tests/Visual/TestCaseBeatmapDetails.cs +++ b/osu.Desktop.Tests/Visual/TestCaseBeatmapDetails.cs @@ -12,50 +12,104 @@ namespace osu.Desktop.Tests.Visual { public override string Description => "BeatmapDetails tab of BeatmapDetailArea"; - private readonly BeatmapDetails details; - public TestCaseBeatmapDetails() { + BeatmapDetails details; Add(details = new BeatmapDetails { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(150), - Beatmap = new BeatmapInfo + }); + + AddStep("beatmap all metrics", () => details.Beatmap = new BeatmapInfo + { + Version = "All Metrics", + Metadata = new BeatmapMetadata { - Version = "VisualTest", - Metadata = new BeatmapMetadata - { - Source = "Some guy", - Tags = "beatmap metadata example with a very very long list of tags and not much creativity", - }, - Difficulty = new BeatmapDifficulty - { - CircleSize = 7, - ApproachRate = 3.5f, - OverallDifficulty = 5.7f, - DrainRate = 1, - }, - StarDifficulty = 5.3f, - Metrics = new BeatmapMetrics - { - Ratings = Enumerable.Range(0, 10), - Fails = Enumerable.Range(lastRange, 100).Select(i => i % 12 - 6), - Retries = Enumerable.Range(lastRange - 3, 100).Select(i => i % 12 - 6), - }, + Source = "osu!lazer", + Tags = "this beatmap has all the metrics", + }, + Difficulty = new BeatmapDifficulty + { + CircleSize = 7, + DrainRate = 1, + OverallDifficulty = 5.7f, + ApproachRate = 3.5f, + }, + StarDifficulty = 5.3f, + Metrics = new BeatmapMetrics + { + Ratings = Enumerable.Range(0, 10), + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), }, }); - AddRepeatStep("fail values", newRetryAndFailValues, 10); - } + AddStep("beatmap ratings", () => details.Beatmap = new BeatmapInfo + { + Version = "Only Ratings", + Metadata = new BeatmapMetadata + { + Source = "osu!lazer", + Tags = "this beatmap has ratings metrics but not retries or fails", + }, + Difficulty = new BeatmapDifficulty + { + CircleSize = 6, + DrainRate = 9, + OverallDifficulty = 6, + ApproachRate = 6, + }, + StarDifficulty = 4.8f, + Metrics = new BeatmapMetrics + { + Ratings = Enumerable.Range(0, 10), + }, + }); - private int lastRange = 1; + AddStep("beatmap fails retries", () => details.Beatmap = new BeatmapInfo + { + Version = "Only Retries and Fails", + Metadata = new BeatmapMetadata + { + Source = "osu!lazer", + Tags = "this beatmap has retries and fails but no ratings", + }, + Difficulty = new BeatmapDifficulty + { + CircleSize = 3.7f, + DrainRate = 6, + OverallDifficulty = 6, + ApproachRate = 7, + }, + StarDifficulty = 2.91f, + Metrics = new BeatmapMetrics + { + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + }, + }); - private void newRetryAndFailValues() - { - details.Beatmap.Metrics.Fails = Enumerable.Range(lastRange, 100).Select(i => i % 12 - 6); - details.Beatmap.Metrics.Retries = Enumerable.Range(lastRange - 3, 100).Select(i => i % 12 - 6); - details.Beatmap = details.Beatmap; - lastRange += 100; + AddStep("beatmap no metrics", () => details.Beatmap = new BeatmapInfo + { + Version = "No Metrics", + Metadata = new BeatmapMetadata + { + Source = "osu!lazer", + Tags = "this beatmap has no metrics", + }, + Difficulty = new BeatmapDifficulty + { + CircleSize = 5, + DrainRate = 5, + OverallDifficulty = 5.5f, + ApproachRate = 6.5f, + }, + StarDifficulty = 1.97f, + Metrics = new BeatmapMetrics(), + }); + + AddStep("null beatmap", () => details.Beatmap = null); } } -} \ No newline at end of file +} diff --git a/osu.Desktop.Tests/Visual/TestCaseDirect.cs b/osu.Desktop.Tests/Visual/TestCaseDirect.cs index b78ea02767..9fd93c3f1e 100644 --- a/osu.Desktop.Tests/Visual/TestCaseDirect.cs +++ b/osu.Desktop.Tests/Visual/TestCaseDirect.cs @@ -41,12 +41,14 @@ namespace osu.Desktop.Tests.Visual { new BeatmapSetInfo { + OnlineBeatmapSetID = 578332, Metadata = new BeatmapMetadata { Title = @"OrVid", Artist = @"An", Author = @"RLC", Source = @"", + Tags = @"acuticnotes an-fillnote revid tear tearvid encrpted encryption axi axivid quad her hervid recoll", }, OnlineInfo = new BeatmapSetOnlineInfo { @@ -71,12 +73,14 @@ namespace osu.Desktop.Tests.Visual }, new BeatmapSetInfo { + OnlineBeatmapSetID = 599627, Metadata = new BeatmapMetadata { Title = @"tiny lamp", Artist = @"fhana", Author = @"Sotarks", Source = @"ぎんぎつね", + Tags = @"lantis junichi sato yuxuki waga kevin mitsunaga towana gingitsune opening op full ver version kalibe collab collaboration", }, OnlineInfo = new BeatmapSetOnlineInfo { @@ -101,12 +105,14 @@ namespace osu.Desktop.Tests.Visual }, new BeatmapSetInfo { + OnlineBeatmapSetID = 513268, Metadata = new BeatmapMetadata { Title = @"At Gwanghwamun", Artist = @"KYUHYUN", Author = @"Cerulean Veyron", Source = @"", + Tags = @"soul ballad kh super junior sj suju 슈퍼주니어 kt뮤직 sm엔터테인먼트 s.m.entertainment kt music 1st mini album ep", }, OnlineInfo = new BeatmapSetOnlineInfo { @@ -146,12 +152,14 @@ namespace osu.Desktop.Tests.Visual }, new BeatmapSetInfo { + OnlineBeatmapSetID = 586841, Metadata = new BeatmapMetadata { Title = @"RHAPSODY OF BLUE SKY", Artist = @"fhana", Author = @"[Kamiya]", Source = @"小林さんちのメイドラゴン", + Tags = @"kobayashi san chi no maidragon aozora no opening anime maid dragon oblivion karen dynamix imoutosan pata-mon gxytcgxytc", }, OnlineInfo = new BeatmapSetOnlineInfo { diff --git a/osu.Desktop.Tests/Visual/TestCaseEditorMenuBar.cs b/osu.Desktop.Tests/Visual/TestCaseEditorMenuBar.cs new file mode 100644 index 0000000000..9cb3053ff2 --- /dev/null +++ b/osu.Desktop.Tests/Visual/TestCaseEditorMenuBar.cs @@ -0,0 +1,84 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Edit.Menus; + +namespace osu.Desktop.Tests.Visual +{ + public class TestCaseEditorMenuBar : OsuTestCase + { + public TestCaseEditorMenuBar() + { + Add(new EditorMenuBar + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Y = 50, + Items = new[] + { + new EditorMenuBarItem("File") + { + Items = new[] + { + new EditorMenuItem("Clear All Notes"), + new EditorMenuItem("Open Difficulty..."), + new EditorMenuItem("Save"), + new EditorMenuItem("Create a new Difficulty..."), + new EditorMenuItemSpacer(), + new EditorMenuItem("Revert to Saved"), + new EditorMenuItem("Revert to Saved (Full)"), + new EditorMenuItemSpacer(), + new EditorMenuItem("Test Beatmap"), + new EditorMenuItem("Open AiMod"), + new EditorMenuItemSpacer(), + new EditorMenuItem("Upload Beatmap..."), + new EditorMenuItem("Export Package"), + new EditorMenuItem("Export Map Package"), + new EditorMenuItem("Import from..."), + new EditorMenuItemSpacer(), + new EditorMenuItem("Open Song Folder"), + new EditorMenuItem("Open .osu in Notepad"), + new EditorMenuItem("Open .osb in Notepad"), + new EditorMenuItemSpacer(), + new EditorMenuItem("Exit"), + } + }, + new EditorMenuBarItem("Timing") + { + Items = new[] + { + new EditorMenuItem("Time Signature"), + new EditorMenuItem("Metronome Clicks"), + new EditorMenuItemSpacer(), + new EditorMenuItem("Add Timing Section"), + new EditorMenuItem("Add Inheriting Section"), + new EditorMenuItem("Reset Current Section"), + new EditorMenuItem("Delete Timing Section"), + new EditorMenuItem("Resnap Current Section"), + new EditorMenuItemSpacer(), + new EditorMenuItem("Timing Setup"), + new EditorMenuItemSpacer(), + new EditorMenuItem("Resnap All Notes", MenuItemType.Destructive), + new EditorMenuItem("Move all notes in time...", MenuItemType.Destructive), + new EditorMenuItem("Recalculate Slider Lengths", MenuItemType.Destructive), + new EditorMenuItem("Delete All Timing Sections", MenuItemType.Destructive), + new EditorMenuItemSpacer(), + new EditorMenuItem("Set Current Position as Preview Point"), + } + }, + new EditorMenuBarItem("Testing") + { + Items = new[] + { + new EditorMenuItem("Item 1"), + new EditorMenuItem("Item 2"), + new EditorMenuItem("Item 3"), + } + }, + } + }); + } + } +} diff --git a/osu.Desktop.Tests/Visual/TestCaseLeaderboard.cs b/osu.Desktop.Tests/Visual/TestCaseLeaderboard.cs index 5f8ee8795c..f67db2f41a 100644 --- a/osu.Desktop.Tests/Visual/TestCaseLeaderboard.cs +++ b/osu.Desktop.Tests/Visual/TestCaseLeaderboard.cs @@ -1,13 +1,13 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using OpenTK; using osu.Framework.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; -using OpenTK; namespace osu.Desktop.Tests.Visual { @@ -24,7 +24,7 @@ namespace osu.Desktop.Tests.Visual new Score { Rank = ScoreRank.XH, - Accuracy = 100, + Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, @@ -42,7 +42,7 @@ namespace osu.Desktop.Tests.Visual new Score { Rank = ScoreRank.X, - Accuracy = 100, + Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, @@ -60,7 +60,7 @@ namespace osu.Desktop.Tests.Visual new Score { Rank = ScoreRank.SH, - Accuracy = 100, + Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, @@ -78,7 +78,7 @@ namespace osu.Desktop.Tests.Visual new Score { Rank = ScoreRank.S, - Accuracy = 100, + Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, @@ -96,7 +96,7 @@ namespace osu.Desktop.Tests.Visual new Score { Rank = ScoreRank.A, - Accuracy = 100, + Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, @@ -114,7 +114,7 @@ namespace osu.Desktop.Tests.Visual new Score { Rank = ScoreRank.B, - Accuracy = 98.26, + Accuracy = 0.9826, MaxCombo = 244, TotalScore = 1707827, Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, @@ -132,7 +132,7 @@ namespace osu.Desktop.Tests.Visual new Score { Rank = ScoreRank.C, - Accuracy = 96.54, + Accuracy = 0.9654, MaxCombo = 244, TotalScore = 1707827, Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, @@ -150,7 +150,7 @@ namespace osu.Desktop.Tests.Visual new Score { Rank = ScoreRank.F, - Accuracy = 60.25, + Accuracy = 0.6025, MaxCombo = 244, TotalScore = 1707827, Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, @@ -168,7 +168,7 @@ namespace osu.Desktop.Tests.Visual new Score { Rank = ScoreRank.F, - Accuracy = 51.40, + Accuracy = 0.5140, MaxCombo = 244, TotalScore = 1707827, Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, @@ -186,7 +186,7 @@ namespace osu.Desktop.Tests.Visual new Score { Rank = ScoreRank.F, - Accuracy = 42.22, + Accuracy = 0.4222, MaxCombo = 244, TotalScore = 1707827, Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, diff --git a/osu.Desktop.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Desktop.Tests/Visual/TestCasePlaySongSelect.cs index 379100b543..8d1ae7d913 100644 --- a/osu.Desktop.Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Desktop.Tests/Visual/TestCasePlaySongSelect.cs @@ -32,7 +32,7 @@ namespace osu.Desktop.Tests.Visual backingDatabase.CreateTable(); rulesets = new RulesetStore(backingDatabase); - manager = new BeatmapManager(storage, null, backingDatabase, rulesets); + manager = new BeatmapManager(storage, null, backingDatabase, rulesets, null); for (int i = 0; i < 100; i += 10) manager.Import(createTestBeatmapSet(i)); diff --git a/osu.Desktop.Tests/osu.Desktop.Tests.csproj b/osu.Desktop.Tests/osu.Desktop.Tests.csproj index 24d112a45c..975af1a782 100644 --- a/osu.Desktop.Tests/osu.Desktop.Tests.csproj +++ b/osu.Desktop.Tests/osu.Desktop.Tests.csproj @@ -9,7 +9,7 @@ Properties osu.Desktop.Tests osu.Desktop.Tests - v4.5 + v4.6.1 512 @@ -87,6 +87,7 @@ + diff --git a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj index 8bba59207f..33019909c6 100644 --- a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj +++ b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj @@ -22,7 +22,7 @@ OnOutputUpdated false LocalIntranet - v4.5 + v4.6.1 true publish\ true diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index bbca4145c6..661c17699b 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -22,7 +22,7 @@ OnOutputUpdated false LocalIntranet - v4.5 + v4.6.1 true publish\ true diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 96f8a8df9b..214cbc7f50 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -93,8 +93,6 @@ namespace osu.Game.Rulesets.Catch } } - public override Mod GetAutoplayMod() => new ModAutoplay(); - public override string Description => "osu!catch"; public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o }; diff --git a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj index 79ef5f4ba8..2ae2262ac7 100644 --- a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj +++ b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj @@ -9,7 +9,7 @@ Properties osu.Game.Rulesets.Catch osu.Game.Rulesets.Catch - v4.5 + v4.6.1 512 diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index ed46da4f85..d852a54ab6 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -105,8 +105,6 @@ namespace osu.Game.Rulesets.Mania } } - public override Mod GetAutoplayMod() => new ModAutoplay(); - public override string Description => "osu!mania"; public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o }; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaMod.cs b/osu.Game.Rulesets.Mania/Mods/ManiaMod.cs index f44ad6fd60..bb11a05fc8 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaMod.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaMod.cs @@ -68,6 +68,7 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModFadeIn : Mod { public override string Name => "FadeIn"; + public override string ShortenedName => "FI"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_hidden; public override ModType Type => ModType.DifficultyIncrease; public override double ScoreMultiplier => 1; @@ -78,12 +79,14 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModRandom : Mod { public override string Name => "Random"; + public override string ShortenedName => "RD"; public override string Description => @"Shuffle around the notes!"; public override double ScoreMultiplier => 1; } public abstract class ManiaKeyMod : Mod { + public override string ShortenedName => Name; public abstract int KeyCount { get; } public override double ScoreMultiplier => 1; // TODO: Implement the mania key mod score multiplier public override bool Ranked => true; @@ -146,6 +149,7 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModKeyCoop : Mod { public override string Name => "KeyCoop"; + public override string ShortenedName => "2P"; public override string Description => @"Double the key amount, double the fun!"; public override double ScoreMultiplier => 1; public override bool Ranked => true; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModGravity.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModGravity.cs index a054e0db56..132c49ab31 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModGravity.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModGravity.cs @@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModGravity : Mod, IGenerateSpeedAdjustments { public override string Name => "Gravity"; + public override string ShortenedName => "GR"; public override double ScoreMultiplier => 0; diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index ce4a84dc80..11f1d54b9d 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -197,7 +197,7 @@ namespace osu.Game.Rulesets.Mania.UI { if (action == Action) { - background.FadeTo(background.Alpha + 0.2f, 50, Easing.OutQuint); + background.FadeTo(0.6f, 50, Easing.OutQuint); keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint); } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 15f65b5545..8aa3a00f24 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Containers; using System; using osu.Game.Graphics; using osu.Framework.Allocation; -using OpenTK.Input; using System.Linq; using System.Collections.Generic; using osu.Framework.Configuration; @@ -24,12 +23,6 @@ namespace osu.Game.Rulesets.Mania.UI { public const float HIT_TARGET_POSITION = 50; - /// - /// Default column keys, expanding outwards from the middle as more column are added. - /// E.g. 2 columns use FJ, 4 columns use DFJK, 6 use SDFJKL, etc... - /// - private static readonly Key[] default_keys = { Key.A, Key.S, Key.D, Key.F, Key.J, Key.K, Key.L, Key.Semicolon }; - private SpecialColumnPosition specialColumnPosition; /// /// The style to use for the special column. diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj index 5c39139956..afd3b8e72d 100644 --- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj +++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj @@ -9,7 +9,7 @@ Properties osu.Game.Rulesets.Mania osu.Game.Rulesets.Mania - v4.5 + v4.6.1 512 diff --git a/osu.Game.Rulesets.Osu/Mods/OsuMod.cs b/osu.Game.Rulesets.Osu/Mods/OsuMod.cs index 432c6d391c..2970055bff 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuMod.cs @@ -96,6 +96,7 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModSpunOut : Mod { public override string Name => "Spun Out"; + public override string ShortenedName => "SO"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_spunout; public override string Description => @"Spinners will be automatically completed"; public override double ScoreMultiplier => 0.9; @@ -106,6 +107,7 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModAutopilot : Mod { public override string Name => "Autopilot"; + public override string ShortenedName => "AP"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_autopilot; public override string Description => @"Automatic cursor movement - just follow the rhythm."; public override double ScoreMultiplier => 0; @@ -126,6 +128,7 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModTarget : Mod { public override string Name => "Target"; + public override string ShortenedName => "TP"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_target; public override string Description => @""; public override double ScoreMultiplier => 1; diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs index 72ba421344..c6ecc3a506 100644 --- a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs +++ b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs @@ -51,6 +51,7 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing foreach (OsuDifficultyHitObject h in onScreen) { + // ReSharper disable once PossibleNullReferenceException (resharper not smart enough to understand IEnumerator.MoveNext()) h.TimeUntilHit -= latest.DeltaTime; // Calculate reading strain here } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 00413331e0..c943518b0b 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -112,8 +112,6 @@ namespace osu.Game.Rulesets.Osu } } - public override Mod GetAutoplayMod() => new OsuModAutoplay(); - public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_osu_o }; public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new OsuDifficultyCalculator(beatmap); diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index 0c9e53cf69..857f47f9b9 100644 --- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -9,7 +9,7 @@ Properties osu.Game.Rulesets.Osu osu.Game.Rulesets.Osu - v4.5 + v4.6.1 512 diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index 21d790169c..6b99b23a12 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -242,6 +242,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring base.Reset(); Health.Value = 0; + Accuracy.Value = 1; bonusScore = 0; comboPortion = 0; diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index f457732085..7b1452766e 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -95,8 +95,6 @@ namespace osu.Game.Rulesets.Taiko } } - public override Mod GetAutoplayMod() => new TaikoModAutoplay(); - public override string Description => "osu!taiko"; public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_taiko_o }; diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj index 1e07907cc8..a840997214 100644 --- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj +++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj @@ -9,7 +9,7 @@ Properties osu.Game.Rulesets.Taiko osu.Game.Rulesets.Taiko - v4.5 + v4.6.1 512 diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 8ec68b41be..220b1aac7f 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -7,7 +7,7 @@ Library osu.Game.Tests osu.Game.Tests - v4.5 + v4.6.1 true diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 82777734bb..15953fcd82 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -17,7 +17,7 @@ namespace osu.Game.Beatmaps public class Beatmap where T : HitObject { - public BeatmapInfo BeatmapInfo; + public BeatmapInfo BeatmapInfo = new BeatmapInfo(); public ControlPointInfo ControlPointInfo = new ControlPointInfo(); public List Breaks = new List(); public readonly List ComboColors = new List @@ -33,7 +33,7 @@ namespace osu.Game.Beatmaps /// /// The HitObjects this Beatmap contains. /// - public List HitObjects; + public List HitObjects = new List(); /// /// Total amount of break time in the beatmap. @@ -44,12 +44,13 @@ namespace osu.Game.Beatmaps /// Constructs a new beatmap. /// /// The original beatmap to use the parameters of. - public Beatmap(Beatmap original = null) + public Beatmap(Beatmap original = null) { BeatmapInfo = original?.BeatmapInfo.DeepClone() ?? BeatmapInfo; ControlPointInfo = original?.ControlPointInfo ?? ControlPointInfo; Breaks = original?.Breaks ?? Breaks; ComboColors = original?.ComboColors ?? ComboColors; + HitObjects = original?.HitObjects ?? HitObjects; } } @@ -65,7 +66,6 @@ namespace osu.Game.Beatmaps public Beatmap(Beatmap original = null) : base(original) { - HitObjects = original?.HitObjects; } } } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 551612330b..f58b3505c5 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -20,6 +20,9 @@ using osu.Game.IPC; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using SQLite.Net; +using osu.Game.Online.API.Requests; +using System.Threading.Tasks; +using osu.Game.Online.API; namespace osu.Game.Beatmaps { @@ -63,6 +66,10 @@ namespace osu.Game.Beatmaps private readonly BeatmapStore beatmaps; + private readonly APIAccess api; + + private readonly List currentDownloads = new List(); + // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) private BeatmapIPCChannel ipc; @@ -76,7 +83,7 @@ namespace osu.Game.Beatmaps /// public Func GetStableStorage { private get; set; } - public BeatmapManager(Storage storage, FileStore files, SQLiteConnection connection, RulesetStore rulesets, IIpcHost importHost = null) + public BeatmapManager(Storage storage, FileStore files, SQLiteConnection connection, RulesetStore rulesets, APIAccess api, IIpcHost importHost = null) { beatmaps = new BeatmapStore(connection); beatmaps.BeatmapSetAdded += s => BeatmapSetAdded?.Invoke(s); @@ -88,6 +95,7 @@ namespace osu.Game.Beatmaps this.files = files; this.connection = connection; this.rulesets = rulesets; + this.api = api; if (importHost != null) ipc = new BeatmapIPCChannel(importHost, this); @@ -177,6 +185,74 @@ namespace osu.Game.Beatmaps beatmaps.Add(beatmapSetInfo); } + /// + /// Downloads a beatmap. + /// + /// The to be downloaded. + /// A new , or an existing one if a download is already in progress. + public DownloadBeatmapSetRequest Download(BeatmapSetInfo beatmapSetInfo) + { + var existing = GetExistingDownload(beatmapSetInfo); + + if (existing != null) return existing; + + if (api == null) return null; + + ProgressNotification downloadNotification = new ProgressNotification + { + Text = $"Downloading {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}", + }; + + var request = new DownloadBeatmapSetRequest(beatmapSetInfo); + + request.DownloadProgressed += progress => + { + downloadNotification.State = ProgressNotificationState.Active; + downloadNotification.Progress = progress; + }; + + request.Success += data => + { + downloadNotification.State = ProgressNotificationState.Completed; + + using (var stream = new MemoryStream(data)) + using (var archive = new OszArchiveReader(stream)) + Import(archive); + + currentDownloads.Remove(request); + }; + + request.Failure += data => + { + downloadNotification.State = ProgressNotificationState.Completed; + Logger.Error(data, "Failed to get beatmap download information"); + currentDownloads.Remove(request); + }; + + downloadNotification.CancelRequested += () => + { + request.Cancel(); + currentDownloads.Remove(request); + downloadNotification.State = ProgressNotificationState.Cancelled; + return true; + }; + + currentDownloads.Add(request); + PostNotification?.Invoke(downloadNotification); + + // don't run in the main api queue as this is a long-running task. + Task.Run(() => request.Perform(api)); + + return request; + } + + /// + /// Get an existing download request if it exists. + /// + /// The whose download request is wanted. + /// The object if it exists, or null. + public DownloadBeatmapSetRequest GetExistingDownload(BeatmapSetInfo beatmap) => currentDownloads.Find(d => d.BeatmapSet.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID); + /// /// Delete a beatmap from the manager. /// Is a no-op for already deleted beatmaps. diff --git a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs index 4a389101e2..9c62289bfa 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs @@ -11,6 +11,8 @@ namespace osu.Game.Beatmaps.Drawables { public class BeatmapGroup : IStateful { + public event Action StateChanged; + public BeatmapPanel SelectedPanel; /// @@ -31,17 +33,18 @@ namespace osu.Game.Beatmaps.Drawables public BeatmapSetHeader Header; - private BeatmapGroupState state; - public List BeatmapPanels; public BeatmapSetInfo BeatmapSet; + private BeatmapGroupState state; public BeatmapGroupState State { get { return state; } set { + state = value; + switch (value) { case BeatmapGroupState.Expanded: @@ -60,7 +63,8 @@ namespace osu.Game.Beatmaps.Drawables panel.State = PanelSelectedState.Hidden; break; } - state = value; + + StateChanged?.Invoke(state); } } @@ -90,6 +94,7 @@ namespace osu.Game.Beatmaps.Drawables Header.AddDifficultyIcons(BeatmapPanels); } + private void headerGainedSelection(BeatmapSetHeader panel) { State = BeatmapGroupState.Expanded; diff --git a/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs b/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs index 2614baa116..41b77f6584 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs @@ -33,7 +33,8 @@ namespace osu.Game.Beatmaps.Drawables Normal, Hard, Insane, - Expert + Expert, + ExpertPlus } private DifficultyRating getDifficultyRating(BeatmapInfo beatmap) @@ -44,7 +45,8 @@ namespace osu.Game.Beatmaps.Drawables if (rating < 2.25) return DifficultyRating.Normal; if (rating < 3.75) return DifficultyRating.Hard; if (rating < 5.25) return DifficultyRating.Insane; - return DifficultyRating.Expert; + if (rating < 6.75) return DifficultyRating.Expert; + return DifficultyRating.ExpertPlus; } private Color4 getColour(BeatmapInfo beatmap) @@ -55,12 +57,14 @@ namespace osu.Game.Beatmaps.Drawables return palette.Green; default: case DifficultyRating.Normal: - return palette.Yellow; + return palette.Blue; case DifficultyRating.Hard: - return palette.Pink; + return palette.Yellow; case DifficultyRating.Insane: - return palette.Purple; + return palette.Pink; case DifficultyRating.Expert: + return palette.Purple; + case DifficultyRating.ExpertPlus: return palette.Gray0; } } diff --git a/osu.Game/Beatmaps/Drawables/Panel.cs b/osu.Game/Beatmaps/Drawables/Panel.cs index d07be88392..d6ed306b39 100644 --- a/osu.Game/Beatmaps/Drawables/Panel.cs +++ b/osu.Game/Beatmaps/Drawables/Panel.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using osu.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -15,6 +16,8 @@ namespace osu.Game.Beatmaps.Drawables { public const float MAX_HEIGHT = 80; + public event Action StateChanged; + public override bool RemoveWhenNotAlive => false; private readonly Container nestedContainer; @@ -77,11 +80,15 @@ namespace osu.Game.Beatmaps.Drawables set { - if (state == value) return; + if (state == value) + return; var last = state; state = value; + ApplyState(last); + + StateChanged?.Invoke(State); } } diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 479f274efb..ad0b110e48 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -7,7 +7,6 @@ using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; @@ -42,10 +41,7 @@ namespace osu.Game.Beatmaps this.game = game; } - protected override Beatmap GetBeatmap() => new Beatmap - { - HitObjects = new List(), - }; + protected override Beatmap GetBeatmap() => new Beatmap(); protected override Texture GetBackground() => game.Textures.Get(@"Backgrounds/bg4"); @@ -59,8 +55,6 @@ namespace osu.Game.Beatmaps { public override IEnumerable GetModsFor(ModType type) => new Mod[] { }; - public override Mod GetAutoplayMod() => new ModAutoplay(); - public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) { throw new NotImplementedException(); diff --git a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs index 234d65eee4..81695c3b5a 100644 --- a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.IO; -using osu.Game.Rulesets.Objects; namespace osu.Game.Beatmaps.Formats { @@ -21,7 +20,7 @@ namespace osu.Game.Beatmaps.Formats { string line; do { line = stream.ReadLine()?.Trim(); } - while (line != null && line.Length == 0); + while (line != null && line.Length == 0); if (line == null || !decoders.ContainsKey(line)) throw new IOException(@"Unknown file format"); @@ -47,7 +46,6 @@ namespace osu.Game.Beatmaps.Formats { var beatmap = new Beatmap { - HitObjects = new List(), BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata(), diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 462f94ed7c..4797f438d0 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -54,7 +54,7 @@ namespace osu.Game.Beatmaps { if (beatmap != null) return beatmap; - beatmap = GetBeatmap(); + beatmap = GetBeatmap() ?? new Beatmap(); // use the database-backed info. beatmap.BeatmapInfo = BeatmapInfo; diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 0713fa1a52..4ea4f4cdc3 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -19,12 +19,12 @@ namespace osu.Game.Graphics.Containers samplePopIn = audio.Sample.Get(@"UI/melodic-5"); samplePopOut = audio.Sample.Get(@"UI/melodic-4"); - StateChanged += OsuFocusedOverlayContainer_StateChanged; + StateChanged += onStateChanged; } - private void OsuFocusedOverlayContainer_StateChanged(VisibilityContainer arg1, Visibility arg2) + private void onStateChanged(Visibility visibility) { - switch (arg2) + switch (visibility) { case Visibility.Visible: samplePopIn?.Play(); diff --git a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs index b3e53280fb..65ece51a70 100644 --- a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs +++ b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using OpenTK; using osu.Framework; using osu.Framework.Graphics; @@ -35,6 +36,8 @@ namespace osu.Game.Graphics.UserInterface private class BreadcrumbTabItem : OsuTabItem, IStateful { + public event Action StateChanged; + public readonly SpriteIcon Chevron; //don't allow clicking between transitions and don't make the chevron clickable @@ -42,6 +45,7 @@ namespace osu.Game.Graphics.UserInterface public override bool HandleInput => State == Visibility.Visible; private Visibility state; + public Visibility State { get { return state; } @@ -62,6 +66,8 @@ namespace osu.Game.Graphics.UserInterface this.FadeOut(transition_duration, Easing.OutQuint); this.ScaleTo(new Vector2(0.8f, 1f), transition_duration, Easing.OutQuint); } + + StateChanged?.Invoke(State); } } diff --git a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs index 808c72ee5d..4ce6c98744 100644 --- a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs @@ -14,14 +14,17 @@ namespace osu.Game.Graphics.UserInterface private const int fade_duration = 250; public OsuContextMenu() + : base(Direction.Vertical) { - CornerRadius = 5; - EdgeEffect = new EdgeEffectParameters + MaskingContainer.CornerRadius = 5; + MaskingContainer.EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, Colour = Color4.Black.Opacity(0.1f), Radius = 4, }; + + ItemsContainer.Padding = new MarginPadding { Vertical = DrawableOsuMenuItem.MARGIN_VERTICAL }; } [BackgroundDependencyLoader] @@ -32,7 +35,5 @@ namespace osu.Game.Graphics.UserInterface protected override void AnimateOpen() => this.FadeIn(fade_duration, Easing.OutQuint); protected override void AnimateClose() => this.FadeOut(fade_duration, Easing.OutQuint); - - protected override MarginPadding ItemFlowContainerPadding => new MarginPadding { Vertical = DrawableOsuMenuItem.MARGIN_VERTICAL }; } } \ No newline at end of file diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index dde154bb61..f605804aaa 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -57,6 +57,9 @@ namespace osu.Game.Graphics.UserInterface { CornerRadius = 4; BackgroundColour = Color4.Black.Opacity(0.5f); + + // todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring + ItemsContainer.Padding = new MarginPadding(5); } // todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring @@ -64,13 +67,18 @@ namespace osu.Game.Graphics.UserInterface protected override void AnimateClose() => this.FadeOut(300, Easing.OutQuint); // todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring - protected override MarginPadding ItemFlowContainerPadding => new MarginPadding(5); - - // todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring - protected override void UpdateMenuHeight() + protected override void UpdateSize(Vector2 newSize) { - var actualHeight = (RelativeSizeAxes & Axes.Y) > 0 ? 1 : ContentHeight; - this.ResizeHeightTo(State == MenuState.Opened ? actualHeight : 0, 300, Easing.OutQuint); + if (Direction == Direction.Vertical) + { + Width = newSize.X; + this.ResizeHeightTo(newSize.Y, 300, Easing.OutQuint); + } + else + { + Height = newSize.Y; + this.ResizeWidthTo(newSize.X, 300, Easing.OutQuint); + } } private Color4 accentColour; @@ -88,7 +96,7 @@ namespace osu.Game.Graphics.UserInterface protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableOsuDropdownMenuItem(item) { AccentColour = accentColour }; #region DrawableOsuDropdownMenuItem - protected class DrawableOsuDropdownMenuItem : DrawableDropdownMenuItem, IHasAccentColour + public class DrawableOsuDropdownMenuItem : DrawableDropdownMenuItem, IHasAccentColour { private Color4? accentColour; public Color4 AccentColour @@ -141,7 +149,7 @@ namespace osu.Game.Graphics.UserInterface protected override Drawable CreateContent() => new Content(); - protected class Content : FillFlowContainer, IHasText + protected new class Content : FillFlowContainer, IHasText { public string Text { diff --git a/osu.Game/Graphics/UserInterface/OsuMenu.cs b/osu.Game/Graphics/UserInterface/OsuMenu.cs index ab37fd2c90..3fd5481152 100644 --- a/osu.Game/Graphics/UserInterface/OsuMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuMenu.cs @@ -12,30 +12,45 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Game.Graphics.Sprites; +using OpenTK; namespace osu.Game.Graphics.UserInterface { public class OsuMenu : Menu { - public OsuMenu() + public OsuMenu(Direction direction, bool topLevelMenu = false) + : base(direction, topLevelMenu) { - CornerRadius = 4; BackgroundColour = Color4.Black.Opacity(0.5f); + + MaskingContainer.CornerRadius = 4; + ItemsContainer.Padding = new MarginPadding(5); } protected override void AnimateOpen() => this.FadeIn(300, Easing.OutQuint); protected override void AnimateClose() => this.FadeOut(300, Easing.OutQuint); - protected override void UpdateMenuHeight() + protected override void UpdateSize(Vector2 newSize) { - var actualHeight = (RelativeSizeAxes & Axes.Y) > 0 ? 1 : ContentHeight; - this.ResizeHeightTo(State == MenuState.Opened ? actualHeight : 0, 300, Easing.OutQuint); + if (Direction == Direction.Vertical) + { + Width = newSize.X; + this.ResizeHeightTo(newSize.Y, 300, Easing.OutQuint); + } + else + { + Height = newSize.Y; + this.ResizeWidthTo(newSize.X, 300, Easing.OutQuint); + } } - protected override MarginPadding ItemFlowContainerPadding => new MarginPadding(5); - protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableOsuMenuItem(item); + protected override Menu CreateSubMenu() => new OsuMenu(Direction.Vertical) + { + Anchor = Direction == Direction.Horizontal ? Anchor.BottomLeft : Anchor.TopRight + }; + protected class DrawableOsuMenuItem : DrawableMenuItem { private const int margin_horizontal = 17; @@ -51,7 +66,6 @@ namespace osu.Game.Graphics.UserInterface public DrawableOsuMenuItem(MenuItem item) : base(item) { - } [BackgroundDependencyLoader] @@ -104,9 +118,10 @@ namespace osu.Game.Graphics.UserInterface return base.OnClick(state); } - protected override Drawable CreateContent() => text = new TextContainer(); + protected sealed override Drawable CreateContent() => text = CreateTextContainer(); + protected virtual TextContainer CreateTextContainer() => new TextContainer(); - private class TextContainer : Container, IHasText + protected class TextContainer : Container, IHasText { public string Text { diff --git a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs index 4c108e793a..8c777f491b 100644 --- a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs +++ b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs @@ -15,11 +15,7 @@ namespace osu.Game.Graphics.UserInterface.Volume { private readonly VolumeMeter volumeMeterMaster; - private void volumeChanged(double newVolume) - { - Show(); - schedulePopOut(); - } + protected override bool BlockPassThroughMouse => false; public VolumeControl() { @@ -85,6 +81,12 @@ namespace osu.Game.Graphics.UserInterface.Volume return false; } + private void volumeChanged(double newVolume) + { + Show(); + schedulePopOut(); + } + [BackgroundDependencyLoader] private void load(AudioManager audio) { diff --git a/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs b/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs new file mode 100644 index 0000000000..5a9f609bca --- /dev/null +++ b/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using System; + +namespace osu.Game.Online.API.Requests +{ + public class DownloadBeatmapSetRequest : APIDownloadRequest + { + public readonly BeatmapSetInfo BeatmapSet; + + public Action DownloadProgressed; + + public DownloadBeatmapSetRequest(BeatmapSetInfo set) + { + BeatmapSet = set; + + Progress += (current, total) => DownloadProgressed?.Invoke((float) current / total); + } + + protected override string Target => $@"beatmapsets/{BeatmapSet.OnlineBeatmapSetID}/download"; + } +} diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index 966049429e..ef9ee85d25 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -1,10 +1,14 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Collections.Generic; +using System.Linq; using Newtonsoft.Json; using osu.Framework.IO.Network; using osu.Game.Beatmaps; +using osu.Game.Users; +using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; namespace osu.Game.Online.API.Requests @@ -16,6 +20,14 @@ namespace osu.Game.Online.API.Requests public GetScoresRequest(BeatmapInfo beatmap) { this.beatmap = beatmap; + + Success += onSuccess; + } + + private void onSuccess(GetScoresResponse r) + { + foreach (OnlineScore score in r.Scores) + score.ApplyBeatmap(beatmap); } protected override WebRequest CreateWebRequest() @@ -32,6 +44,88 @@ namespace osu.Game.Online.API.Requests public class GetScoresResponse { [JsonProperty(@"scores")] - public IEnumerable Scores; + public IEnumerable Scores; } -} \ No newline at end of file + + public class OnlineScore : Score + { + [JsonProperty(@"score")] + private double totalScore + { + set { TotalScore = value; } + } + + [JsonProperty(@"max_combo")] + private int maxCombo + { + set { MaxCombo = value; } + } + + [JsonProperty(@"user")] + private User user + { + set { User = value; } + } + + [JsonProperty(@"replay_data")] + private Replay replay + { + set { Replay = value; } + } + + [JsonProperty(@"score_id")] + private long onlineScoreID + { + set { OnlineScoreID = value; } + } + + [JsonProperty(@"created_at")] + private DateTimeOffset date + { + set { Date = value; } + } + + [JsonProperty(@"statistics")] + private Dictionary jsonStats + { + set + { + foreach (var kvp in value) + { + string key = kvp.Key; + switch (key) + { + case @"count_300": + key = @"300"; + break; + case @"count_100": + key = @"100"; + break; + case @"count_50": + key = @"50"; + break; + case @"count_miss": + key = @"x"; + break; + default: + continue; + } + + Statistics.Add(key, kvp.Value); + } + } + } + + [JsonProperty(@"mods")] + private string[] modStrings { get; set; } + + public void ApplyBeatmap(BeatmapInfo beatmap) + { + Beatmap = beatmap; + Ruleset = beatmap.Ruleset; + + // Evaluate the mod string + Mods = Ruleset.CreateInstance().GetAllMods().Where(mod => modStrings.Contains(mod.ShortenedName)).ToArray(); + } + } +} diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index bec4aadb18..77683ae857 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -30,7 +30,7 @@ namespace osu.Game.Online.Chat public Bindable Joined = new Bindable(); - public bool ReadOnly => Name != "#lazer"; + public bool ReadOnly => false; public const int MAX_HISTORY = 300; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 30bc09d50f..b4fbdfb252 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -230,13 +230,13 @@ namespace osu.Game var singleDisplayOverlays = new OverlayContainer[] { chat, social, direct }; foreach (var overlay in singleDisplayOverlays) { - overlay.StateChanged += (container, state) => + overlay.StateChanged += state => { if (state == Visibility.Hidden) return; foreach (var c in singleDisplayOverlays) { - if (c == container) continue; + if (c == overlay) continue; c.State = Visibility.Hidden; } }; @@ -245,7 +245,11 @@ namespace osu.Game LoadComponentAsync(Toolbar = new Toolbar { Depth = -4, - OnHome = delegate { intro?.ChildScreen?.MakeCurrent(); }, + OnHome = delegate + { + hideAllOverlays(); + intro?.ChildScreen?.MakeCurrent(); + }, }, overlayContent.Add); settings.StateChanged += delegate @@ -310,6 +314,16 @@ namespace osu.Game private OsuScreen currentScreen; private FrameworkConfigManager frameworkConfig; + private void hideAllOverlays() + { + settings.State = Visibility.Hidden; + chat.State = Visibility.Hidden; + direct.State = Visibility.Hidden; + social.State = Visibility.Hidden; + userProfile.State = Visibility.Hidden; + notificationOverlay.State = Visibility.Hidden; + } + private void screenChanged(Screen newScreen) { currentScreen = newScreen as OsuScreen; @@ -323,19 +337,12 @@ namespace osu.Game //central game screen change logic. if (!currentScreen.ShowOverlays) { - settings.State = Visibility.Hidden; - Toolbar.State = Visibility.Hidden; + hideAllOverlays(); musicController.State = Visibility.Hidden; - chat.State = Visibility.Hidden; - direct.State = Visibility.Hidden; - social.State = Visibility.Hidden; - userProfile.State = Visibility.Hidden; - notificationOverlay.State = Visibility.Hidden; + Toolbar.State = Visibility.Hidden; } else - { Toolbar.State = Visibility.Visible; - } ScreenChanged?.Invoke(newScreen); } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index a7136ce803..76eb7d5101 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -106,9 +106,15 @@ namespace osu.Game connection.CreateTable(); + dependencies.Cache(API = new APIAccess + { + Username = LocalConfig.Get(OsuSetting.Username), + Token = LocalConfig.Get(OsuSetting.Token) + }); + dependencies.Cache(RulesetStore = new RulesetStore(connection)); dependencies.Cache(FileStore = new FileStore(connection, Host.Storage)); - dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, FileStore, connection, RulesetStore, Host)); + dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, FileStore, connection, RulesetStore, API, Host)); dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, connection, Host, BeatmapManager, RulesetStore)); dependencies.Cache(KeyBindingStore = new KeyBindingStore(connection, RulesetStore)); dependencies.Cache(new OsuColour()); @@ -144,12 +150,6 @@ namespace osu.Game Beatmap = new NonNullableBindable(defaultBeatmap); BeatmapManager.DefaultBeatmap = defaultBeatmap; - dependencies.Cache(API = new APIAccess - { - Username = LocalConfig.Get(OsuSetting.Username), - Token = LocalConfig.Get(OsuSetting.Token) - }); - Beatmap.ValueChanged += b => { // compare to last beatmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo) diff --git a/osu.Game/Overlays/Chat/ChannelListItem.cs b/osu.Game/Overlays/Chat/ChannelListItem.cs index f43154ea20..8360e793d8 100644 --- a/osu.Game/Overlays/Chat/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/ChannelListItem.cs @@ -76,7 +76,6 @@ namespace osu.Game.Overlays.Chat Size = new Vector2(text_size), Shadow = false, Margin = new MarginPadding { Right = 10f }, - Alpha = 0f, }, }, }, @@ -109,7 +108,6 @@ namespace osu.Game.Overlays.Chat TextSize = text_size, Font = @"Exo2.0-SemiBold", Shadow = false, - Alpha = 0.8f, }, }, }, @@ -151,6 +149,9 @@ namespace osu.Game.Overlays.Chat joinedBind.ValueChanged += updateColour; joinedBind.BindTo(channel.Joined); + + joinedBind.TriggerChange(); + FinishTransforms(true); } protected override bool OnHover(InputState state) diff --git a/osu.Game/Overlays/Chat/ChatTabControl.cs b/osu.Game/Overlays/Chat/ChatTabControl.cs index 4ff9169877..9f1028c168 100644 --- a/osu.Game/Overlays/Chat/ChatTabControl.cs +++ b/osu.Game/Overlays/Chat/ChatTabControl.cs @@ -16,15 +16,16 @@ using osu.Game.Online.Chat; using OpenTK; using OpenTK.Graphics; using osu.Framework.Configuration; +using System; namespace osu.Game.Overlays.Chat { public class ChatTabControl : OsuTabControl { - protected override TabItem CreateTabItem(Channel value) => new ChannelTabItem(value); - private const float shear_width = 10; + public Action OnRequestLeave; + public readonly Bindable ChannelSelectorActive = new Bindable(); private readonly ChannelTabItem.ChannelSelectorTabItem selectorTab; @@ -49,6 +50,20 @@ namespace osu.Game.Overlays.Chat ChannelSelectorActive.BindTo(selectorTab.Active); } + protected override void AddTabItem(TabItem item, bool addToDropdown = true) + { + if (selectorTab.Depth < float.MaxValue) + // performTabSort might've made selectorTab's position wonky, fix it + TabContainer.ChangeChildDepth(selectorTab, float.MaxValue); + + base.AddTabItem(item, addToDropdown); + + if (SelectedTab == null) + SelectTab(item); + } + + protected override TabItem CreateTabItem(Channel value) => new ChannelTabItem(value) { OnRequestClose = tabCloseRequested }; + protected override void SelectTab(TabItem tab) { if (tab is ChannelTabItem.ChannelSelectorTabItem) @@ -62,18 +77,38 @@ namespace osu.Game.Overlays.Chat base.SelectTab(tab); } + private void tabCloseRequested(TabItem tab) + { + int totalTabs = TabContainer.Count - 1; // account for selectorTab + int currentIndex = MathHelper.Clamp(TabContainer.IndexOf(tab), 1, totalTabs); + + if (tab == SelectedTab && totalTabs > 1) + // Select the tab after tab-to-be-removed's index, or the tab before if current == last + SelectTab(TabContainer[currentIndex == totalTabs ? currentIndex - 1 : currentIndex + 1]); + else if (totalTabs == 1 && !selectorTab.Active) + // Open channel selection overlay if all channel tabs will be closed after removing this tab + SelectTab(selectorTab); + + OnRequestLeave?.Invoke(tab.Value); + } + private class ChannelTabItem : TabItem { private Color4 backgroundInactive; private Color4 backgroundHover; private Color4 backgroundActive; + public override bool IsRemovable => !Pinned; + private readonly SpriteText text; private readonly SpriteText textBold; + private readonly ClickableContainer closeButton; private readonly Box box; private readonly Box highlightBox; private readonly SpriteIcon icon; + public Action OnRequestClose; + private void updateState() { if (Active) @@ -108,6 +143,9 @@ namespace osu.Game.Overlays.Chat protected override bool OnHover(InputState state) { + if (IsRemovable) + closeButton.FadeIn(200, Easing.OutQuint); + if (!Active) box.FadeColour(backgroundHover, transition_length, Easing.OutQuint); return true; @@ -115,6 +153,7 @@ namespace osu.Game.Overlays.Chat protected override void OnHoverLost(InputState state) { + closeButton.FadeOut(200, Easing.OutQuint); updateState(); } @@ -204,13 +243,69 @@ namespace osu.Game.Overlays.Chat Font = @"Exo2.0-Bold", TextSize = 18, }, - } - } + closeButton = new CloseButton + { + Alpha = 0, + Margin = new MarginPadding { Right = 20 }, + Origin = Anchor.CentreRight, + Anchor = Anchor.CentreRight, + Action = delegate + { + if (IsRemovable) OnRequestClose?.Invoke(this); + }, + }, + }, + }, }; } + public class CloseButton : ClickableContainer + { + private readonly SpriteIcon icon; + + public CloseButton() + { + Size = new Vector2(20); + + Child = icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.75f), + Icon = FontAwesome.fa_close, + RelativeSizeAxes = Axes.Both, + }; + } + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + icon.ScaleTo(0.5f, 1000, Easing.OutQuint); + return base.OnMouseDown(state, args); + } + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + { + icon.ScaleTo(0.75f, 1000, Easing.OutElastic); + return base.OnMouseUp(state, args); + } + + protected override bool OnHover(InputState state) + { + icon.FadeColour(Color4.Red, 200, Easing.OutQuint); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + icon.FadeColour(Color4.White, 200, Easing.OutQuint); + base.OnHoverLost(state); + } + } + public class ChannelSelectorTabItem : ChannelTabItem { + public override bool IsRemovable => false; + public ChannelSelectorTabItem(Channel value) : base(value) { Depth = float.MaxValue; diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 6dd5425fd1..b1deae8272 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -160,6 +160,7 @@ namespace osu.Game.Overlays channelTabs = new ChatTabControl { RelativeSizeAxes = Axes.Both, + OnRequestLeave = removeChannel, }, } }, @@ -169,7 +170,7 @@ namespace osu.Game.Overlays channelTabs.Current.ValueChanged += newChannel => CurrentChannel = newChannel; channelTabs.ChannelSelectorActive.ValueChanged += value => channelSelection.State = value ? Visibility.Visible : Visibility.Hidden; - channelSelection.StateChanged += (overlay, state) => + channelSelection.StateChanged += state => { channelTabs.ChannelSelectorActive.Value = state == Visibility.Visible; @@ -305,6 +306,7 @@ namespace osu.Game.Overlays addChannel(channels.Find(c => c.Name == @"#lobby")); channelSelection.OnRequestJoin = addChannel; + channelSelection.OnRequestLeave = removeChannel; channelSelection.Sections = new[] { new ChannelSection @@ -332,7 +334,15 @@ namespace osu.Game.Overlays set { - if (currentChannel == value || value == null) return; + if (currentChannel == value) return; + + if (value == null) + { + currentChannel = null; + textbox.Current.Disabled = true; + currentChannelContainer.Clear(false); + return; + } currentChannel = value; @@ -391,6 +401,19 @@ namespace osu.Game.Overlays channel.Joined.Value = true; } + private void removeChannel(Channel channel) + { + if (channel == null) return; + + if (channel == CurrentChannel) CurrentChannel = null; + + careChannels.Remove(channel); + loadedChannels.Remove(loadedChannels.Find(c => c.Channel == channel)); + channelTabs.RemoveItem(channel); + + channel.Joined.Value = false; + } + private void fetchInitialMessages(Channel channel) { var req = new GetMessagesRequest(new List { channel }, null); diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 012e93f10d..7853eefd2c 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -26,7 +26,7 @@ namespace osu.Game.Overlays dialogContainer.Add(currentDialog); currentDialog.Show(); - currentDialog.StateChanged += onDialogOnStateChanged; + currentDialog.StateChanged += state => onDialogOnStateChanged(dialog, state); State = Visibility.Visible; } diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index a642f72821..6f1f581d0b 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -4,8 +4,6 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; -using System.IO; -using System.Threading.Tasks; using OpenTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -20,8 +18,8 @@ using osu.Framework.Input; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Framework.Logging; -using osu.Game.Beatmaps.IO; using osu.Game.Overlays.Notifications; +using osu.Game.Online.API.Requests; namespace osu.Game.Overlays.Direct { @@ -97,6 +95,11 @@ namespace osu.Game.Overlays.Direct }, } }); + + var downloadRequest = beatmaps.GetExistingDownload(SetInfo); + + if (downloadRequest != null) + attachDownload(downloadRequest); } protected override bool OnHover(InputState state) @@ -115,23 +118,8 @@ namespace osu.Game.Overlays.Direct base.OnHoverLost(state); } - // this should eventually be moved to a more central place, like BeatmapManager. - private DownloadBeatmapSetRequest downloadRequest; - protected void StartDownload() { - if (api == null) return; - - // we already have an active download running. - if (downloadRequest != null) - { - content.MoveToX(-5, 50, Easing.OutSine).Then() - .MoveToX(5, 100, Easing.InOutSine).Then() - .MoveToX(-5, 100, Easing.InOutSine).Then() - .MoveToX(0, 50, Easing.InSine).Then(); - return; - } - if (!api.LocalUser.Value.IsSupporter) { notifications.Post(new SimpleNotification @@ -142,72 +130,43 @@ namespace osu.Game.Overlays.Direct return; } + if (beatmaps.GetExistingDownload(SetInfo) != null) + { + // we already have an active download running. + content.MoveToX(-5, 50, Easing.OutSine).Then() + .MoveToX(5, 100, Easing.InOutSine).Then() + .MoveToX(-5, 100, Easing.InOutSine).Then() + .MoveToX(0, 50, Easing.InSine).Then(); + + return; + } + + var request = beatmaps.Download(SetInfo); + + attachDownload(request); + } + + private void attachDownload(DownloadBeatmapSetRequest request) + { progressBar.FadeIn(400, Easing.OutQuint); progressBar.ResizeHeightTo(4, 400, Easing.OutQuint); progressBar.Current.Value = 0; - ProgressNotification downloadNotification = new ProgressNotification - { - Text = $"Downloading {SetInfo.Metadata.Artist} - {SetInfo.Metadata.Title}", - }; - - downloadRequest = new DownloadBeatmapSetRequest(SetInfo); - downloadRequest.Failure += e => + request.Failure += e => { progressBar.Current.Value = 0; progressBar.FadeOut(500); - downloadNotification.State = ProgressNotificationState.Completed; Logger.Error(e, "Failed to get beatmap download information"); - - downloadRequest = null; }; - downloadRequest.Progress += (current, total) => - { - float progress = (float)current / total; + request.DownloadProgressed += progress => progressBar.Current.Value = progress; - progressBar.Current.Value = progress; - - downloadNotification.State = ProgressNotificationState.Active; - downloadNotification.Progress = progress; - }; - - downloadRequest.Success += data => + request.Success += data => { progressBar.Current.Value = 1; progressBar.FadeOut(500); - - downloadNotification.State = ProgressNotificationState.Completed; - - using (var stream = new MemoryStream(data)) - using (var archive = new OszArchiveReader(stream)) - beatmaps.Import(archive); }; - - downloadNotification.CancelRequested += () => - { - downloadRequest.Cancel(); - downloadRequest = null; - return true; - }; - - notifications.Post(downloadNotification); - - // don't run in the main api queue as this is a long-running task. - Task.Run(() => downloadRequest.Perform(api)); - } - - public class DownloadBeatmapSetRequest : APIDownloadRequest - { - private readonly BeatmapSetInfo beatmapSet; - - public DownloadBeatmapSetRequest(BeatmapSetInfo beatmapSet) - { - this.beatmapSet = beatmapSet; - } - - protected override string Target => $@"beatmapsets/{beatmapSet.OnlineBeatmapSetID}/download"; } protected override void LoadComplete() diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index f734e43826..9c07e1087f 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -27,6 +27,7 @@ namespace osu.Game.Overlays private APIAccess api; private RulesetStore rulesets; + private BeatmapManager beatmaps; private readonly FillFlowContainer resultCountsContainer; private readonly OsuSpriteText resultCountsText; @@ -46,9 +47,26 @@ namespace osu.Game.Overlays set { if (beatmapSets?.Equals(value) ?? false) return; + beatmapSets = value; - recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value); + if (beatmapSets == null) return; + + var artists = new List(); + var songs = new List(); + var tags = new List(); + foreach (var s in beatmapSets) + { + artists.Add(s.Metadata.Artist); + songs.Add(s.Metadata.Title); + tags.AddRange(s.Metadata.Tags.Split(' ')); + } + + ResultAmounts = new ResultCounts(distinctCount(artists), distinctCount(songs), distinctCount(tags)); + + if (beatmapSets.Any() && panels == null) + // real use case? currently only seems to be for test case + recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value); } } @@ -147,6 +165,8 @@ namespace osu.Game.Overlays { this.api = api; this.rulesets = rulesets; + this.beatmaps = beatmaps; + resultCountsContainer.Colour = colours.Yellow; beatmaps.BeatmapSetAdded += setAdded; @@ -156,6 +176,7 @@ namespace osu.Game.Overlays { // if a new map was imported, we should remove it from search results (download completed etc.) panels?.FirstOrDefault(p => p.SetInfo.OnlineBeatmapSetID == set.OnlineBeatmapSetID)?.FadeOut(400).Expire(); + BeatmapSets = BeatmapSets?.Where(b => b.OnlineBeatmapSetID != set.OnlineBeatmapSetID); } private void updateResultCounts() @@ -237,20 +258,11 @@ namespace osu.Game.Overlays getSetsRequest.Success += r => { - BeatmapSets = r?.Select(response => response.ToBeatmapSet(rulesets)); - if (BeatmapSets == null) return; + BeatmapSets = r?. + Select(response => response.ToBeatmapSet(rulesets)). + Where(b => beatmaps.QueryBeatmapSet(q => q.OnlineBeatmapSetID == b.OnlineBeatmapSetID) == null); - var artists = new List(); - var songs = new List(); - var tags = new List(); - foreach (var s in BeatmapSets) - { - artists.Add(s.Metadata.Artist); - songs.Add(s.Metadata.Title); - tags.AddRange(s.Metadata.Tags.Split(' ')); - } - - ResultAmounts = new ResultCounts(distinctCount(artists), distinctCount(songs), distinctCount(tags)); + recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value); }; api.Queue(getSetsRequest); diff --git a/osu.Game/Overlays/MainSettings.cs b/osu.Game/Overlays/MainSettings.cs index b4d9cac045..4fe86d62fd 100644 --- a/osu.Game/Overlays/MainSettings.cs +++ b/osu.Game/Overlays/MainSettings.cs @@ -53,7 +53,7 @@ namespace osu.Game.Overlays private const float hidden_width = 120; - private void keyBindingOverlay_StateChanged(VisibilityContainer container, Visibility visibility) + private void keyBindingOverlay_StateChanged(Visibility visibility) { switch (visibility) { diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index 56b26e7176..3ac8af7b2b 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using osu.Framework; using OpenTK; using osu.Framework.Allocation; @@ -19,6 +20,8 @@ namespace osu.Game.Overlays.MedalSplash private const float scale_when_unlocked = 0.76f; private const float scale_when_full = 0.6f; + public event Action StateChanged; + private readonly Medal medal; private readonly Container medalContainer; private readonly Sprite medalSprite, medalGlow; @@ -132,6 +135,8 @@ namespace osu.Game.Overlays.MedalSplash state = value; updateState(); + + StateChanged?.Invoke(State); } } diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index cb4628825e..64d0d628f0 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -204,7 +204,7 @@ namespace osu.Game.Overlays beatmapBacking.BindTo(game.Beatmap); - playlist.StateChanged += (c, s) => playlistButton.FadeColour(s == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint); + playlist.StateChanged += s => playlistButton.FadeColour(s == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint); } protected override void LoadComplete() @@ -302,8 +302,8 @@ namespace osu.Game.Overlays else { //figure out the best direction based on order in playlist. - var last = playlist.BeatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo.ID).Count(); - var next = beatmap == null ? -1 : playlist.BeatmapSets.TakeWhile(b => b.ID != beatmap.BeatmapSetInfo.ID).Count(); + var last = playlist.BeatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count(); + var next = beatmap == null ? -1 : playlist.BeatmapSets.TakeWhile(b => b.ID != beatmap.BeatmapSetInfo?.ID).Count(); direction = last > next ? TransformDirection.Prev : TransformDirection.Next; } diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index a816fa56c1..e62050fae1 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -292,6 +292,8 @@ namespace osu.Game.Overlays.Settings.Sections.General Colour = Color4.Black.Opacity(0.25f), Radius = 4, }; + + ItemsContainer.Padding = new MarginPadding(); } [BackgroundDependencyLoader] @@ -300,8 +302,6 @@ namespace osu.Game.Overlays.Settings.Sections.General BackgroundColour = colours.Gray3; } - protected override MarginPadding ItemFlowContainerPadding => new MarginPadding(); - protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableUserDropdownMenuItem(item); private class DrawableUserDropdownMenuItem : DrawableOsuDropdownMenuItem diff --git a/osu.Game/Overlays/Settings/Sidebar.cs b/osu.Game/Overlays/Settings/Sidebar.cs index b22a5ab126..55167188a3 100644 --- a/osu.Game/Overlays/Settings/Sidebar.cs +++ b/osu.Game/Overlays/Settings/Sidebar.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Linq; using osu.Framework; using OpenTK; @@ -19,6 +20,9 @@ namespace osu.Game.Overlays.Settings private readonly FillFlowContainer content; internal const float DEFAULT_WIDTH = ToolbarButton.WIDTH; internal const int EXPANDED_WIDTH = 200; + + public event Action StateChanged; + protected override Container Content => content; public Sidebar() @@ -102,6 +106,8 @@ namespace osu.Game.Overlays.Settings this.ResizeTo(new Vector2(EXPANDED_WIDTH, Height), 500, Easing.OutQuint); break; } + + StateChanged?.Invoke(State); } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs index 6d61a096b2..c530e8d7ff 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs @@ -45,7 +45,7 @@ namespace osu.Game.Overlays.Toolbar stateContainer.StateChanged -= stateChanged; } - private void stateChanged(VisibilityContainer c, Visibility state) + private void stateChanged(Visibility state) { switch (state) { diff --git a/osu.Game/Overlays/WaveOverlayContainer.cs b/osu.Game/Overlays/WaveOverlayContainer.cs index fd89dcfbc4..4f9783a762 100644 --- a/osu.Game/Overlays/WaveOverlayContainer.cs +++ b/osu.Game/Overlays/WaveOverlayContainer.cs @@ -167,6 +167,8 @@ namespace osu.Game.Overlays private class Wave : Container, IStateful { + public event Action StateChanged; + public float FinalPosition; public Wave() @@ -200,6 +202,7 @@ namespace osu.Game.Overlays } private Visibility state; + public Visibility State { get { return state; } @@ -216,6 +219,8 @@ namespace osu.Game.Overlays this.MoveToY(FinalPosition, APPEAR_DURATION, easing_show); break; } + + StateChanged?.Invoke(State); } } } diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index f54267af29..7b0034863e 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -16,6 +16,11 @@ namespace osu.Game.Rulesets.Mods /// public abstract string Name { get; } + /// + /// The shortened name of this mod. + /// + public abstract string ShortenedName { get; } + /// /// The icon of this mod. /// diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index ca120e446e..2b10d098f3 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Mods public class ModAutoplay : Mod { public override string Name => "Autoplay"; + public override string ShortenedName => "AT"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_auto; public override string Description => "Watch a perfect automated play through the song"; public override double ScoreMultiplier => 0; diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 332bd2c5ac..581fbc5e3a 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -8,6 +8,7 @@ namespace osu.Game.Rulesets.Mods public class ModCinema : ModAutoplay { public override string Name => "Cinema"; + public override string ShortenedName => "CN"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_cinema; } } \ No newline at end of file diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index 3e878d7104..cbad224316 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -10,6 +10,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModDaycore : ModHalfTime { public override string Name => "Daycore"; + public override string ShortenedName => "DC"; public override FontAwesome Icon => FontAwesome.fa_question; public override string Description => "whoaaaaa"; diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index 1aab56a9fc..d8aa5ea1d2 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -10,6 +10,7 @@ namespace osu.Game.Rulesets.Mods public class ModDoubleTime : Mod, IApplicableToClock { public override string Name => "Double Time"; + public override string ShortenedName => "DT"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_doubletime; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Zoooooooooom"; diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index 075a62b0d7..aaf083fd9e 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -10,6 +10,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModEasy : Mod, IApplicableToDifficulty { public override string Name => "Easy"; + public override string ShortenedName => "EZ"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_easy; public override ModType Type => ModType.DifficultyReduction; public override string Description => "Reduces overall difficulty - larger circles, more forgiving HP drain, less accuracy required."; diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index b5ad859172..b7499e624a 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -8,6 +8,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModFlashlight : Mod { public override string Name => "Flashlight"; + public override string ShortenedName => "FL"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_flashlight; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Restricted view area."; diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 14aede0809..a9e49bb4b0 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -10,6 +10,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModHalfTime : Mod, IApplicableToClock { public override string Name => "Half Time"; + public override string ShortenedName => "HT"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_halftime; public override ModType Type => ModType.DifficultyReduction; public override string Description => "Less zoom"; diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs index f33f46a207..36d82362e3 100644 --- a/osu.Game/Rulesets/Mods/ModHardRock.cs +++ b/osu.Game/Rulesets/Mods/ModHardRock.cs @@ -10,6 +10,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModHardRock : Mod, IApplicableToDifficulty { public override string Name => "Hard Rock"; + public override string ShortenedName => "HR"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_hardrock; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Everything just got a bit harder..."; diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index 89b9b3b62d..25f6ad024d 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -8,6 +8,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModHidden : Mod { public override string Name => "Hidden"; + public override string ShortenedName => "HD"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_hidden; public override ModType Type => ModType.DifficultyIncrease; public override bool Ranked => true; diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index d04643fb8b..5cefd89023 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -10,6 +10,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModNightcore : ModDoubleTime { public override string Name => "Nightcore"; + public override string ShortenedName => "NC"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_nightcore; public override string Description => "uguuuuuuuu"; diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs index d41c4e3956..3a3878d77e 100644 --- a/osu.Game/Rulesets/Mods/ModNoFail.cs +++ b/osu.Game/Rulesets/Mods/ModNoFail.cs @@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModNoFail : Mod { public override string Name => "NoFail"; + public override string ShortenedName => "NF"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_nofail; public override ModType Type => ModType.DifficultyReduction; public override string Description => "You can't fail, no matter what."; diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs index 35217c8305..082370ea5d 100644 --- a/osu.Game/Rulesets/Mods/ModPerfect.cs +++ b/osu.Game/Rulesets/Mods/ModPerfect.cs @@ -6,6 +6,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModPerfect : ModSuddenDeath { public override string Name => "Perfect"; + public override string ShortenedName => "PF"; public override string Description => "SS or quit."; } } \ No newline at end of file diff --git a/osu.Game/Rulesets/Mods/ModRelax.cs b/osu.Game/Rulesets/Mods/ModRelax.cs index 5491f8fc58..a3f38e4ff6 100644 --- a/osu.Game/Rulesets/Mods/ModRelax.cs +++ b/osu.Game/Rulesets/Mods/ModRelax.cs @@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModRelax : Mod { public override string Name => "Relax"; + public override string ShortenedName => "RX"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_relax; public override double ScoreMultiplier => 0; public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModSuddenDeath) }; diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index 2bf0278046..999cb40f89 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModSuddenDeath : Mod { public override string Name => "Sudden Death"; + public override string ShortenedName => "SD"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_suddendeath; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Miss a note and fail."; diff --git a/osu.Game/Rulesets/Mods/MultiMod.cs b/osu.Game/Rulesets/Mods/MultiMod.cs index c5fac250d0..c40d107bda 100644 --- a/osu.Game/Rulesets/Mods/MultiMod.cs +++ b/osu.Game/Rulesets/Mods/MultiMod.cs @@ -6,6 +6,7 @@ namespace osu.Game.Rulesets.Mods public class MultiMod : Mod { public override string Name => string.Empty; + public override string ShortenedName => string.Empty; public override string Description => string.Empty; public override double ScoreMultiplier => 0.0; diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index c54aeb7852..33621662af 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -1,15 +1,17 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.UI; +using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.Scoring; +using osu.Game.Beatmaps; +using osu.Game.Graphics; using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets { @@ -19,9 +21,18 @@ namespace osu.Game.Rulesets public virtual IEnumerable GetBeatmapStatistics(WorkingBeatmap beatmap) => new BeatmapStatistic[] { }; + public IEnumerable GetAllMods() => Enum.GetValues(typeof(ModType)).Cast() + // Get all mod types as an IEnumerable + .SelectMany(GetModsFor) + // Confine all mods of each mod type into a single IEnumerable + .Where(mod => mod != null) + // Filter out all null mods + .SelectMany(mod => (mod as MultiMod)?.Mods ?? new[] { mod }); + // Resolve MultiMods as their .Mods property + public abstract IEnumerable GetModsFor(ModType type); - public abstract Mod GetAutoplayMod(); + public Mod GetAutoplayMod() => GetAllMods().First(mod => mod is ModAutoplay); protected Ruleset(RulesetInfo rulesetInfo) { diff --git a/osu.Game/Rulesets/Scoring/Score.cs b/osu.Game/Rulesets/Scoring/Score.cs index 6169bb7380..ff4632a9c2 100644 --- a/osu.Game/Rulesets/Scoring/Score.cs +++ b/osu.Game/Rulesets/Scoring/Score.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using Newtonsoft.Json; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Users; @@ -15,70 +14,30 @@ namespace osu.Game.Rulesets.Scoring { public ScoreRank Rank { get; set; } - [JsonProperty(@"score")] public double TotalScore { get; set; } public double Accuracy { get; set; } public double Health { get; set; } = 1; - [JsonProperty(@"max_combo")] public int MaxCombo { get; set; } public int Combo { get; set; } - [JsonProperty(@"mods")] - protected string[] ModStrings { get; set; } //todo: parse to Mod objects - public RulesetInfo Ruleset { get; set; } public Mod[] Mods { get; set; } - [JsonProperty(@"user")] public User User; - [JsonProperty(@"replay_data")] public Replay Replay; public BeatmapInfo Beatmap; - [JsonProperty(@"score_id")] public long OnlineScoreID; - [JsonProperty(@"created_at")] public DateTimeOffset Date; - [JsonProperty(@"statistics")] - private Dictionary jsonStats - { - set - { - foreach (var kvp in value) - { - string key = kvp.Key; - switch (key) - { - case @"count_300": - key = @"300"; - break; - case @"count_100": - key = @"100"; - break; - case @"count_50": - key = @"50"; - break; - case @"count_miss": - key = @"x"; - break; - default: - continue; - } - - Statistics.Add(key, kvp.Value); - } - } - } - public Dictionary Statistics = new Dictionary(); } } diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 986d8c92dc..47a39d4644 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.UI private readonly SpriteIcon modIcon; private readonly SpriteIcon background; - private const float icon_size = 80; + private const float background_size = 80; public FontAwesome Icon { @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.UI { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Size = new Vector2(icon_size), + Size = new Vector2(background_size), Icon = FontAwesome.fa_osu_mod_bg, Shadow = true, }, @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.UI Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, Colour = OsuColour.Gray(84), - Size = new Vector2(icon_size - 35), + Size = new Vector2(background_size - 35), Y = 25, Icon = mod.Icon }, diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index 9503ac1e0b..cae211fb9c 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -75,9 +75,6 @@ namespace osu.Game.Rulesets.UI internal RulesetContainer(Ruleset ruleset) { Ruleset = ruleset; - - KeyBindingInputManager = CreateInputManager(); - KeyBindingInputManager.RelativeSizeAxes = Axes.Both; } /// @@ -206,6 +203,9 @@ namespace osu.Game.Rulesets.UI // Post-process the beatmap processor.PostProcess(Beatmap); + KeyBindingInputManager = CreateInputManager(); + KeyBindingInputManager.RelativeSizeAxes = Axes.Both; + // Add mods, should always be the last thing applied to give full control to mods applyMods(Mods); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 9f33d624e2..be9098e3be 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -8,6 +8,11 @@ using osu.Framework.Screens; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Select; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Edit.Menus; namespace osu.Game.Screens.Edit { @@ -17,6 +22,175 @@ namespace osu.Game.Screens.Edit protected override BackgroundScreen CreateBackground() => new BackgroundScreenCustom(@"Backgrounds/bg4"); + internal override bool ShowOverlays => false; + + public Editor() + { + Add(new Container + { + RelativeSizeAxes = Axes.X, + Height = 40, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex("111") + }, + new EditorMenuBar + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + X = 100, + Items = new[] + { + new EditorMenuBarItem("File") + { + Items = new[] + { + new EditorMenuItem("Clear all notes"), + new EditorMenuItem("Open difficulty..."), + new EditorMenuItem("Save"), + new EditorMenuItem("Create new difficulty..."), + new EditorMenuItemSpacer(), + new EditorMenuItem("Revert to saved"), + new EditorMenuItem("Revert to saved (full"), + new EditorMenuItemSpacer(), + new EditorMenuItem("Test beatmap"), + new EditorMenuItem("Open AiMod"), + new EditorMenuItemSpacer(), + new EditorMenuItem("Upload Beatmap..."), + new EditorMenuItem("Export package"), + new EditorMenuItem("Export map package"), + new EditorMenuItem("Import from..."), + new EditorMenuItemSpacer(), + new EditorMenuItem("Open song folder"), + new EditorMenuItem("Open .osu in Notepad"), + new EditorMenuItem("Open .osb in Notepad"), + new EditorMenuItemSpacer(), + new EditorMenuItem("Exit", MenuItemType.Standard, Exit) + } + }, + new EditorMenuBarItem("Edit") + { + Items = new[] + { + new EditorMenuItem("Undo"), + new EditorMenuItem("Redo"), + new EditorMenuItemSpacer(), + new EditorMenuItem("Cut"), + new EditorMenuItem("Copy"), + new EditorMenuItem("Paste"), + new EditorMenuItem("Delete"), + new EditorMenuItemSpacer(), + new EditorMenuItem("Select all"), + new EditorMenuItem("Clone"), + new EditorMenuItemSpacer(), + new EditorMenuItem("Reverse selection"), + new EditorMenuItem("Flip horizontally"), + new EditorMenuItem("Flip vertically"), + new EditorMenuItem("Rotate 90deg clockwise"), + new EditorMenuItem("Rotate 90deg anticlockwise"), + new EditorMenuItem("Rotate by..."), + new EditorMenuItem("Scale by..."), + new EditorMenuItemSpacer(), + new EditorMenuItem("Reset selected objects' samples"), + new EditorMenuItem("Reset all samples", MenuItemType.Destructive), + new EditorMenuItem("Reset combo colours", MenuItemType.Destructive), + new EditorMenuItem("Reset breaks", MenuItemType.Destructive), + new EditorMenuItemSpacer(), + new EditorMenuItem("Nudge backward"), + new EditorMenuItem("Nudge forward") + } + }, + new EditorMenuBarItem("View") + { + Items = new[] + { + new EditorMenuItem("Compose"), + new EditorMenuItem("Design"), + new EditorMenuItem("Timing"), + new EditorMenuItemSpacer(), + new EditorMenuItem("Song setup..."), + new EditorMenuItem("Timing setup..."), + new EditorMenuItemSpacer(), + new EditorMenuItem("Volume"), + new EditorMenuItem("Grid level"), + new EditorMenuItem("Show video"), + new EditorMenuItem("Show sample name"), + new EditorMenuItem("Snaking sliders"), + new EditorMenuItem("Hit animations"), + new EditorMenuItem("Follow points"), + new EditorMenuItem("Stacking") + } + }, + new EditorMenuBarItem("Compose") + { + Items = new[] + { + new EditorMenuItem("Snap divisor"), + new EditorMenuItem("Audio rate"), + new EditorMenuItem("Grid snapping"), + new EditorMenuItemSpacer(), + new EditorMenuItem("Create polygon cricles..."), + new EditorMenuItem("Convert slider to stream"), + new EditorMenuItem("Enable live mapping mode"), + new EditorMenuItem("Sample import") + } + }, + new EditorMenuBarItem("Design") + { + Items = new[] + { + new EditorMenuItem("Move all elements in time...") + } + }, + new EditorMenuBarItem("Timing") + { + Items = new[] + { + new EditorMenuItem("Time signature"), + new EditorMenuItem("Metronome clicks"), + new EditorMenuItemSpacer(), + new EditorMenuItem("Add timing section"), + new EditorMenuItem("Add inheriting section"), + new EditorMenuItem("Reset current section"), + new EditorMenuItem("Delete timing section"), + new EditorMenuItem("Resnap current section"), + new EditorMenuItemSpacer(), + new EditorMenuItem("Timing setup..."), + new EditorMenuItemSpacer(), + new EditorMenuItem("Resnap all notes", MenuItemType.Destructive), + new EditorMenuItem("Move all notes in time...", MenuItemType.Destructive), + new EditorMenuItem("Recalculate slider lengths", MenuItemType.Destructive), + new EditorMenuItem("Delete all timing sections", MenuItemType.Destructive), + new EditorMenuItemSpacer(), + new EditorMenuItem("Set current position as preview point") + } + }, + new EditorMenuBarItem("Web") + { + Items = new[] + { + new EditorMenuItem("This Beatmap's information page"), + new EditorMenuItem("This Beatmap's thread"), + new EditorMenuItem("Quick reply") + } + }, + new EditorMenuBarItem("Help") + { + Items = new[] + { + new EditorMenuItem("Show in-game help"), + new EditorMenuItem("View FAQ") + } + } + } + } + } + }); + } + protected override void OnResuming(Screen last) { Beatmap.Value.Track?.Stop(); diff --git a/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs new file mode 100644 index 0000000000..bb349b1531 --- /dev/null +++ b/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs @@ -0,0 +1,122 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Screens.Edit.Menus +{ + public class EditorMenuBar : OsuMenu + { + public EditorMenuBar() + : base(Direction.Horizontal, true) + { + ItemsContainer.Padding = new MarginPadding(0); + BackgroundColour = Color4.Transparent; + } + + protected override Framework.Graphics.UserInterface.Menu CreateSubMenu() => new SubMenu(); + + protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableEditorBarMenuItem(item); + + private class DrawableEditorBarMenuItem : DrawableOsuMenuItem + { + private Color4 openedForegroundColour; + private Color4 openedBackgroundColour; + + public DrawableEditorBarMenuItem(MenuItem item) + : base(item) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + ForegroundColour = ForegroundColourHover = colours.BlueLight; + BackgroundColour = BackgroundColourHover = Color4.Transparent; + openedForegroundColour = Color4.White; + openedBackgroundColour = colours.Gray3; + } + + protected override void UpdateBackgroundColour() + { + if (State == MenuItemState.Selected) + Background.FadeColour(openedBackgroundColour); + else + base.UpdateBackgroundColour(); + } + + protected override void UpdateForegroundColour() + { + if (State == MenuItemState.Selected) + Foreground.FadeColour(openedForegroundColour); + else + base.UpdateForegroundColour(); + } + + protected override Drawable CreateBackground() => new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Height = 2, + Masking = true, + CornerRadius = 4, + Child = new Box { RelativeSizeAxes = Axes.Both } + } + }; + } + + private class SubMenu : OsuMenu + { + public SubMenu() + : base(Direction.Vertical) + { + OriginPosition = new Vector2(5, 1); + ItemsContainer.Padding = new MarginPadding { Top = 5, Bottom = 5 }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BackgroundColour = colours.Gray3; + } + + protected override Framework.Graphics.UserInterface.Menu CreateSubMenu() => new SubMenu(); + + protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableSubMenuItem(item); + + private class DrawableSubMenuItem : DrawableOsuMenuItem + { + public DrawableSubMenuItem(MenuItem item) + : base(item) + { + } + + protected override bool OnHover(InputState state) + { + if (Item is EditorMenuItemSpacer) + return true; + return base.OnHover(state); + } + + protected override bool OnClick(InputState state) + { + if (Item is EditorMenuItemSpacer) + return true; + return base.OnClick(state); + } + } + } + } +} diff --git a/osu.Game/Screens/Edit/Menus/EditorMenuBarItem.cs b/osu.Game/Screens/Edit/Menus/EditorMenuBarItem.cs new file mode 100644 index 0000000000..201bc6e5c3 --- /dev/null +++ b/osu.Game/Screens/Edit/Menus/EditorMenuBarItem.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics.UserInterface; + +namespace osu.Game.Screens.Edit.Menus +{ + public class EditorMenuBarItem : MenuItem + { + public EditorMenuBarItem(string text) + : base(text) + { + } + } +} diff --git a/osu.Game/Screens/Edit/Menus/EditorMenuItem.cs b/osu.Game/Screens/Edit/Menus/EditorMenuItem.cs new file mode 100644 index 0000000000..c7e36522cf --- /dev/null +++ b/osu.Game/Screens/Edit/Menus/EditorMenuItem.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Edit.Menus +{ + public class EditorMenuItem : OsuMenuItem + { + private const int min_text_length = 40; + + public EditorMenuItem(string text, MenuItemType type = MenuItemType.Standard) + : base(text.PadRight(min_text_length), type) + { + } + + public EditorMenuItem(string text, MenuItemType type, Action action) + : base(text.PadRight(min_text_length), type, action) + { + } + } +} diff --git a/osu.Game/Screens/Edit/Menus/EditorMenuItemSpacer.cs b/osu.Game/Screens/Edit/Menus/EditorMenuItemSpacer.cs new file mode 100644 index 0000000000..5060165ef7 --- /dev/null +++ b/osu.Game/Screens/Edit/Menus/EditorMenuItemSpacer.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Screens.Edit.Menus +{ + public class EditorMenuItemSpacer : EditorMenuItem + { + public EditorMenuItemSpacer() + : base(" ") + { + } + } +} diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs index 0898c079ce..aca169c3dc 100644 --- a/osu.Game/Screens/Menu/Button.cs +++ b/osu.Game/Screens/Menu/Button.cs @@ -28,6 +28,8 @@ namespace osu.Game.Screens.Menu /// public class Button : BeatSyncedContainer, IStateful { + public event Action StateChanged; + private readonly Container iconText; private readonly Container box; private readonly Box boxHoverLayer; @@ -266,6 +268,8 @@ namespace osu.Game.Screens.Menu this.FadeOut(explode_duration / 4f * 3); break; } + + StateChanged?.Invoke(State); } } } diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 71f2a16c09..e4dbe00a80 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -22,6 +22,8 @@ namespace osu.Game.Screens.Menu { public class ButtonSystem : Container, IStateful { + public event Action StateChanged; + public Action OnEdit; public Action OnExit; public Action OnDirect; @@ -294,6 +296,8 @@ namespace osu.Game.Screens.Menu backButton.State = state == MenuState.Play ? ButtonState.Expanded : ButtonState.Contracted; settingsButton.State = state == MenuState.TopLevel ? ButtonState.Expanded : ButtonState.Contracted; } + + StateChanged?.Invoke(State); } } diff --git a/osu.Game/Screens/Play/KeyCounterCollection.cs b/osu.Game/Screens/Play/KeyCounterCollection.cs index 0f6c5984c4..d21be71785 100644 --- a/osu.Game/Screens/Play/KeyCounterCollection.cs +++ b/osu.Game/Screens/Play/KeyCounterCollection.cs @@ -28,6 +28,8 @@ namespace osu.Game.Screens.Play public override void Add(KeyCounter key) { + if (key == null) throw new ArgumentNullException(nameof(key)); + base.Add(key); key.IsCounting = IsCounting; key.FadeTime = FadeTime; diff --git a/osu.Game/Screens/Play/SkipButton.cs b/osu.Game/Screens/Play/SkipButton.cs index 3cf371f1e1..6519a8db36 100644 --- a/osu.Game/Screens/Play/SkipButton.cs +++ b/osu.Game/Screens/Play/SkipButton.cs @@ -133,6 +133,8 @@ namespace osu.Game.Screens.Play private class FadeContainer : Container, IStateful { + public event Action StateChanged; + private Visibility state; private ScheduledDelegate scheduledHide; @@ -144,8 +146,10 @@ namespace osu.Game.Screens.Play } set { - var lastState = state; + if (state == value) + return; + var lastState = state; state = value; scheduledHide?.Cancel(); @@ -164,6 +168,8 @@ namespace osu.Game.Screens.Play this.FadeOut(1000, Easing.OutExpo); break; } + + StateChanged?.Invoke(State); } } diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs index f3bb523611..81dbf3eca4 100644 --- a/osu.Game/Screens/Play/SquareGraph.cs +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Collections.Generic; using System.Linq; using osu.Framework; @@ -170,6 +171,8 @@ namespace osu.Game.Screens.Play private const float padding = 2; public const float WIDTH = cube_size + padding; + public event Action StateChanged; + private readonly List drawableRows = new List(); private float filled; @@ -186,6 +189,7 @@ namespace osu.Game.Screens.Play } private ColumnState state; + public ColumnState State { get { return state; } @@ -196,6 +200,8 @@ namespace osu.Game.Screens.Play if (IsLoaded) fillActive(); + + StateChanged?.Invoke(State); } } diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs index 47d25585ad..a676516300 100644 --- a/osu.Game/Screens/Select/BeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; @@ -10,6 +11,8 @@ namespace osu.Game.Screens.Select { public class BeatmapDetailArea : Container { + private const float details_padding = 10; + private readonly Container content; protected override Container Content => content; @@ -66,9 +69,8 @@ namespace osu.Game.Screens.Select Details = new BeatmapDetails { RelativeSizeAxes = Axes.X, - Masking = true, - Height = 352, Alpha = 0, + Margin = new MarginPadding { Top = details_padding }, }, Leaderboard = new Leaderboard { @@ -76,5 +78,12 @@ namespace osu.Game.Screens.Select } }); } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + Details.Height = Math.Min(DrawHeight - details_padding * 3 - BeatmapDetailAreaTabControl.HEIGHT, 450); + } } } diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index a5486efa96..a98362e89c 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -9,71 +9,189 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using System.Globalization; using System.Linq; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Framework.Threading; using osu.Framework.Graphics.Shapes; +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Screens.Select.Details; using osu.Game.Beatmaps; namespace osu.Game.Screens.Select { public class BeatmapDetails : Container { - private readonly MetadataSegment description; - private readonly MetadataSegment source; - private readonly MetadataSegment tags; + private const float spacing = 10; + private const float transition_duration = 250; - private readonly DifficultyRow circleSize; - private readonly DifficultyRow drainRate; - private readonly DifficultyRow overallDifficulty; - private readonly DifficultyRow approachRate; - private readonly DifficultyRow stars; + private readonly FillFlowContainer top, statsFlow; + private readonly AdvancedStats advanced; + private readonly DetailBox ratingsContainer; + private readonly UserRatings ratings; + private readonly ScrollContainer metadataScroll; + private readonly MetadataSection description, source, tags; + private readonly Container failRetryContainer; + private readonly FailRetryGraph failRetryGraph; + private readonly DimmedLoadingAnimation loading; - private readonly Container ratingsContainer; - private readonly Bar ratingsBar; - private readonly OsuSpriteText negativeRatings; - private readonly OsuSpriteText positiveRatings; - private readonly BarGraph ratingsGraph; - - private readonly FillFlowContainer retryFailContainer; - private readonly BarGraph retryGraph; - private readonly BarGraph failGraph; + private APIAccess api; private ScheduledDelegate pendingBeatmapSwitch; - private BeatmapInfo beatmap; + private BeatmapInfo beatmap; public BeatmapInfo Beatmap { get { return beatmap; } - set { - if (beatmap == value) return; - + if (value == beatmap) return; beatmap = value; pendingBeatmapSwitch?.Cancel(); - pendingBeatmapSwitch = Schedule(updateStats); + pendingBeatmapSwitch = Schedule(updateStatistics); } } - private void updateStats() + public BeatmapDetails() { - if (beatmap == null) return; + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.5f), + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = spacing }, + Children = new Drawable[] + { + top = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + statsFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.5f, + Spacing = new Vector2(spacing), + Padding = new MarginPadding { Right = spacing / 2 }, + Children = new[] + { + new DetailBox + { + Child = advanced = new AdvancedStats + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = spacing, Top = spacing * 2, Bottom = spacing }, + }, + }, + ratingsContainer = new DetailBox + { + Child = ratings = new UserRatings + { + RelativeSizeAxes = Axes.X, + Height = 134, + Padding = new MarginPadding { Horizontal = spacing, Top = spacing }, + }, + }, + }, + }, + metadataScroll = new ScrollContainer + { + RelativeSizeAxes = Axes.X, + Width = 0.5f, + ScrollbarVisible = false, + Padding = new MarginPadding { Left = spacing / 2 }, + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + LayoutDuration = transition_duration, + Spacing = new Vector2(spacing * 2), + Margin = new MarginPadding { Top = spacing * 2 }, + Children = new[] + { + description = new MetadataSection("Description") + { + TextColour = Color4.White.Opacity(0.75f), + }, + source = new MetadataSection("Source") + { + TextColour = Color4.White.Opacity(0.75f), + }, + tags = new MetadataSection("Tags"), + }, + }, + }, + }, + }, + failRetryContainer = new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = "Points of Failure", + Font = @"Exo2.0-Bold", + TextSize = 14, + }, + failRetryGraph = new FailRetryGraph + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 14 + spacing / 2 }, + }, + }, + }, + }, + }, + loading = new DimmedLoadingAnimation + { + RelativeSizeAxes = Axes.Both, + }, + }; + } - description.Text = beatmap.Version; - source.Text = beatmap.Metadata.Source; - tags.Text = beatmap.Metadata.Tags; + [BackgroundDependencyLoader] + private void load(OsuColour colours, APIAccess api) + { + this.api = api; + tags.TextColour = colours.Yellow; + } - circleSize.Value = beatmap.Difficulty.CircleSize; - drainRate.Value = beatmap.Difficulty.DrainRate; - overallDifficulty.Value = beatmap.Difficulty.OverallDifficulty; - approachRate.Value = beatmap.Difficulty.ApproachRate; - stars.Value = (float)beatmap.StarDifficulty; + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); - var requestedBeatmap = beatmap; + metadataScroll.Height = statsFlow.DrawHeight; + failRetryContainer.Height = DrawHeight - Padding.TotalVertical - (top.DrawHeight + spacing / 2); + } + + private void updateStatistics() + { + if (Beatmap == null) + { + clearStats(); + return; + } + + ratingsContainer.FadeIn(transition_duration); + advanced.Beatmap = Beatmap; + description.Text = Beatmap.Version; + source.Text = Beatmap.Metadata.Source; + tags.Text = Beatmap.Metadata.Tags; + + var requestedBeatmap = Beatmap; if (requestedBeatmap.Metrics == null) { var lookup = new GetBeatmapDetailsRequest(requestedBeatmap); @@ -84,413 +202,195 @@ namespace osu.Game.Screens.Select return; requestedBeatmap.Metrics = res; - Schedule(() => updateMetrics(res)); + Schedule(() => displayMetrics(res)); }; - lookup.Failure += e => Schedule(() => updateMetrics(null)); + lookup.Failure += e => Schedule(() => displayMetrics(null)); api.Queue(lookup); loading.Show(); } - updateMetrics(requestedBeatmap.Metrics, false); + displayMetrics(requestedBeatmap.Metrics, false); } - /// - /// Update displayed metrics. - /// - /// New metrics to overwrite the existing display. Can be null. - /// Whether to hide the display on null or empty metrics. If false, we will dim as if waiting for further updates. - private void updateMetrics(BeatmapMetrics metrics, bool failOnMissing = true) + private void displayMetrics(BeatmapMetrics metrics, bool failOnMissing = true) { - var hasRatings = metrics?.Ratings.Any() ?? false; - var hasRetriesFails = (metrics?.Retries.Any() ?? false) && metrics.Fails.Any(); + var hasRatings = metrics?.Ratings?.Any() ?? false; + var hasRetriesFails = (metrics?.Retries?.Any() ?? false) && (metrics.Fails?.Any() ?? false); - if (failOnMissing) - loading.Hide(); + if (failOnMissing) loading.Hide(); if (hasRatings) { - var ratings = metrics.Ratings.ToList(); - ratingsContainer.Show(); - - negativeRatings.Text = ratings.GetRange(0, ratings.Count / 2).Sum().ToString(); - positiveRatings.Text = ratings.GetRange(ratings.Count / 2, ratings.Count / 2).Sum().ToString(); - ratingsBar.Length = (float)ratings.GetRange(0, ratings.Count / 2).Sum() / ratings.Sum(); - - ratingsGraph.Values = ratings.Select(rating => (float)rating); - - ratingsContainer.FadeColour(Color4.White, 500, Easing.Out); + ratings.Metrics = metrics; + ratings.FadeIn(transition_duration); } else if (failOnMissing) - ratingsGraph.Values = new float[10]; + { + ratings.Metrics = new BeatmapMetrics + { + Ratings = new int[10], + }; + } else - ratingsContainer.FadeColour(Color4.Gray, 500, Easing.Out); + { + ratings.FadeTo(0.25f, transition_duration); + } if (hasRetriesFails) { - var retries = metrics.Retries; - var fails = metrics.Fails; - retryFailContainer.Show(); - - float maxValue = fails.Zip(retries, (fail, retry) => fail + retry).Max(); - failGraph.MaxValue = maxValue; - retryGraph.MaxValue = maxValue; - - failGraph.Values = fails.Select(fail => (float)fail); - retryGraph.Values = retries.Zip(fails, (retry, fail) => retry + MathHelper.Clamp(fail, 0, maxValue)); - - retryFailContainer.FadeColour(Color4.White, 500, Easing.Out); + failRetryGraph.Metrics = metrics; + failRetryContainer.FadeIn(transition_duration); } else if (failOnMissing) { - failGraph.Values = new float[100]; - retryGraph.Values = new float[100]; + failRetryGraph.Metrics = new BeatmapMetrics + { + Fails = new int[100], + Retries = new int[100], + }; } else - retryFailContainer.FadeColour(Color4.Gray, 500, Easing.Out); + { + failRetryContainer.FadeTo(0.25f, transition_duration); + } } - public BeatmapDetails() + private void clearStats() { - Children = new Drawable[] + description.Text = null; + source.Text = null; + tags.Text = null; + advanced.Beatmap = new BeatmapInfo { - new Box + StarDifficulty = 0, + Difficulty = new BeatmapDifficulty { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.5f, + CircleSize = 0, + DrainRate = 0, + OverallDifficulty = 0, + ApproachRate = 0, }, - new FillFlowContainer - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Width = 0.4f, - Direction = FillDirection.Vertical, - LayoutDuration = 200, - LayoutEasing = Easing.OutQuint, - Children = new[] - { - description = new MetadataSegment("Description"), - source = new MetadataSegment("Source"), - tags = new MetadataSegment("Tags") - }, - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Width = 0.6f, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 15), - Padding = new MarginPadding(10) { Top = 0 }, - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.5f, - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), - Padding = new MarginPadding(10), - Children = new[] - { - circleSize = new DifficultyRow("Circle Size", 7), - drainRate = new DifficultyRow("HP Drain"), - overallDifficulty = new DifficultyRow("Accuracy"), - approachRate = new DifficultyRow("Approach Rate"), - stars = new DifficultyRow("Star Difficulty"), - }, - }, - }, - }, - ratingsContainer = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Alpha = 0, - AlwaysPresent = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.5f, - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Padding = new MarginPadding - { - Top = 25, - Left = 15, - Right = 15, - }, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = "User Rating", - Font = @"Exo2.0-Medium", - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - }, - ratingsBar = new Bar - { - RelativeSizeAxes = Axes.X, - Height = 5, - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new[] - { - negativeRatings = new OsuSpriteText - { - Font = @"Exo2.0-Regular", - Text = "0", - }, - positiveRatings = new OsuSpriteText - { - Font = @"Exo2.0-Regular", - Text = "0", - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - }, - }, - }, - new OsuSpriteText - { - Text = "Rating Spread", - TextSize = 14, - Font = @"Exo2.0-Regular", - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - }, - ratingsGraph = new BarGraph - { - RelativeSizeAxes = Axes.X, - Height = 50, - }, - }, - }, - }, - }, - retryFailContainer = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Alpha = 0, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = "Points of Failure", - Font = @"Exo2.0-Regular", - }, - new Container - { - RelativeSizeAxes = Axes.X, - Size = new Vector2(1 / 0.6f, 50), - Children = new[] - { - retryGraph = new BarGraph - { - RelativeSizeAxes = Axes.Both, - }, - failGraph = new BarGraph - { - RelativeSizeAxes = Axes.Both, - }, - }, - }, - } - }, - }, - }, - loading = new LoadingAnimation() }; + + loading.Hide(); + ratingsContainer.FadeOut(transition_duration); + failRetryContainer.FadeOut(transition_duration); } - private APIAccess api; - private readonly LoadingAnimation loading; - - [BackgroundDependencyLoader] - private void load(OsuColour colour, APIAccess api) + private class DetailBox : Container { - this.api = api; + private readonly Container content; + protected override Container Content => content; - description.AccentColour = colour.GrayB; - source.AccentColour = colour.GrayB; - tags.AccentColour = colour.YellowLight; - - stars.AccentColour = colour.Yellow; - - ratingsBar.BackgroundColour = colour.Green; - ratingsBar.AccentColour = colour.YellowDark; - ratingsGraph.Colour = colour.BlueDark; - - failGraph.Colour = colour.YellowDarker; - retryGraph.Colour = colour.Yellow; - } - - private class DifficultyRow : Container, IHasAccentColour - { - private readonly OsuSpriteText name; - private readonly Bar bar; - private readonly OsuSpriteText valueText; - - private readonly float maxValue; - - private float difficultyValue; - public float Value + public DetailBox() { - get - { - return difficultyValue; - } - set - { - difficultyValue = value; - bar.Length = value / maxValue; - valueText.Text = value.ToString("N1", CultureInfo.CurrentCulture); - } - } - - public Color4 AccentColour - { - get - { - return bar.AccentColour; - } - set - { - bar.AccentColour = value; - } - } - - public DifficultyRow(string difficultyName, float maxValue = 10) - { - this.maxValue = maxValue; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - Children = new Drawable[] + + InternalChildren = new Drawable[] { - name = new OsuSpriteText + new Box { - Font = @"Exo2.0-Regular", - Text = difficultyName, - }, - bar = new Bar - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, RelativeSizeAxes = Axes.Both, - Size = new Vector2(1, 0.35f), - Padding = new MarginPadding { Left = 100, Right = 25 }, + Colour = Color4.Black.Opacity(0.5f), }, - valueText = new OsuSpriteText + content = new Container { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Font = @"Exo2.0-Regular", + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, }, }; } - - [BackgroundDependencyLoader] - private void load(OsuColour colour) - { - name.Colour = colour.GrayB; - bar.BackgroundColour = colour.Gray7; - valueText.Colour = colour.GrayB; - } } - private class MetadataSegment : Container, IHasAccentColour + private class MetadataSection : Container { - private readonly OsuSpriteText header; - private readonly FillFlowContainer content; + private readonly TextFlowContainer textFlow; public string Text { set { if (string.IsNullOrEmpty(value)) - Hide(); - else { - Show(); - if (header.Text == "Tags") - content.ChildrenEnumerable = value.Split(' ').Select(text => new OsuSpriteText - { - Text = text, - Font = "Exo2.0-Regular", - }); - else - content.Children = new[] - { - new OsuSpriteText - { - Text = value, - Font = "Exo2.0-Regular", - } - }; + this.FadeOut(transition_duration); + return; } + + this.FadeIn(transition_duration); + textFlow.Clear(); + textFlow.AddText(value, s => s.TextSize = 14); } } - public Color4 AccentColour + public Color4 TextColour { - get - { - return content.Colour; - } - set - { - content.Colour = value; - } + get { return textFlow.Colour; } + set { textFlow.Colour = value; } } - public MetadataSegment(string headerText) + public MetadataSection(string title) { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - Margin = new MarginPadding { Top = 10 }; + + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(spacing / 2), + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = new OsuSpriteText + { + Text = title, + Font = @"Exo2.0-Bold", + TextSize = 14, + }, + }, + textFlow = new TextFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + }, + }; + } + } + + private class DimmedLoadingAnimation : VisibilityContainer + { + private readonly LoadingAnimation loading; + + public DimmedLoadingAnimation() + { Children = new Drawable[] { - header = new OsuSpriteText + new Box { - Font = @"Exo2.0-Bold", - Text = headerText, + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.5f), }, - content = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Full, - Spacing = new Vector2(5, 0), - Margin = new MarginPadding { Top = header.TextSize } - } + loading = new LoadingAnimation(), }; } + + protected override void PopIn() + { + this.FadeIn(transition_duration, Easing.OutQuint); + loading.State = Visibility.Visible; + } + + protected override void PopOut() + { + this.FadeOut(transition_duration, Easing.OutQuint); + loading.State = Visibility.Hidden; + } } } } diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs new file mode 100644 index 0000000000..d92c8ed509 --- /dev/null +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -0,0 +1,152 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using System; +using osu.Game.Beatmaps; + +namespace osu.Game.Screens.Select.Details +{ + public class AdvancedStats : Container + { + private readonly StatisticRow firstValue, hpDrain, accuracy, approachRate, starDifficulty; + + private BeatmapInfo beatmap; + public BeatmapInfo Beatmap + { + get { return beatmap; } + set + { + if (value == beatmap) return; + beatmap = value; + + //mania specific + if ((Beatmap?.Ruleset?.ID ?? 0) == 3) + { + firstValue.Title = "Key Amount"; + firstValue.Value = (int)Math.Round(Beatmap?.Difficulty?.CircleSize ?? 0); + } + else + { + firstValue.Title = "Circle Size"; + firstValue.Value = Beatmap?.Difficulty?.CircleSize ?? 0; + } + + hpDrain.Value = beatmap.Difficulty.DrainRate; + accuracy.Value = beatmap.Difficulty.OverallDifficulty; + approachRate.Value = beatmap.Difficulty.ApproachRate; + starDifficulty.Value = (float)beatmap.StarDifficulty; + } + } + + public AdvancedStats() + { + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(4f), + Children = new[] + { + firstValue = new StatisticRow(), //circle size/key amount + hpDrain = new StatisticRow { Title = "HP Drain" }, + accuracy = new StatisticRow { Title = "Accuracy" }, + approachRate = new StatisticRow { Title = "Approach Rate" }, + starDifficulty = new StatisticRow(10, true) { Title = "Star Difficulty" }, + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + starDifficulty.AccentColour = colours.Yellow; + } + + private class StatisticRow : Container, IHasAccentColour + { + private const float value_width = 25; + private const float name_width = 70; + + private readonly float maxValue; + private readonly bool forceDecimalPlaces; + private readonly OsuSpriteText name, value; + private readonly Bar bar; + + public string Title + { + get { return name.Text; } + set { name.Text = value; } + } + + private float difficultyValue; + public float Value + { + get { return difficultyValue; } + set + { + difficultyValue = value; + bar.Length = value / maxValue; + this.value.Text = value.ToString(forceDecimalPlaces ? "0.00" : "0.##"); + } + } + + public Color4 AccentColour + { + get { return bar.AccentColour; } + set { bar.AccentColour = value; } + } + + public StatisticRow(float maxValue = 10, bool forceDecimalPlaces = false) + { + this.maxValue = maxValue; + this.forceDecimalPlaces = forceDecimalPlaces; + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Children = new Drawable[] + { + new Container + { + Width = name_width, + AutoSizeAxes = Axes.Y, + Child = name = new OsuSpriteText + { + TextSize = 13, + }, + }, + bar = new Bar + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = 5, + BackgroundColour = Color4.White.Opacity(0.5f), + Padding = new MarginPadding { Left = name_width + 10, Right = value_width + 10 }, + }, + new Container + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Width = value_width, + RelativeSizeAxes = Axes.Y, + Child = value = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + TextSize = 13, + }, + }, + }; + } + } + } +} diff --git a/osu.Game/Screens/Select/Details/FailRetryGraph.cs b/osu.Game/Screens/Select/Details/FailRetryGraph.cs new file mode 100644 index 0000000000..4d75c49d17 --- /dev/null +++ b/osu.Game/Screens/Select/Details/FailRetryGraph.cs @@ -0,0 +1,62 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using System.Linq; +using osu.Game.Beatmaps; + +namespace osu.Game.Screens.Select.Details +{ + public class FailRetryGraph : Container + { + private readonly BarGraph retryGraph, failGraph; + + private BeatmapMetrics metrics; + public BeatmapMetrics Metrics + { + get { return metrics; } + set + { + if (value == metrics) return; + metrics = value; + + var retries = Metrics.Retries; + var fails = Metrics.Fails; + + float maxValue = fails.Zip(retries, (fail, retry) => fail + retry).Max(); + failGraph.MaxValue = maxValue; + retryGraph.MaxValue = maxValue; + + failGraph.Values = fails.Select(f => (float)f); + retryGraph.Values = retries.Zip(fails, (retry, fail) => retry + MathHelper.Clamp(fail, 0, maxValue)); + } + } + + public FailRetryGraph() + { + Children = new[] + { + retryGraph = new BarGraph + { + RelativeSizeAxes = Axes.Both, + }, + failGraph = new BarGraph + { + RelativeSizeAxes = Axes.Both, + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + retryGraph.Colour = colours.Yellow; + failGraph.Colour = colours.YellowDarker; + } + } +} diff --git a/osu.Game/Screens/Select/Details/UserRatings.cs b/osu.Game/Screens/Select/Details/UserRatings.cs new file mode 100644 index 0000000000..db6d932464 --- /dev/null +++ b/osu.Game/Screens/Select/Details/UserRatings.cs @@ -0,0 +1,122 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using System.Linq; +using osu.Game.Beatmaps; + +namespace osu.Game.Screens.Select.Details +{ + public class UserRatings : Container + { + private readonly FillFlowContainer header; + private readonly Bar ratingsBar; + private readonly OsuSpriteText negativeRatings, positiveRatings; + private readonly Container graphContainer; + private readonly BarGraph graph; + + private BeatmapMetrics metrics; + public BeatmapMetrics Metrics + { + get { return metrics; } + set + { + if (value == metrics) return; + metrics = value; + + var ratings = Metrics.Ratings.ToList(); + negativeRatings.Text = ratings.GetRange(0, ratings.Count / 2).Sum().ToString(); + positiveRatings.Text = ratings.GetRange(ratings.Count / 2, ratings.Count / 2).Sum().ToString(); + ratingsBar.Length = (float)ratings.GetRange(0, ratings.Count / 2).Sum() / ratings.Sum(); + graph.Values = Metrics.Ratings.Select(r => (float)r); + } + } + + public UserRatings() + { + Children = new Drawable[] + { + header = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = "User Rating", + TextSize = 13, + }, + ratingsBar = new Bar + { + RelativeSizeAxes = Axes.X, + Height = 5, + Margin = new MarginPadding { Top = 5 }, + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new[] + { + negativeRatings = new OsuSpriteText + { + Text = "0", + TextSize = 13, + }, + positiveRatings = new OsuSpriteText + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Text = @"0", + TextSize = 13, + }, + }, + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = "Rating Spread", + TextSize = 13, + Margin = new MarginPadding { Top = 10, Bottom = 5 }, + }, + }, + }, + graphContainer = new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Both, + Child = graph = new BarGraph + { + RelativeSizeAxes = Axes.Both, + }, + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + ratingsBar.BackgroundColour = colours.Green; + ratingsBar.AccentColour = colours.Yellow; + graph.Colour = colours.BlueDark; + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + graphContainer.Padding = new MarginPadding { Top = header.DrawHeight }; + } + } +} diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 5a375e55d4..0506784614 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -1,15 +1,15 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Collections.Generic; using OpenTK; using OpenTK.Graphics; +using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; -using System; -using osu.Framework.Allocation; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; diff --git a/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs b/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs index d7c85fff90..4f81b2871d 100644 --- a/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs @@ -1,19 +1,22 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using OpenTK; using OpenTK.Graphics; +using osu.Framework; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Framework.Extensions.Color4Extensions; -using osu.Game.Rulesets.Mods; -using osu.Game.Users; -using osu.Framework; -using osu.Game.Rulesets.Scoring; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Users; namespace osu.Game.Screens.Select.Leaderboards { @@ -21,6 +24,8 @@ namespace osu.Game.Screens.Select.Leaderboards { public static readonly float HEIGHT = 60; + public event Action StateChanged; + public readonly int RankPosition; public readonly Score Score; @@ -38,14 +43,17 @@ namespace osu.Game.Screens.Select.Leaderboards private readonly ScoreComponentLabel maxCombo; private readonly ScoreComponentLabel accuracy; private readonly Container flagBadgeContainer; - private readonly FillFlowContainer modsContainer; + private readonly FillFlowContainer modsContainer; private Visibility state; + public Visibility State { get { return state; } set { + if (state == value) + return; state = value; switch (state) @@ -88,6 +96,8 @@ namespace osu.Game.Screens.Select.Leaderboards break; } + + StateChanged?.Invoke(State); } } @@ -239,7 +249,7 @@ namespace osu.Game.Screens.Select.Leaderboards }, }, }, - modsContainer = new FillFlowContainer + modsContainer = new FillFlowContainer { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, @@ -252,13 +262,13 @@ namespace osu.Game.Screens.Select.Leaderboards }, }; - if (Score.Mods != null) + foreach (Mod mod in Score.Mods) { - foreach (Mod mod in Score.Mods) + modsContainer.Add(new ModIcon(mod) { - // TODO: Get actual mod colours - modsContainer.Add(new ScoreModIcon(mod.Icon, OsuColour.FromHex(@"ffcc22"))); - } + AutoSizeAxes = Axes.Both, + Scale = new Vector2(0.375f) + }); } } @@ -267,13 +277,13 @@ namespace osu.Game.Screens.Select.Leaderboards public override void Hide() => State = Visibility.Hidden; public override void Show() => State = Visibility.Visible; - protected override bool OnHover(Framework.Input.InputState state) + protected override bool OnHover(InputState state) { background.FadeTo(0.5f, 300, Easing.OutQuint); return base.OnHover(state); } - protected override void OnHoverLost(Framework.Input.InputState state) + protected override void OnHoverLost(InputState state) { background.FadeTo(background_alpha, 200, Easing.OutQuint); base.OnHoverLost(state); @@ -303,8 +313,8 @@ namespace osu.Game.Screens.Select.Leaderboards Anchor = Anchor.Centre, Origin = Anchor.Centre, Font = font, - TextSize = textSize, FixedWidth = true, + TextSize = textSize, Text = text, Colour = glowColour, Shadow = false, @@ -326,36 +336,6 @@ namespace osu.Game.Screens.Select.Leaderboards } } - private class ScoreModIcon : Container - { - public ScoreModIcon(FontAwesome icon, Color4 colour) - { - AutoSizeAxes = Axes.Both; - - Children = new[] - { - new SpriteIcon - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Icon = FontAwesome.fa_osu_mod_bg, - Colour = colour, - Shadow = true, - Size = new Vector2(30), - }, - new SpriteIcon - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Icon = icon, - Colour = OsuColour.Gray(84), - Size = new Vector2(18), - Position = new Vector2(0f, 2f), - }, - }; - } - } - private class ScoreComponentLabel : Container { public ScoreComponentLabel(FontAwesome icon, string value) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3c522ca0e8..e0eafe8422 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -9,7 +9,7 @@ Properties osu.Game osu.Game - v4.5 + v4.6.1 512 @@ -98,6 +98,7 @@ + @@ -133,6 +134,10 @@ + + + + @@ -533,6 +538,9 @@ + + +