diff --git a/.vscode/launch.json b/.vscode/launch.json index b981556649..c836ff97bc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,7 +11,7 @@ "preLaunchTask": "build", "runtimeExecutable": null, "env": {}, - "externalConsole": false + "console": "internalConsole" }, { "name": "Launch Desktop", @@ -23,7 +23,7 @@ "preLaunchTask": "build", "runtimeExecutable": null, "env": {}, - "externalConsole": false + "console": "internalConsole" }, { "name": "Attach", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 0c0e79f7fb..03f5bc4c6c 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -2,25 +2,23 @@ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "0.1.0", - "windows": { - "command": "msbuild" - }, - "linux": { - "command": "xbuild" - }, - "args": [ - // Ask msbuild to generate full paths for file names. - "/property:GenerateFullPaths=true" - ], "taskSelector": "/t:", - "showOutput": "silent", "tasks": [ { "taskName": "build", - // Show the output window only if unrecognized errors occur. + "isShellCommand": true, "showOutput": "silent", + "command": "xbuild", + "windows": { + "command": "msbuild" + }, + "args": [ + // Ask msbuild to generate full paths for file names. + "/property:GenerateFullPaths=true" + ], // Use the standard MS compiler pattern to detect errors, warnings and infos - "problemMatcher": "$msCompile" + "problemMatcher": "$msCompile", + "isBuildCommand": true } ] } \ No newline at end of file diff --git a/osu-framework b/osu-framework index a7c99e06ff..2234013e59 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit a7c99e06ff4c3f56fad24bec170eb93f42b1e149 +Subproject commit 2234013e59a99116ee9f9e56a95ff8a6667db2a7 diff --git a/osu.Desktop.Deploy/App.config b/osu.Desktop.Deploy/App.config index d1da144f50..45685a74a8 100644 --- a/osu.Desktop.Deploy/App.config +++ b/osu.Desktop.Deploy/App.config @@ -21,4 +21,16 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste + + + + + + + + + + + + \ No newline at end of file diff --git a/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj b/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj index 7a3719a25b..901117b026 100644 --- a/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj +++ b/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj @@ -68,9 +68,8 @@ $(SolutionDir)\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Rocks.dll True - - $(SolutionDir)\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll - True + + $(SolutionDir)\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll $(SolutionDir)\packages\squirrel.windows.1.5.2\lib\Net45\NuGet.Squirrel.dll @@ -120,7 +119,7 @@ - - - + + diff --git a/osu.Desktop.VisualTests/Tests/TestCaseBeatmapDetails.cs b/osu.Desktop.VisualTests/Tests/TestCaseBeatmapDetails.cs new file mode 100644 index 0000000000..4a59ad9534 --- /dev/null +++ b/osu.Desktop.VisualTests/Tests/TestCaseBeatmapDetails.cs @@ -0,0 +1,65 @@ +// 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.Framework.Graphics.Primitives; +using osu.Framework.Testing; +using osu.Game.Database; +using osu.Game.Screens.Select; +using System.Linq; + +namespace osu.Desktop.VisualTests.Tests +{ + internal class TestCaseBeatmapDetails : TestCase + { + public override string Description => "BeatmapDetails tab of BeatmapDetailArea"; + + private BeatmapDetails details; + + public override void Reset() + { + base.Reset(); + + Add(details = new BeatmapDetails + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(150), + Beatmap = new BeatmapInfo + { + 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), + }, + }, + }); + + AddRepeatStep("fail values", newRetryAndFailValues, 10); + } + + private int lastRange = 1; + + 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; + } + } +} \ No newline at end of file diff --git a/osu.Desktop.VisualTests/Tests/TestCaseGraph.cs b/osu.Desktop.VisualTests/Tests/TestCaseGraph.cs new file mode 100644 index 0000000000..7ac795f6f9 --- /dev/null +++ b/osu.Desktop.VisualTests/Tests/TestCaseGraph.cs @@ -0,0 +1,42 @@ +// 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.Framework.Testing; +using osu.Game.Graphics.UserInterface; +using System.Linq; + +namespace osu.Desktop.VisualTests.Tests +{ + internal class TestCaseGraph : TestCase + { + public override string Description => "graph"; + + private BarGraph graph; + + public override void Reset() + { + base.Reset(); + + Children = new[] + { + graph = new BarGraph + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(0.5f), + }, + }; + + AddStep("values from 1-10", () => graph.Values = Enumerable.Range(1,10).Select(i => (float)i)); + AddStep("values from 1-100", () => graph.Values = Enumerable.Range(1, 100).Select(i => (float)i)); + AddStep("reversed values from 1-10", () => graph.Values = Enumerable.Range(1, 10).Reverse().Select(i => (float)i)); + AddStep("Bottom to top", () => graph.Direction = BarDirection.BottomToTop); + AddStep("Top to bottom", () => graph.Direction = BarDirection.TopToBottom); + AddStep("Left to right", () => graph.Direction = BarDirection.LeftToRight); + AddStep("Right to left", () => graph.Direction = BarDirection.RightToLeft); + } + } +} \ No newline at end of file diff --git a/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs b/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs index 99da7d1c73..3d9f3aad79 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs @@ -99,6 +99,7 @@ namespace osu.Desktop.VisualTests.Tests AddToggleStep(@"auto", state => { auto = state; load(mode); }); + BasicSliderBar sliderBar; Add(new Container { Anchor = Anchor.TopRight, @@ -107,16 +108,17 @@ namespace osu.Desktop.VisualTests.Tests Children = new Drawable[] { new SpriteText { Text = "Playback Speed" }, - new BasicSliderBar + sliderBar = new BasicSliderBar { Width = 150, Height = 10, SelectionColor = Color4.Orange, - Value = playbackSpeed } } }); + sliderBar.Current.BindTo(playbackSpeed); + framedClock.ProcessFrame(); var clockAdjustContainer = new Container diff --git a/osu.Desktop.VisualTests/Tests/TestCaseKeyCounter.cs b/osu.Desktop.VisualTests/Tests/TestCaseKeyCounter.cs index 7e7782662b..b1b9ddbcda 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseKeyCounter.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseKeyCounter.cs @@ -44,6 +44,8 @@ namespace osu.Desktop.VisualTests.Tests kc.Add(new KeyCounterKeyboard(key)); }); + TestSliderBar sliderBar; + Add(new Container { Anchor = Anchor.TopRight, @@ -52,16 +54,17 @@ namespace osu.Desktop.VisualTests.Tests Children = new Drawable[] { new SpriteText { Text = "FadeTime" }, - new TestSliderBar + sliderBar =new TestSliderBar { Width = 150, Height = 10, SelectionColor = Color4.Orange, - Value = bindable } } }); + sliderBar.Current.BindTo(bindable); + Add(kc); } private class TestSliderBar : SliderBar where T : struct diff --git a/osu.Desktop.VisualTests/Tests/TestCasePlayer.cs b/osu.Desktop.VisualTests/Tests/TestCasePlayer.cs index f36889b02a..624723ed35 100644 --- a/osu.Desktop.VisualTests/Tests/TestCasePlayer.cs +++ b/osu.Desktop.VisualTests/Tests/TestCasePlayer.cs @@ -83,10 +83,7 @@ namespace osu.Desktop.VisualTests.Tests Colour = Color4.Black, }); - Add(new PlayerLoader(Player = CreatePlayer(beatmap)) - { - Beatmap = beatmap - }); + Add(Player = CreatePlayer(beatmap)); } protected virtual Player CreatePlayer(WorkingBeatmap beatmap) diff --git a/osu.Desktop.VisualTests/Tests/TestCaseTabControl.cs b/osu.Desktop.VisualTests/Tests/TestCaseTabControl.cs index 2d3969b822..b72abd1992 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseTabControl.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseTabControl.cs @@ -36,7 +36,7 @@ namespace osu.Desktop.VisualTests.Tests filter.PinItem(GroupMode.All); filter.PinItem(GroupMode.RecentlyPlayed); - filter.SelectedItem.ValueChanged += newFilter => + filter.Current.ValueChanged += newFilter => { text.Text = "Currently Selected: " + newFilter.ToString(); }; diff --git a/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs b/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs index 88a037afee..4e9ff4980e 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.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 OpenTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.MathUtils; @@ -11,20 +12,22 @@ using osu.Game.Modes.Taiko.Judgements; using osu.Game.Modes.Taiko.Objects; using osu.Game.Modes.Taiko.Objects.Drawables; using osu.Game.Modes.Taiko.UI; +using System; namespace osu.Desktop.VisualTests.Tests { internal class TestCaseTaikoPlayfield : TestCase { - public override string Description => "Taiko playfield"; + private const double default_duration = 300; + private const float scroll_time = 1000; - private TaikoPlayfield playfield; + public override string Description => "Taiko playfield"; protected override double TimePerAction => default_duration * 2; - private const double default_duration = 300; - - private const float scroll_time = 1000; + private readonly Random rng = new Random(1337); + private TaikoPlayfield playfield; + private Container playfieldContainer; public override void Reset() { @@ -41,15 +44,22 @@ namespace osu.Desktop.VisualTests.Tests AddStep("Strong Rim", () => addRimHit(true)); AddStep("Add bar line", () => addBarLine(false)); AddStep("Add major bar line", () => addBarLine(true)); - + AddStep("Height test 1", () => changePlayfieldSize(1)); + AddStep("Height test 2", () => changePlayfieldSize(2)); + AddStep("Height test 3", () => changePlayfieldSize(3)); + AddStep("Height test 4", () => changePlayfieldSize(4)); + AddStep("Height test 5", () => changePlayfieldSize(5)); + AddStep("Reset height", () => changePlayfieldSize(6)); var rateAdjustClock = new StopwatchClock(true) { Rate = 1 }; - Add(new Container + Add(playfieldContainer = new Container { - Clock = new FramedClock(rateAdjustClock), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, - Y = 200, + Height = TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT, + Clock = new FramedClock(rateAdjustClock), Children = new[] { playfield = new TaikoPlayfield() @@ -57,21 +67,63 @@ namespace osu.Desktop.VisualTests.Tests }); } + private void changePlayfieldSize(int step) + { + // Add new hits + switch (step) + { + case 1: + addCentreHit(false); + break; + case 2: + addCentreHit(true); + break; + case 3: + addDrumRoll(false); + break; + case 4: + addDrumRoll(true); + break; + case 5: + addSwell(1000); + playfieldContainer.Delay(scroll_time - 100); + break; + } + + // Tween playfield height + switch (step) + { + default: + playfieldContainer.ResizeTo(new Vector2(1, rng.Next(25, 400)), 500); + break; + case 6: + playfieldContainer.ResizeTo(new Vector2(1, TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT), 500); + break; + } + } + private void addHitJudgement() { TaikoHitResult hitResult = RNG.Next(2) == 0 ? TaikoHitResult.Good : TaikoHitResult.Great; - playfield.OnJudgement(new DrawableTestHit(new Hit()) + var h = new DrawableTestHit(new Hit()) { X = RNG.NextSingle(hitResult == TaikoHitResult.Good ? -0.1f : -0.05f, hitResult == TaikoHitResult.Good ? 0.1f : 0.05f), Judgement = new TaikoJudgement { Result = HitResult.Hit, TaikoResult = hitResult, - TimeOffset = 0, - SecondHit = RNG.Next(10) == 0 + TimeOffset = 0 } - }); + }; + + playfield.OnJudgement(h); + + if (RNG.Next(10) == 0) + { + h.Judgement.SecondHit = true; + playfield.OnJudgement(h); + } } private void addMissJudgement() diff --git a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj index 99abf3fa41..d75bb94308 100644 --- a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj +++ b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj @@ -83,22 +83,20 @@ - - $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1340\lib\net45\OpenTK.dll - True + + $(SolutionDir)\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll - - ..\packages\SharpCompress.0.15.1\lib\net45\SharpCompress.dll - True + + $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1341\lib\net45\OpenTK.dll + + + $(SolutionDir)\packages\SharpCompress.0.15.2\lib\net45\SharpCompress.dll False $(SolutionDir)\packages\SQLite.Net.Core-PCL.3.1.1\lib\portable-win8+net45+wp8+wpa81+MonoAndroid1+MonoTouch1\SQLite.Net.dll - - $(SolutionDir)\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll - $(SolutionDir)\packages\SQLiteNetExtensions.1.3.0\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1\SQLiteNetExtensions.dll @@ -187,8 +185,10 @@ + + diff --git a/osu.Desktop.VisualTests/packages.config b/osu.Desktop.VisualTests/packages.config index 5a30c50600..cad2ffff0d 100644 --- a/osu.Desktop.VisualTests/packages.config +++ b/osu.Desktop.VisualTests/packages.config @@ -4,9 +4,9 @@ Copyright (c) 2007-2017 ppy Pty Ltd . Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE --> - - - + + + diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs index 70925f6cf4..9532652bfe 100644 --- a/osu.Desktop/Overlays/VersionManager.cs +++ b/osu.Desktop/Overlays/VersionManager.cs @@ -189,19 +189,24 @@ namespace osu.Desktop.Overlays private class UpdateProgressNotification : ProgressNotification { + private OsuGame game; + protected override Notification CreateCompletionNotification() => new ProgressCompletionNotification() { Text = @"Update ready to install. Click to restart!", Activated = () => { - UpdateManager.RestartApp(); + UpdateManager.RestartAppWhenExited(); + game.GracefullyExit(); return true; } }; [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, OsuGame game) { + this.game = game; + IconContent.Add(new Drawable[] { new Box diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index fbc342d695..dbd26b4640 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -124,8 +124,7 @@ True - $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1340\lib\net45\OpenTK.dll - True + $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1341\lib\net45\OpenTK.dll $(SolutionDir)\packages\Splat.2.0.0\lib\Net45\Splat.dll diff --git a/osu.Desktop/packages.config b/osu.Desktop/packages.config index be9b65f0c6..60e8182c82 100644 --- a/osu.Desktop/packages.config +++ b/osu.Desktop/packages.config @@ -7,7 +7,7 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste - + \ No newline at end of file diff --git a/osu.Game.Modes.Catch/UI/CatchPlayfield.cs b/osu.Game.Modes.Catch/UI/CatchPlayfield.cs index cf1a665470..f8792a7fd5 100644 --- a/osu.Game.Modes.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Modes.Catch/UI/CatchPlayfield.cs @@ -14,8 +14,7 @@ namespace osu.Game.Modes.Catch.UI { public CatchPlayfield() { - RelativeSizeAxes = Axes.Y; - Size = new Vector2(512, 0.9f); + Size = new Vector2(1, 0.9f); Anchor = Anchor.BottomCentre; Origin = Anchor.BottomCentre; diff --git a/osu.Game.Modes.Catch/osu.Game.Modes.Catch.csproj b/osu.Game.Modes.Catch/osu.Game.Modes.Catch.csproj index 593d8db4f6..50b1a095af 100644 --- a/osu.Game.Modes.Catch/osu.Game.Modes.Catch.csproj +++ b/osu.Game.Modes.Catch/osu.Game.Modes.Catch.csproj @@ -33,8 +33,7 @@ - $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1340\lib\net45\OpenTK.dll - True + $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1341\lib\net45\OpenTK.dll @@ -84,7 +83,7 @@ - - + \ No newline at end of file diff --git a/osu.Game.Modes.Mania/UI/ManiaPlayfield.cs b/osu.Game.Modes.Mania/UI/ManiaPlayfield.cs index 670d18f71f..deb4ebac25 100644 --- a/osu.Game.Modes.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Modes.Mania/UI/ManiaPlayfield.cs @@ -15,8 +15,7 @@ namespace osu.Game.Modes.Mania.UI { public ManiaPlayfield(int columns) { - RelativeSizeAxes = Axes.Both; - Size = new Vector2(columns / 20f, 1f); + Size = new Vector2(0.8f, 1f); Anchor = Anchor.BottomCentre; Origin = Anchor.BottomCentre; diff --git a/osu.Game.Modes.Mania/osu.Game.Modes.Mania.csproj b/osu.Game.Modes.Mania/osu.Game.Modes.Mania.csproj index cc925d417a..896e9c68c6 100644 --- a/osu.Game.Modes.Mania/osu.Game.Modes.Mania.csproj +++ b/osu.Game.Modes.Mania/osu.Game.Modes.Mania.csproj @@ -33,8 +33,7 @@ - $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1340\lib\net45\OpenTK.dll - True + $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1341\lib\net45\OpenTK.dll @@ -89,7 +88,7 @@ - - + \ No newline at end of file diff --git a/osu.Game.Modes.Osu/OsuKeyConversionInputManager.cs b/osu.Game.Modes.Osu/OsuKeyConversionInputManager.cs index 986240b37f..567c7a35b1 100644 --- a/osu.Game.Modes.Osu/OsuKeyConversionInputManager.cs +++ b/osu.Game.Modes.Osu/OsuKeyConversionInputManager.cs @@ -42,14 +42,14 @@ namespace osu.Game.Modes.Osu { if (mouseDisabled.Value) { - mouse.PressedButtons.Remove(MouseButton.Left); - mouse.PressedButtons.Remove(MouseButton.Right); + mouse.SetPressed(MouseButton.Left, false); + mouse.SetPressed(MouseButton.Right, false); } if (leftViaKeyboard) - mouse.PressedButtons.Add(MouseButton.Left); + mouse.SetPressed(MouseButton.Left, true); if (rightViaKeyboard) - mouse.PressedButtons.Add(MouseButton.Right); + mouse.SetPressed(MouseButton.Right, true); } } } diff --git a/osu.Game.Modes.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Modes.Osu/Scoring/OsuScoreProcessor.cs index 0bd587e8ea..3b798a2fad 100644 --- a/osu.Game.Modes.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Modes.Osu/Scoring/OsuScoreProcessor.cs @@ -35,11 +35,9 @@ namespace osu.Game.Modes.Osu.Scoring switch (judgement.Result) { case HitResult.Hit: - Combo.Value++; Health.Value += 0.1f; break; case HitResult.Miss: - Combo.Value = 0; Health.Value -= 0.2f; break; } diff --git a/osu.Game.Modes.Osu/UI/OsuHitRenderer.cs b/osu.Game.Modes.Osu/UI/OsuHitRenderer.cs index ca9ff6fc61..7e314c5ba1 100644 --- a/osu.Game.Modes.Osu/UI/OsuHitRenderer.cs +++ b/osu.Game.Modes.Osu/UI/OsuHitRenderer.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 OpenTK; using osu.Game.Beatmaps; using osu.Game.Modes.Objects.Drawables; using osu.Game.Modes.Osu.Beatmaps; @@ -46,5 +47,7 @@ namespace osu.Game.Modes.Osu.UI return new DrawableSpinner(spinner); return null; } + + protected override Vector2 GetPlayfieldAspectAdjust() => new Vector2(0.75f); } } diff --git a/osu.Game.Modes.Osu/UI/OsuPlayfield.cs b/osu.Game.Modes.Osu/UI/OsuPlayfield.cs index d89bbfd131..4164607b4d 100644 --- a/osu.Game.Modes.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Modes.Osu/UI/OsuPlayfield.cs @@ -38,8 +38,6 @@ namespace osu.Game.Modes.Osu.UI { Anchor = Anchor.Centre; Origin = Anchor.Centre; - RelativeSizeAxes = Axes.Both; - Size = new Vector2(0.75f); Add(new Drawable[] { diff --git a/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj b/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj index 55322e855e..21f0f03d8c 100644 --- a/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj +++ b/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj @@ -34,8 +34,7 @@ - $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1340\lib\net45\OpenTK.dll - True + $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1341\lib\net45\OpenTK.dll @@ -104,7 +103,7 @@ - - + \ No newline at end of file diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableHit.cs index f325026be9..167fbebd7b 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableHit.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Graphics; using osu.Game.Modes.Objects.Drawables; using osu.Game.Modes.Taiko.Judgements; +using osu.Game.Modes.Taiko.Objects.Drawables.Pieces; using OpenTK.Input; namespace osu.Game.Modes.Taiko.Objects.Drawables @@ -66,6 +67,10 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables { Delay(HitObject.StartTime - Time.Current + Judgement.TimeOffset, true); + var circlePiece = MainPiece as CirclePiece; + + circlePiece?.FlashBox.Flush(); + switch (State) { case ArmedState.Idle: @@ -77,6 +82,16 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables case ArmedState.Hit: FadeOut(600); + var flash = circlePiece?.FlashBox; + if (flash != null) + { + flash.FadeTo(0.9f); + flash.FadeOut(300); + } + + + FadeOut(800); + const float gravity_time = 300; const float gravity_travel_height = 200; diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableSwell.cs index e1a590a025..1e440df69a 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableSwell.cs @@ -65,7 +65,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables Anchor = Anchor.Centre, Origin = Anchor.Centre, Alpha = 0, - Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2), + Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER), BlendingMode = BlendingMode.Additive, Masking = true, Children = new [] @@ -82,7 +82,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables Name = "Target ring (thick border)", Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2), + Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER), Masking = true, BorderThickness = target_ring_thick_border, BlendingMode = BlendingMode.Additive, diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/CirclePiece.cs index 6ea1494ea7..216e05ebc4 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/CirclePiece.cs @@ -19,15 +19,10 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces /// public class CirclePiece : TaikoPiece { - public const float SYMBOL_SIZE = TaikoHitObject.CIRCLE_RADIUS * 2f * 0.45f; + public const float SYMBOL_SIZE = TaikoHitObject.DEFAULT_CIRCLE_DIAMETER * 0.45f; public const float SYMBOL_BORDER = 8; public const float SYMBOL_INNER_SIZE = SYMBOL_SIZE - 2 * SYMBOL_BORDER; - /// - /// The amount to scale up the base circle to show it as a "strong" piece. - /// - private const float strong_scale = 1.5f; - /// /// The colour of the inner circle and outer glows. /// @@ -64,6 +59,8 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces private readonly Container background; + public Box FlashBox; + public CirclePiece(bool isStrong = false) { AddInternal(new Drawable[] @@ -104,11 +101,13 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces Masking = true, Children = new[] { - new Box + FlashBox = new Box { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + BlendingMode = BlendingMode.Additive, Alpha = 0, AlwaysPresent = true } @@ -125,10 +124,10 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces if (isStrong) { - Size *= strong_scale; + Size *= TaikoHitObject.STRONG_CIRCLE_DIAMETER_SCALE; //default for symbols etc. - Content.Scale *= strong_scale; + Content.Scale *= TaikoHitObject.STRONG_CIRCLE_DIAMETER_SCALE; } } diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs index a0c8865c59..2220438a4a 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs @@ -39,7 +39,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces public TaikoPiece() { //just a default - Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2); + Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER); } } } diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/TickPiece.cs b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/TickPiece.cs index 697102eb22..53e795e2e2 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/TickPiece.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/TickPiece.cs @@ -15,12 +15,12 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces /// Any tick that is not the first for a drumroll is not filled, but is instead displayed /// as a hollow circle. This is what controls the border width of that circle. /// - private const float tick_border_width = TaikoHitObject.CIRCLE_RADIUS / 2 / 4; + private const float tick_border_width = TaikoHitObject.DEFAULT_CIRCLE_DIAMETER / 16; /// /// The size of a tick. /// - private const float tick_size = TaikoHitObject.CIRCLE_RADIUS / 2; + private const float tick_size = TaikoHitObject.DEFAULT_CIRCLE_DIAMETER / 4; private bool filled; public bool Filled diff --git a/osu.Game.Modes.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Modes.Taiko/Objects/TaikoHitObject.cs index 54ab8c5300..ebc9b19d3a 100644 --- a/osu.Game.Modes.Taiko/Objects/TaikoHitObject.cs +++ b/osu.Game.Modes.Taiko/Objects/TaikoHitObject.cs @@ -4,15 +4,31 @@ using osu.Game.Beatmaps.Timing; using osu.Game.Database; using osu.Game.Modes.Objects; +using osu.Game.Modes.Taiko.UI; namespace osu.Game.Modes.Taiko.Objects { public abstract class TaikoHitObject : HitObject { /// - /// HitCircle radius. + /// Diameter of a circle relative to the size of the . /// - public const float CIRCLE_RADIUS = 42f; + public const float PLAYFIELD_RELATIVE_DIAMETER = 0.5f; + + /// + /// Scale multiplier for a strong circle. + /// + public const float STRONG_CIRCLE_DIAMETER_SCALE = 1.5f; + + /// + /// Default circle diameter. + /// + public const float DEFAULT_CIRCLE_DIAMETER = TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT * PLAYFIELD_RELATIVE_DIAMETER; + + /// + /// Default strong circle diameter. + /// + public const float DEFAULT_STRONG_CIRCLE_DIAMETER = DEFAULT_CIRCLE_DIAMETER * STRONG_CIRCLE_DIAMETER_SCALE; /// /// The time taken from the initial (off-screen) spawn position to the centre of the hit target for a of 1000ms. diff --git a/osu.Game.Modes.Taiko/UI/HitExplosion.cs b/osu.Game.Modes.Taiko/UI/HitExplosion.cs index eb43c1a5d0..e4e329523f 100644 --- a/osu.Game.Modes.Taiko/UI/HitExplosion.cs +++ b/osu.Game.Modes.Taiko/UI/HitExplosion.cs @@ -18,11 +18,6 @@ namespace osu.Game.Modes.Taiko.UI /// internal class HitExplosion : CircularContainer { - /// - /// The size multiplier of a hit explosion if a hit object has been hit with the second key. - /// - private const float secondhit_size_multiplier = 1.5f; - /// /// The judgement this hit explosion visualises. /// @@ -34,7 +29,7 @@ namespace osu.Game.Modes.Taiko.UI { Judgement = judgement; - Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2); + Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER); Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -85,7 +80,7 @@ namespace osu.Game.Modes.Taiko.UI /// public void VisualiseSecondHit() { - ResizeTo(Size * secondhit_size_multiplier, 50); + ResizeTo(Size * TaikoHitObject.STRONG_CIRCLE_DIAMETER_SCALE, 50); } } } diff --git a/osu.Game.Modes.Taiko/UI/HitTarget.cs b/osu.Game.Modes.Taiko/UI/HitTarget.cs index a17480628d..b22dc1d647 100644 --- a/osu.Game.Modes.Taiko/UI/HitTarget.cs +++ b/osu.Game.Modes.Taiko/UI/HitTarget.cs @@ -15,16 +15,6 @@ namespace osu.Game.Modes.Taiko.UI /// internal class HitTarget : Container { - /// - /// Diameter of normal hit object circles. - /// - private const float normal_diameter = TaikoHitObject.CIRCLE_RADIUS * 2; - - /// - /// Diameter of strong hit object circles. - /// - private const float strong_hit_diameter = normal_diameter * 1.5f; - /// /// The 1px inner border of the taiko playfield. /// @@ -37,7 +27,7 @@ namespace osu.Game.Modes.Taiko.UI public HitTarget() { - RelativeSizeAxes = Axes.Y; + Size = new Vector2(TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT); Children = new Drawable[] { @@ -47,7 +37,7 @@ namespace osu.Game.Modes.Taiko.UI Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Y = border_offset, - Size = new Vector2(border_thickness, (TaikoPlayfield.PLAYFIELD_HEIGHT - strong_hit_diameter) / 2f - border_offset), + Size = new Vector2(border_thickness, (TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT - TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER) / 2f - border_offset), Alpha = 0.1f }, new CircularContainer @@ -55,7 +45,7 @@ namespace osu.Game.Modes.Taiko.UI Name = "Strong Hit Ring", Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(strong_hit_diameter), + Size = new Vector2(TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER), Masking = true, BorderColour = Color4.White, BorderThickness = border_thickness, @@ -75,7 +65,7 @@ namespace osu.Game.Modes.Taiko.UI Name = "Normal Hit Ring", Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(normal_diameter), + Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER), Masking = true, BorderColour = Color4.White, BorderThickness = border_thickness, @@ -96,7 +86,7 @@ namespace osu.Game.Modes.Taiko.UI Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, Y = -border_offset, - Size = new Vector2(border_thickness, (TaikoPlayfield.PLAYFIELD_HEIGHT - strong_hit_diameter) / 2f - border_offset), + Size = new Vector2(border_thickness, (TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT - TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER) / 2f - border_offset), Alpha = 0.1f }, }; diff --git a/osu.Game.Modes.Taiko/UI/InputDrum.cs b/osu.Game.Modes.Taiko/UI/InputDrum.cs index 0c1e1105cb..d238c38e74 100644 --- a/osu.Game.Modes.Taiko/UI/InputDrum.cs +++ b/osu.Game.Modes.Taiko/UI/InputDrum.cs @@ -21,7 +21,7 @@ namespace osu.Game.Modes.Taiko.UI { public InputDrum() { - Size = new Vector2(TaikoPlayfield.PLAYFIELD_HEIGHT); + Size = new Vector2(TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT); const float middle_split = 10; diff --git a/osu.Game.Modes.Taiko/UI/TaikoHitRenderer.cs b/osu.Game.Modes.Taiko/UI/TaikoHitRenderer.cs index 29fa693d58..32476dff7f 100644 --- a/osu.Game.Modes.Taiko/UI/TaikoHitRenderer.cs +++ b/osu.Game.Modes.Taiko/UI/TaikoHitRenderer.cs @@ -17,6 +17,7 @@ using osu.Game.Modes.Taiko.Objects.Drawables; using osu.Game.Modes.Taiko.Scoring; using osu.Game.Modes.UI; using osu.Game.Modes.Taiko.Replays; +using OpenTK; namespace osu.Game.Modes.Taiko.UI { @@ -100,6 +101,17 @@ namespace osu.Game.Modes.Taiko.UI } } + protected override Vector2 GetPlayfieldAspectAdjust() + { + const float default_relative_height = TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT / 768; + const float default_aspect = 16f / 9f; + + float aspectAdjust = MathHelper.Clamp(DrawWidth / DrawHeight, 0.4f, 4) / default_aspect; + + return new Vector2(1, default_relative_height * aspectAdjust); + } + + public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this); protected override IBeatmapConverter CreateBeatmapConverter() => new TaikoBeatmapConverter(); diff --git a/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs index 9e7eb571a1..db3a1bc84e 100644 --- a/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs @@ -16,21 +16,21 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.Primitives; using System.Linq; using osu.Game.Modes.Taiko.Objects.Drawables; +using System; namespace osu.Game.Modes.Taiko.UI { public class TaikoPlayfield : Playfield { /// - /// The play field height. This is relative to the size of hit objects - /// such that the playfield is just a bit larger than strong hits. + /// The default play field height. /// - public const float PLAYFIELD_HEIGHT = TaikoHitObject.CIRCLE_RADIUS * 2 * 2; + public const float DEFAULT_PLAYFIELD_HEIGHT = 168f; /// /// The offset from which the center of the hit target lies at. /// - private const float hit_target_offset = TaikoHitObject.CIRCLE_RADIUS * 1.5f + 40; + private const float hit_target_offset = TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER / 2f + 40; /// /// The size of the left area of the playfield. This area contains the input drum. @@ -52,13 +52,11 @@ namespace osu.Game.Modes.Taiko.UI public TaikoPlayfield() { - RelativeSizeAxes = Axes.X; - Height = PLAYFIELD_HEIGHT; - AddInternal(new Drawable[] { rightBackgroundContainer = new Container { + Name = "Transparent playfield background", RelativeSizeAxes = Axes.Both, BorderThickness = 2, Masking = true, @@ -77,76 +75,88 @@ namespace osu.Game.Modes.Taiko.UI }, } }, - new Container + new ScaleFixContainer { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = left_area_size }, - Children = new Drawable[] + RelativeSizeAxes = Axes.X, + Height = DEFAULT_PLAYFIELD_HEIGHT, + Children = new[] { new Container { - X = hit_target_offset, + Name = "Transparent playfield elements", RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = left_area_size }, Children = new Drawable[] { - hitExplosionContainer = new Container + new Container { - Anchor = Anchor.CentreLeft, + Name = "Hit target container", + X = hit_target_offset, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + hitExplosionContainer = new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + BlendingMode = BlendingMode.Additive + }, + barLineContainer = new Container + { + RelativeSizeAxes = Axes.Both, + }, + new HitTarget + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + }, + hitObjectContainer = new Container + { + RelativeSizeAxes = Axes.Both, + }, + judgementContainer = new Container + { + RelativeSizeAxes = Axes.Y, + BlendingMode = BlendingMode.Additive + }, + }, + }, + } + }, + leftBackgroundContainer = new Container + { + Name = "Left overlay", + Size = new Vector2(left_area_size, DEFAULT_PLAYFIELD_HEIGHT), + BorderThickness = 1, + Children = new Drawable[] + { + leftBackground = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new InputDrum + { + Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2), - BlendingMode = BlendingMode.Additive + RelativePositionAxes = Axes.X, + Position = new Vector2(0.10f, 0), + Scale = new Vector2(0.9f) }, - barLineContainer = new Container + new Box { - RelativeSizeAxes = Axes.Both, + Anchor = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = 10, + ColourInfo = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)), }, - new HitTarget - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.Centre, - }, - hitObjectContainer = new Container - { - RelativeSizeAxes = Axes.Both, - }, - judgementContainer = new Container - { - RelativeSizeAxes = Axes.Both, - BlendingMode = BlendingMode.Additive - }, - }, - }, - } - }, - leftBackgroundContainer = new Container - { - Size = new Vector2(left_area_size, PLAYFIELD_HEIGHT), - BorderThickness = 1, - Children = new Drawable[] - { - leftBackground = new Box - { - RelativeSizeAxes = Axes.Both, - }, - new InputDrum - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.X, - Position = new Vector2(0.10f, 0), - Scale = new Vector2(0.9f) - }, - new Box - { - Anchor = Anchor.TopRight, - RelativeSizeAxes = Axes.Y, - Width = 10, - ColourInfo = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)), + } }, } }, topLevelHitContainer = new Container { + Name = "Top level hit objects", RelativeSizeAxes = Axes.Both, } }); @@ -208,5 +218,56 @@ namespace osu.Game.Modes.Taiko.UI else hitExplosionContainer.Children.FirstOrDefault(e => e.Judgement == judgedObject.Judgement)?.VisualiseSecondHit(); } + + /// + /// This is a very special type of container. It serves a similar purpose to , however unlike , + /// this will only adjust the scale relative to the height of its parent and will maintain the original width relative to its parent. + /// + /// + /// By adjusting the scale relative to the height of its parent, the aspect ratio of this container's children is maintained, however this is undesirable + /// in the case where the hit object container should not have its width adjusted by scale. To counteract this, another container is nested inside this + /// container which takes care of reversing the width adjustment while appearing transparent to the user. + /// + /// + private class ScaleFixContainer : Container + { + protected override Container Content => widthAdjustmentContainer; + private readonly WidthAdjustmentContainer widthAdjustmentContainer; + + /// + /// We only want to apply DrawScale in the Y-axis to preserve aspect ratio and doesn't care about having its width adjusted. + /// + protected override Vector2 DrawScale => Scale * RelativeToAbsoluteFactor.Y / DrawHeight; + + public ScaleFixContainer() + { + AddInternal(widthAdjustmentContainer = new WidthAdjustmentContainer { ParentDrawScaleReference = () => DrawScale.X }); + } + + /// + /// The container type that reverses the width adjustment. + /// + private class WidthAdjustmentContainer : Container + { + /// + /// This container needs to know its parent's so it can reverse the width adjustment caused by . + /// + public Func ParentDrawScaleReference; + + public WidthAdjustmentContainer() + { + // This container doesn't care about height, it should always fill its parent + RelativeSizeAxes = Axes.Y; + } + + protected override void Update() + { + base.Update(); + + // Reverse the DrawScale adjustment + Width = Parent.DrawSize.X / ParentDrawScaleReference(); + } + } + } } } \ No newline at end of file diff --git a/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj b/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj index d0981c2500..19ba5c77e4 100644 --- a/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj +++ b/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj @@ -33,8 +33,7 @@ - $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1340\lib\net45\OpenTK.dll - True + $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1341\lib\net45\OpenTK.dll @@ -112,7 +111,7 @@ - - + \ No newline at end of file diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index d01aa77e02..2844528d0c 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -29,13 +29,11 @@ false - - $(SolutionDir)\packages\NUnit.3.5.0\lib\net45\nunit.framework.dll - True + + $(SolutionDir)\packages\NUnit.3.6.1\lib\net45\nunit.framework.dll - $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1340\lib\net45\OpenTK.dll - True + $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1341\lib\net45\OpenTK.dll diff --git a/osu.Game.Tests/packages.config b/osu.Game.Tests/packages.config index ca53ef08b0..9972fb41a1 100644 --- a/osu.Game.Tests/packages.config +++ b/osu.Game.Tests/packages.config @@ -1,12 +1,11 @@  - - - + + \ No newline at end of file diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 7d7c61b69a..e2f33479c0 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -46,6 +46,7 @@ namespace osu.Game.Configuration Set(OsuConfig.AutomaticDownload, true).Disabled = true; Set(OsuConfig.AutomaticDownloadNoVideo, false).Disabled = true; Set(OsuConfig.BlockNonFriendPM, false).Disabled = true; + Set(OsuConfig.Bloom, false).Disabled = true; Set(OsuConfig.BloomSoftening, false).Disabled = true; Set(OsuConfig.BossKeyFirstActivation, true).Disabled = true; Set(OsuConfig.ChatAudibleHighlight, true).Disabled = true; diff --git a/osu.Game/Database/BeatmapInfo.cs b/osu.Game/Database/BeatmapInfo.cs index bc6e077633..3e84825919 100644 --- a/osu.Game/Database/BeatmapInfo.cs +++ b/osu.Game/Database/BeatmapInfo.cs @@ -41,8 +41,12 @@ namespace osu.Game.Database [OneToOne(CascadeOperations = CascadeOperation.All)] public BeatmapDifficulty Difficulty { get; set; } + [Ignore] + public BeatmapMetrics Metrics { get; set; } + public string Path { get; set; } + [JsonProperty("file_md5")] public string Hash { get; set; } // General diff --git a/osu.Game/Database/BeatmapMetrics.cs b/osu.Game/Database/BeatmapMetrics.cs new file mode 100644 index 0000000000..91320110d0 --- /dev/null +++ b/osu.Game/Database/BeatmapMetrics.cs @@ -0,0 +1,28 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; + +namespace osu.Game.Database +{ + /// + /// Beatmap metrics based on acculumated online data from community plays. + /// + public class BeatmapMetrics + { + /// + /// Total vote counts of user ratings on a scale of 0..length. + /// + public IEnumerable Ratings { get; set; } + + /// + /// Points of failure on a relative time scale (usually 0..100). + /// + public IEnumerable Fails { get; set; } + + /// + /// Points of retry on a relative time scale (usually 0..100). + /// + public IEnumerable Retries { get; set; } + } +} diff --git a/osu.Game/Graphics/Cursor/CursorTrail.cs b/osu.Game/Graphics/Cursor/CursorTrail.cs index 4b5610e840..09d1b99d13 100644 --- a/osu.Game/Graphics/Cursor/CursorTrail.cs +++ b/osu.Game/Graphics/Cursor/CursorTrail.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.OpenGL.Buffers; using OpenTK.Graphics.ES30; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Colour; +using osu.Framework.Timing; namespace osu.Game.Graphics.Cursor { @@ -58,6 +59,9 @@ namespace osu.Game.Graphics.Cursor public CursorTrail() { + // as we are currently very dependent on having a running clock, let's make our own clock for the time being. + Clock = new FramedClock(); + AlwaysReceiveInput = true; RelativeSizeAxes = Axes.Both; @@ -231,4 +235,4 @@ namespace osu.Game.Graphics.Cursor } } } -} \ No newline at end of file +} diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 0fb7f59212..ceb3296bdf 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -80,14 +80,12 @@ namespace osu.Game.Graphics.Cursor protected override void PopIn() { ActiveCursor.FadeTo(1, 250, EasingTypes.OutQuint); - ActiveCursor.ScaleTo(1, 1000, EasingTypes.OutElastic); + ActiveCursor.ScaleTo(1, 400, EasingTypes.OutQuint); } protected override void PopOut() { - ActiveCursor.FadeTo(0, 1400, EasingTypes.OutQuint); - ActiveCursor.ScaleTo(1.1f, 100, EasingTypes.Out); - ActiveCursor.Delay(100); + ActiveCursor.FadeTo(0, 900, EasingTypes.OutQuint); ActiveCursor.ScaleTo(0, 500, EasingTypes.In); } diff --git a/osu.Game/Graphics/IHasAccentColour.cs b/osu.Game/Graphics/IHasAccentColour.cs index f959bc8760..e4647f22fd 100644 --- a/osu.Game/Graphics/IHasAccentColour.cs +++ b/osu.Game/Graphics/IHasAccentColour.cs @@ -28,7 +28,7 @@ namespace osu.Game.Graphics /// The tween easing. public static void FadeAccent(this IHasAccentColour accentedDrawable, Color4 newColour, double duration = 0, EasingTypes easing = EasingTypes.None) { - accentedDrawable.TransformTo(accentedDrawable.AccentColour, newColour, duration, easing, new TransformAccent()); + accentedDrawable.TransformTo(() => accentedDrawable.AccentColour, newColour, duration, easing, new TransformAccent()); } } } diff --git a/osu.Game/Graphics/UserInterface/Bar.cs b/osu.Game/Graphics/UserInterface/Bar.cs new file mode 100644 index 0000000000..76b75f1084 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/Bar.cs @@ -0,0 +1,137 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using System; + +namespace osu.Game.Graphics.UserInterface +{ + public class Bar : Container, IHasAccentColour + { + private readonly Box background; + private readonly Box bar; + + private const int resize_duration = 250; + + private const EasingTypes easing = EasingTypes.InOutCubic; + + private float length; + /// + /// Length of the bar, ranges from 0 to 1 + /// + public float Length + { + get + { + return length; + } + set + { + length = MathHelper.Clamp(value, 0, 1); + updateBarLength(); + } + } + + public Color4 BackgroundColour + { + get + { + return background.Colour; + } + set + { + background.Colour = value; + } + } + + public Color4 AccentColour + { + get + { + return bar.Colour; + } + set + { + bar.Colour = value; + } + } + + private BarDirection direction = BarDirection.LeftToRight; + public BarDirection Direction + { + get + { + return direction; + } + set + { + direction = value; + updateBarLength(); + } + } + + public Bar() + { + Children = new[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = new Color4(0,0,0,0) + }, + bar = new Box + { + RelativeSizeAxes = Axes.Both, + Width = 0, + }, + }; + } + + private void updateBarLength() + { + switch (direction) + { + case BarDirection.LeftToRight: + case BarDirection.RightToLeft: + bar.ResizeTo(new Vector2(length, 1), resize_duration, easing); + break; + + case BarDirection.TopToBottom: + case BarDirection.BottomToTop: + bar.ResizeTo(new Vector2(1, length), resize_duration, easing); + break; + } + + switch (direction) + { + case BarDirection.LeftToRight: + case BarDirection.TopToBottom: + bar.Anchor = Anchor.TopLeft; + bar.Origin = Anchor.TopLeft; + break; + + case BarDirection.RightToLeft: + case BarDirection.BottomToTop: + bar.Anchor = Anchor.BottomRight; + bar.Origin = Anchor.BottomRight; + break; + } + } + } + + [Flags] + public enum BarDirection + { + LeftToRight = 1 << 0, + RightToLeft = 1 << 1, + TopToBottom = 1 << 2, + BottomToTop = 1 << 3, + + Vertical = TopToBottom | BottomToTop, + Horizontal = LeftToRight | RightToLeft, + } +} \ No newline at end of file diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs new file mode 100644 index 0000000000..d0965a1861 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -0,0 +1,65 @@ +// 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.Framework.Graphics.Containers; +using System.Collections.Generic; +using System.Linq; + +namespace osu.Game.Graphics.UserInterface +{ + public class BarGraph : FillFlowContainer + { + /// + /// Manually sets the max value, if null is instead used + /// + public float? MaxValue { get; set; } + + private BarDirection direction = BarDirection.BottomToTop; + public new BarDirection Direction + { + get + { + return direction; + } + set + { + direction = value; + base.Direction = (direction & BarDirection.Horizontal) > 0 ? FillDirection.Vertical : FillDirection.Horizontal; + foreach (var bar in Children) + { + bar.Size = (direction & BarDirection.Horizontal) > 0 ? new Vector2(1, 1.0f / Children.Count()) : new Vector2(1.0f / Children.Count(), 1); + bar.Direction = direction; + } + } + } + + /// + /// A list of floats that defines the length of each + /// + public IEnumerable Values + { + set + { + List bars = Children.ToList(); + foreach (var bar in value.Select((length, index) => new { Value = length, Bar = bars.Count > index ? bars[index] : null })) + if (bar.Bar != null) + { + bar.Bar.Length = bar.Value / (MaxValue ?? value.Max()); + bar.Bar.Size = (direction & BarDirection.Horizontal) > 0 ? new Vector2(1, 1.0f / value.Count()) : new Vector2(1.0f / value.Count(), 1); + } + else + Add(new Bar + { + RelativeSizeAxes = Axes.Both, + Size = (direction & BarDirection.Horizontal) > 0 ? new Vector2(1, 1.0f / value.Count()) : new Vector2(1.0f / value.Count(), 1), + Length = bar.Value / (MaxValue ?? value.Max()), + Direction = Direction, + }); + //I'm using ToList() here because Where() returns an Enumerable which can change it's elements afterwards + Remove(Children.Where((bar, index) => index >= value.Count()).ToList()); + } + } + } +} \ No newline at end of file diff --git a/osu.Game/Graphics/UserInterface/Nub.cs b/osu.Game/Graphics/UserInterface/Nub.cs index e150c7dc07..82ede8f079 100644 --- a/osu.Game/Graphics/UserInterface/Nub.cs +++ b/osu.Game/Graphics/UserInterface/Nub.cs @@ -3,8 +3,8 @@ using OpenTK; using OpenTK.Graphics; -using osu.Framework; using osu.Framework.Allocation; +using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -12,18 +12,18 @@ using osu.Framework.Graphics.UserInterface; namespace osu.Game.Graphics.UserInterface { - public class Nub : CircularContainer, IStateful + public class Nub : CircularContainer, IHasCurrentValue { public const float COLLAPSED_SIZE = 20; public const float EXPANDED_SIZE = 40; - private readonly Box fill; - private const float border_width = 3; private Color4 glowingColour, idleColour; public Nub() { + Box fill; + Size = new Vector2(COLLAPSED_SIZE, 12); BorderColour = Color4.White; @@ -40,6 +40,14 @@ namespace osu.Game.Graphics.UserInterface AlwaysPresent = true, }, }; + + Current.ValueChanged += newValue => + { + if (newValue) + fill.FadeIn(200, EasingTypes.OutQuint); + else + fill.FadeTo(0.01f, 200, EasingTypes.OutQuint); //todo: remove once we figure why containers aren't drawing at all times + }; } [BackgroundDependencyLoader] @@ -84,28 +92,6 @@ namespace osu.Game.Graphics.UserInterface } } - private CheckboxState state; - - public CheckboxState State - { - get - { - return state; - } - set - { - state = value; - - switch (state) - { - case CheckboxState.Checked: - fill.FadeIn(200, EasingTypes.OutQuint); - break; - case CheckboxState.Unchecked: - fill.FadeTo(0.01f, 200, EasingTypes.OutQuint); //todo: remove once we figure why containers aren't drawing at all times - break; - } - } - } + public Bindable Current { get; } = new Bindable(); } } diff --git a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs index 6a5151b90c..d339388aa5 100644 --- a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs @@ -23,18 +23,9 @@ namespace osu.Game.Graphics.UserInterface { set { - if (bindable != null) - bindable.ValueChanged -= bindableValueChanged; bindable = value; - if (bindable != null) - { - bool state = State == CheckboxState.Checked; - if (state != bindable.Value) - State = bindable.Value ? CheckboxState.Checked : CheckboxState.Unchecked; - bindable.ValueChanged += bindableValueChanged; - } - - if (bindable?.Disabled ?? true) + Current.BindTo(bindable); + if (value?.Disabled ?? true) Alpha = 0.3f; } } @@ -83,18 +74,16 @@ namespace osu.Game.Graphics.UserInterface Margin = new MarginPadding { Right = 5 }, } }; - } - private void bindableValueChanged(bool isChecked) - { - State = isChecked ? CheckboxState.Checked : CheckboxState.Unchecked; - } + nub.Current.BindTo(Current); - protected override void Dispose(bool isDisposing) - { - if (bindable != null) - bindable.ValueChanged -= bindableValueChanged; - base.Dispose(isDisposing); + Current.ValueChanged += newValue => + { + if (newValue) + sampleChecked?.Play(); + else + sampleUnchecked?.Play(); + }; } protected override bool OnHover(InputState state) @@ -117,23 +106,5 @@ namespace osu.Game.Graphics.UserInterface sampleChecked = audio.Sample.Get(@"Checkbox/check-on"); sampleUnchecked = audio.Sample.Get(@"Checkbox/check-off"); } - - protected override void OnChecked() - { - sampleChecked?.Play(); - nub.State = CheckboxState.Checked; - - if (bindable != null) - bindable.Value = true; - } - - protected override void OnUnchecked() - { - sampleUnchecked?.Play(); - nub.State = CheckboxState.Unchecked; - - if (bindable != null) - bindable.Value = false; - } } } diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index 3466fb1a60..9bb0d15545 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -45,7 +45,7 @@ namespace osu.Game.Graphics.UserInterface private class OsuDropdownMenuItem : DropdownMenuItem { - public OsuDropdownMenuItem(string text, T value) : base(text, value) + public OsuDropdownMenuItem(string text, T current) : base(text, current) { Foreground.Padding = new MarginPadding(2); diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 078c8564d7..180cb88707 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using OpenTK; -using OpenTK.Input; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -50,7 +49,6 @@ namespace osu.Game.Graphics.UserInterface nub = new Nub { Origin = Anchor.TopCentre, - State = CheckboxState.Unchecked, Expanded = true, } }; @@ -64,15 +62,6 @@ namespace osu.Game.Graphics.UserInterface rightBox.Colour = colours.Pink; } - private void playSample() - { - if (Clock == null || Clock.CurrentTime - lastSampleTime <= 50) - return; - lastSampleTime = Clock.CurrentTime; - sample.Frequency.Value = 1 + NormalizedValue * 0.2f; - sample.Play(); - } - protected override bool OnHover(InputState state) { nub.Glowing = true; @@ -85,37 +74,39 @@ namespace osu.Game.Graphics.UserInterface base.OnHoverLost(state); } - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + protected override void OnUserChange() { - if (args.Key == Key.Left || args.Key == Key.Right) - playSample(); - return base.OnKeyDown(state, args); + base.OnUserChange(); + playSample(); + } + + private void playSample() + { + if (Clock == null || Clock.CurrentTime - lastSampleTime <= 50) + return; + lastSampleTime = Clock.CurrentTime; + sample.Frequency.Value = 1 + NormalizedValue * 0.2f; + + if (NormalizedValue == 0) + sample.Frequency.Value -= 0.4f; + else if (NormalizedValue == 1) + sample.Frequency.Value += 0.4f; + + sample.Play(); } protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { - nub.State = CheckboxState.Checked; + nub.Current.Value = true; return base.OnMouseDown(state, args); } protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) { - nub.State = CheckboxState.Unchecked; + nub.Current.Value = false; return base.OnMouseUp(state, args); } - protected override bool OnClick(InputState state) - { - playSample(); - return base.OnClick(state); - } - - protected override bool OnDrag(InputState state) - { - playSample(); - return base.OnDrag(state); - } - protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); diff --git a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs index 5914d0ba4c..f732916889 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs @@ -1,7 +1,6 @@ // 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.Allocation; @@ -24,8 +23,6 @@ namespace osu.Game.Graphics.UserInterface private readonly SpriteText text; private readonly TextAwesome icon; - public event EventHandler Action; - private Color4? accentColour; public Color4 AccentColour { @@ -34,7 +31,7 @@ namespace osu.Game.Graphics.UserInterface { accentColour = value; - if (State != CheckboxState.Checked) + if (Current) { text.Colour = AccentColour; icon.Colour = AccentColour; @@ -48,20 +45,6 @@ namespace osu.Game.Graphics.UserInterface set { text.Text = value; } } - protected override void OnChecked() - { - fadeIn(); - icon.Icon = FontAwesome.fa_check_circle_o; - Action?.Invoke(this, State); - } - - protected override void OnUnchecked() - { - fadeOut(); - icon.Icon = FontAwesome.fa_circle_o; - Action?.Invoke(this, State); - } - private const float transition_length = 500; private void fadeIn() @@ -84,7 +67,7 @@ namespace osu.Game.Graphics.UserInterface protected override void OnHoverLost(InputState state) { - if (State == CheckboxState.Unchecked) + if (!Current) fadeOut(); base.OnHoverLost(state); @@ -134,6 +117,20 @@ namespace osu.Game.Graphics.UserInterface Anchor = Anchor.BottomLeft, } }; + + Current.ValueChanged += v => + { + if (v) + { + fadeIn(); + icon.Icon = FontAwesome.fa_check_circle_o; + } + else + { + fadeOut(); + icon.Icon = FontAwesome.fa_circle_o; + } + }; } } } diff --git a/osu.Game/Graphics/UserInterface/RollingCounter.cs b/osu.Game/Graphics/UserInterface/RollingCounter.cs index 12eeb771dd..869ee37e11 100644 --- a/osu.Game/Graphics/UserInterface/RollingCounter.cs +++ b/osu.Game/Graphics/UserInterface/RollingCounter.cs @@ -108,8 +108,6 @@ namespace osu.Game.Graphics.UserInterface { base.LoadComplete(); - Flush(false, TransformType); - DisplayedCountSpriteText.Text = FormatCount(Current); DisplayedCountSpriteText.Anchor = Anchor; DisplayedCountSpriteText.Origin = Origin; @@ -205,8 +203,8 @@ namespace osu.Game.Graphics.UserInterface ? GetProportionalDuration(currentValue, newValue) : RollingDuration; - transform.StartTime = Time.Current; - transform.EndTime = Time.Current + rollingTotalDuration; + transform.StartTime = TransformStartTime; + transform.EndTime = TransformStartTime + rollingTotalDuration; transform.StartValue = currentValue; transform.EndValue = newValue; transform.Easing = RollingEasing; diff --git a/osu.Game/Modes/Replays/FramedReplayInputHandler.cs b/osu.Game/Modes/Replays/FramedReplayInputHandler.cs index ae20ece515..0c1e140ce4 100644 --- a/osu.Game/Modes/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Modes/Replays/FramedReplayInputHandler.cs @@ -136,7 +136,7 @@ namespace osu.Game.Modes.Replays public ReplayMouseState(Vector2 position, IEnumerable list) { Position = position; - list.ForEach(b => PressedButtons.Add(b)); + list.ForEach(b => SetPressed(b, true)); } } @@ -148,4 +148,4 @@ namespace osu.Game.Modes.Replays } } } -} \ No newline at end of file +} diff --git a/osu.Game/Modes/Scoring/Score.cs b/osu.Game/Modes/Scoring/Score.cs index c998b11f77..b0c123f438 100644 --- a/osu.Game/Modes/Scoring/Score.cs +++ b/osu.Game/Modes/Scoring/Score.cs @@ -27,7 +27,24 @@ namespace osu.Game.Modes.Scoring public int Combo { get; set; } public Mod[] Mods { get; set; } - public User User { get; set; } + private User user; + + public User User + { + get + { + return user ?? new User + { + Username = LegacyUsername, + Id = LegacyUserID + }; + } + + set + { + user = value; + } + } [JsonProperty(@"replay_data")] public Replay Replay; @@ -38,10 +55,10 @@ namespace osu.Game.Modes.Scoring public long OnlineScoreID; [JsonProperty(@"username")] - public string Username; + public string LegacyUsername; [JsonProperty(@"user_id")] - public long UserID; + public long LegacyUserID; [JsonProperty(@"date")] public DateTime Date; diff --git a/osu.Game/Modes/UI/HitRenderer.cs b/osu.Game/Modes/UI/HitRenderer.cs index a958c61c68..dd5eff5a95 100644 --- a/osu.Game/Modes/UI/HitRenderer.cs +++ b/osu.Game/Modes/UI/HitRenderer.cs @@ -16,6 +16,7 @@ using System.Diagnostics; using System.Linq; using osu.Game.Modes.Replays; using osu.Game.Modes.Scoring; +using OpenTK; namespace osu.Game.Modes.UI { @@ -32,6 +33,11 @@ namespace osu.Game.Modes.UI /// public event Action OnAllJudged; + /// + /// Whether to apply adjustments to the child based on our own size. + /// + public bool AspectAdjust = true; + /// /// The input manager for this HitRenderer. /// @@ -219,6 +225,19 @@ namespace osu.Game.Modes.UI Playfield.PostProcess(); } + protected override void Update() + { + base.Update(); + + Playfield.Size = AspectAdjust ? GetPlayfieldAspectAdjust() : Vector2.One; + } + + /// + /// In some cases we want to apply changes to the relative size of our contained based on custom conditions. + /// + /// + protected virtual Vector2 GetPlayfieldAspectAdjust() => new Vector2(0.75f); //a sane default + /// /// Triggered when an object's Judgement is updated. /// diff --git a/osu.Game/Modes/UI/Playfield.cs b/osu.Game/Modes/UI/Playfield.cs index f31ee0f189..1e7cf6579c 100644 --- a/osu.Game/Modes/UI/Playfield.cs +++ b/osu.Game/Modes/UI/Playfield.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.Modes.Objects; @@ -38,6 +39,9 @@ namespace osu.Game.Modes.UI { AlwaysReceiveInput = true; + // Default height since we force relative size axes + Size = Vector2.One; + AddInternal(ScaledContent = new ScaledContainer { CustomWidth = customWidth, @@ -63,6 +67,12 @@ namespace osu.Game.Modes.UI Add(HitObjects); } + public override Axes RelativeSizeAxes + { + get { return Axes.Both; } + set { throw new InvalidOperationException($@"{nameof(Playfield)}'s {nameof(RelativeSizeAxes)} should never be changed from {Axes.Both}"); } + } + /// /// Performs post-processing tasks (if any) after all DrawableHitObjects are loaded into this Playfield. /// diff --git a/osu.Game/Modes/UI/StandardHudOverlay.cs b/osu.Game/Modes/UI/StandardHudOverlay.cs index d51ffab06b..e2eda4a168 100644 --- a/osu.Game/Modes/UI/StandardHudOverlay.cs +++ b/osu.Game/Modes/UI/StandardHudOverlay.cs @@ -40,6 +40,7 @@ namespace osu.Game.Modes.UI Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, Margin = new MarginPadding(10), + Y = - TwoLayerButton.SIZE_RETRACTED.Y, }; protected override ScoreCounter CreateScoreCounter() => new ScoreCounter(6) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 7172aba3be..ccea6ef458 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -307,6 +307,18 @@ namespace osu.Game return base.OnExiting(); } + /// + /// Use to programatically exit the game as if the user was triggering via alt-f4. + /// Will keep persisting until an exit occurs (exit may be blocked multiple times). + /// + public void GracefullyExit() + { + if (!OnExiting()) + Exit(); + else + Scheduler.AddDelayed(GracefullyExit, 2000); + } + protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 0bb3d3dc71..fc12789b05 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -93,7 +93,7 @@ namespace osu.Game.Overlays { var postText = sender.Text; - if (!string.IsNullOrEmpty(postText)) + if (!string.IsNullOrEmpty(postText) && api.LocalUser.Value != null) { //todo: actually send to server careChannels.FirstOrDefault()?.AddNewMessages(new[] diff --git a/osu.Game/Overlays/DragBar.cs b/osu.Game/Overlays/DragBar.cs index 123cd404c7..8690388127 100644 --- a/osu.Game/Overlays/DragBar.cs +++ b/osu.Game/Overlays/DragBar.cs @@ -72,7 +72,7 @@ namespace osu.Game.Overlays private void updatePosition(float position) { position = MathHelper.Clamp(position, 0, 1); - FillContainer.TransformTo(FillContainer.Width, position, 200, EasingTypes.OutQuint, new TransformSeek()); + FillContainer.TransformTo(() => FillContainer.Width, position, 200, EasingTypes.OutQuint, new TransformSeek()); } protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 73b7695dab..9d21a0341c 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -48,7 +48,7 @@ namespace osu.Game.Overlays private const float progress_height = 10; - private const float bottom_black_area_height = 50; + private const float bottom_black_area_height = 55; public MusicController() { @@ -119,7 +119,8 @@ namespace osu.Game.Overlays Text = @"Nothing to play", Font = @"Exo2.0-BoldItalic" }, - new Container { + new Container + { Padding = new MarginPadding { Bottom = progress_height }, Height = bottom_black_area_height, RelativeSizeAxes = Axes.X, @@ -143,7 +144,8 @@ namespace osu.Game.Overlays }, playButton = new Button { - //Scale = new Vector2(1.3f), + Scale = new Vector2(1.4f), + IconScale = new Vector2(1.4f), Action = () => { if (current?.Track == null) return; @@ -421,6 +423,13 @@ namespace osu.Game.Overlays } private const float button_size = 30; + private Color4 flashColour; + + public Vector2 IconScale + { + get { return icon.Scale; } + set { icon.Scale = value; } + } public Button() { @@ -467,6 +476,7 @@ namespace osu.Game.Overlays private void load(OsuColour colours) { hover.Colour = colours.Yellow.Opacity(0.6f); + flashColour = colours.Yellow; } protected override bool OnHover(InputState state) @@ -481,9 +491,15 @@ namespace osu.Game.Overlays base.OnHoverLost(state); } + protected override bool OnClick(InputState state) + { + hover.FlashColour(flashColour, 800, EasingTypes.OutQuint); + return base.OnClick(state); + } + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { - content.ScaleTo(1, 2000, EasingTypes.OutQuint); + content.ScaleTo(0.75f, 2000, EasingTypes.OutQuint); return base.OnMouseDown(state, args); } diff --git a/osu.Game/Overlays/Options/OptionDropdown.cs b/osu.Game/Overlays/Options/OptionDropdown.cs index 9ae02a17d3..ee12ba9b05 100644 --- a/osu.Game/Overlays/Options/OptionDropdown.cs +++ b/osu.Game/Overlays/Options/OptionDropdown.cs @@ -33,8 +33,8 @@ namespace osu.Game.Overlays.Options set { bindable = value; - dropdown.SelectedValue.BindTo(bindable); - if (bindable.Disabled) + dropdown.Current.BindTo(bindable); + if (value?.Disabled ?? true) Alpha = 0.3f; } } diff --git a/osu.Game/Overlays/Options/OptionSlider.cs b/osu.Game/Overlays/Options/OptionSlider.cs index 8fa9bf063d..1c4b54a080 100644 --- a/osu.Game/Overlays/Options/OptionSlider.cs +++ b/osu.Game/Overlays/Options/OptionSlider.cs @@ -27,12 +27,14 @@ namespace osu.Game.Overlays.Options } } - public BindableNumber Bindable + private Bindable bindable; + + public Bindable Bindable { - get { return slider.Value; } set { - slider.Value = value; + bindable = value; + slider.Current.BindTo(bindable); if (value?.Disabled ?? true) Alpha = 0.3f; } diff --git a/osu.Game/Overlays/Options/OptionTextBox.cs b/osu.Game/Overlays/Options/OptionTextBox.cs index 722f24d50d..b5ef39c8b2 100644 --- a/osu.Game/Overlays/Options/OptionTextBox.cs +++ b/osu.Game/Overlays/Options/OptionTextBox.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Configuration; -using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Options @@ -15,38 +14,11 @@ namespace osu.Game.Overlays.Options { set { - if (bindable != null) - bindable.ValueChanged -= bindableValueChanged; bindable = value; - if (bindable != null) - { - Text = bindable.Value; - bindable.ValueChanged += bindableValueChanged; - } - - if (bindable?.Disabled ?? true) + Current.BindTo(bindable); + if (value?.Disabled ?? true) Alpha = 0.3f; } } - - public OptionTextBox() - { - OnChange += onChange; - } - - private void onChange(TextBox sender, bool newText) - { - if (bindable != null) - bindable.Value = Text; - } - - private void bindableValueChanged(string newValue) => Text = newValue; - - protected override void Dispose(bool isDisposing) - { - if (bindable != null) - bindable.ValueChanged -= bindableValueChanged; - base.Dispose(isDisposing); - } } } \ No newline at end of file diff --git a/osu.Game/Overlays/Options/Sections/Audio/AudioDevicesOptions.cs b/osu.Game/Overlays/Options/Sections/Audio/AudioDevicesOptions.cs index 2818f5fbd7..e3033b876d 100644 --- a/osu.Game/Overlays/Options/Sections/Audio/AudioDevicesOptions.cs +++ b/osu.Game/Overlays/Options/Sections/Audio/AudioDevicesOptions.cs @@ -39,7 +39,13 @@ namespace osu.Game.Overlays.Options.Sections.Audio if (deviceItems.All(kv => kv.Value != preferredDeviceName)) deviceItems.Add(new KeyValuePair(preferredDeviceName, preferredDeviceName)); - dropdown.Items = deviceItems; + // The option dropdown for audio device selection lists all audio + // device names. Dropdowns, however, may not have multiple identical + // keys. Thus, we remove duplicate audio device names from + // the dropdown. BASS does not give us a simple mechanism to select + // specific audio devices in such a case anyways. Such + // functionality would require involved OS-specific code. + dropdown.Items = deviceItems.Distinct().ToList(); } private void onDeviceChanged(string name) => updateItems(); diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index e5fb1db38e..29f6cb2967 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -194,8 +194,6 @@ namespace osu.Game.Screens.Menu return true; } - protected override bool OnDragStart(InputState state) => true; - protected override bool OnClick(InputState state) { if (!Interactive) return false; diff --git a/osu.Game/Screens/Play/FailOverlay.cs b/osu.Game/Screens/Play/FailOverlay.cs index 7a32e19338..faff687ddb 100644 --- a/osu.Game/Screens/Play/FailOverlay.cs +++ b/osu.Game/Screens/Play/FailOverlay.cs @@ -1,31 +1,19 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Graphics.Containers; using osu.Framework.Input; using OpenTK.Input; using osu.Game.Graphics; using OpenTK.Graphics; using osu.Framework.Allocation; +using System.Linq; namespace osu.Game.Screens.Play { public class FailOverlay : MenuOverlay { - public override string Header => "failed"; public override string Description => "you're dead, try again?"; - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) - { - if (args.Key == Key.Escape) - { - if (State == Visibility.Hidden) return false; - OnQuit(); - return true; - } - - return base.OnKeyDown(state, args); - } [BackgroundDependencyLoader] private void load(OsuColour colours) @@ -33,5 +21,16 @@ namespace osu.Game.Screens.Play AddButton("Retry", colours.YellowDark, OnRetry); AddButton("Quit", new Color4(170, 27, 39, 255), OnQuit); } + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + if (!args.Repeat && args.Key == Key.Escape) + { + Buttons.Children.Last().TriggerClick(); + return true; + } + + return base.OnKeyDown(state, args); + } } } diff --git a/osu.Game/Screens/Play/HotkeyRetryOverlay.cs b/osu.Game/Screens/Play/HotkeyRetryOverlay.cs new file mode 100644 index 0000000000..16062bebe5 --- /dev/null +++ b/osu.Game/Screens/Play/HotkeyRetryOverlay.cs @@ -0,0 +1,82 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Input; +using OpenTK.Input; +using osu.Framework.Allocation; +using osu.Framework.Audio.Sample; +using osu.Framework.Audio; +using System; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using OpenTK.Graphics; + +namespace osu.Game.Screens.Play +{ + public class HotkeyRetryOverlay : Container + { + public Action Action; + + private SampleChannel retrySample; + private Box overlay; + + private const int activate_delay = 400; + private const int fadeout_delay = 200; + + private bool fired; + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + retrySample = audio.Sample.Get(@"Menu/menuback"); + RelativeSizeAxes = Axes.Both; + AlwaysPresent = true; + + Children = new Drawable[] + { + overlay = new Box + { + Alpha = 0, + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + } + }; + } + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + if (args.Repeat) return false; + + if (args.Key == Key.Tilde) + { + overlay.FadeIn(activate_delay, EasingTypes.Out); + return true; + } + + return base.OnKeyDown(state, args); + } + + protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) + { + if (args.Key == Key.Tilde && !fired) + { + overlay.FadeOut(fadeout_delay, EasingTypes.Out); + return true; + } + + return base.OnKeyUp(state, args); + } + + protected override void Update() + { + base.Update(); + if (!fired && overlay.Alpha == 1) + { + fired = true; + retrySample.Play(); + Action?.Invoke(); + } + } + } +} diff --git a/osu.Game/Screens/Play/MenuOverlay.cs b/osu.Game/Screens/Play/MenuOverlay.cs index ede49065a7..738e5cc35d 100644 --- a/osu.Game/Screens/Play/MenuOverlay.cs +++ b/osu.Game/Screens/Play/MenuOverlay.cs @@ -13,10 +13,11 @@ using OpenTK; using OpenTK.Graphics; using osu.Game.Graphics; using osu.Framework.Allocation; +using osu.Game.Graphics.UserInterface; namespace osu.Game.Screens.Play { - public abstract class MenuOverlay : OverlayContainer + public abstract class MenuOverlay : OverlayContainer, IRequireHighFrequencyMousePosition { private const int transition_duration = 200; private const int button_height = 70; @@ -30,7 +31,7 @@ namespace osu.Game.Screens.Play public abstract string Header { get; } public abstract string Description { get; } - private FillFlowContainer buttons; + protected FillFlowContainer Buttons; public int Retries { @@ -80,11 +81,13 @@ namespace osu.Game.Screens.Play // Don't let mouse down events through the overlay or people can click circles while paused. protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) => true; + protected override bool OnMouseMove(InputState state) => true; protected void AddButton(string text, Color4 colour, Action action) { - buttons.Add(new PauseButton + Buttons.Add(new PauseButton { Text = text, ButtonColour = colour, @@ -151,7 +154,7 @@ namespace osu.Game.Screens.Play } } }, - buttons = new FillFlowContainer + Buttons = new FillFlowContainer { Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index f9706d263e..9561979751 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -2,10 +2,10 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Linq; using osu.Framework.Input; using osu.Game.Graphics; using OpenTK.Input; -using osu.Framework.Graphics.Containers; using OpenTK.Graphics; using osu.Framework.Allocation; @@ -20,10 +20,9 @@ namespace osu.Game.Screens.Play protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) { - if (args.Key == Key.Escape) + if (!args.Repeat && args.Key == Key.Escape) { - if (State == Visibility.Hidden) return false; - OnResume(); + Buttons.Children.First().TriggerClick(); return true; } @@ -39,4 +38,3 @@ namespace osu.Game.Screens.Play } } } - \ No newline at end of file diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 9b3ca0df26..570c1831c0 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -20,6 +20,7 @@ using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Ranking; using System; using System.Linq; +using osu.Framework.Threading; using osu.Game.Modes.Scoring; namespace osu.Game.Screens.Play @@ -115,7 +116,12 @@ namespace osu.Game.Screens.Play scoreProcessor = HitRenderer.CreateScoreProcessor(); - hudOverlay = new StandardHudOverlay(); + hudOverlay = new StandardHudOverlay() + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }; + hudOverlay.KeyCounter.Add(ruleset.CreateGameplayKeys()); hudOverlay.BindProcessor(scoreProcessor); hudOverlay.BindHitRenderer(HitRenderer); @@ -157,6 +163,15 @@ namespace osu.Game.Screens.Play { OnRetry = Restart, OnQuit = Exit, + }, + new HotkeyRetryOverlay + { + Action = () => { + //we want to hide the hitrenderer immediately (looks better). + //we may be able to remove this once the mouse cursor trail is improved. + HitRenderer?.Hide(); + Restart(); + }, } }; } @@ -247,14 +262,16 @@ namespace osu.Game.Screens.Play }); } + private ScheduledDelegate onCompletionEvent; + private void onCompletion() { // Only show the completion screen if the player hasn't failed - if (scoreProcessor.HasFailed) + if (scoreProcessor.HasFailed || onCompletionEvent != null) return; Delay(1000); - Schedule(delegate + onCompletionEvent = Schedule(delegate { ValidForResume = false; Push(new Results @@ -299,41 +316,51 @@ namespace osu.Game.Screens.Play sourceClock.Start(); initializeSkipButton(); }); + + //keep in mind this is using the interpolatedSourceClock so won't be run as early as we may expect. + HitRenderer.Alpha = 0; + HitRenderer.FadeIn(750, EasingTypes.OutQuint); } protected override void OnSuspending(Screen next) { - Content.FadeOut(350); - Content.ScaleTo(0.7f, 750, EasingTypes.InQuint); + fadeOut(); base.OnSuspending(next); } protected override bool OnExiting(Screen next) { - if (pauseOverlay == null) return false; - - if (HitRenderer.HasReplayLoaded) - return false; - - if (pauseOverlay.State != Visibility.Visible && !canPause) return true; - - if (!IsPaused && sourceClock.IsRunning) // For if the user presses escape quickly when entering the map + if (pauseOverlay != null && !HitRenderer.HasReplayLoaded) { - Pause(); - return true; - } - else - { - FadeOut(250); - Content.ScaleTo(0.7f, 750, EasingTypes.InQuint); - Background?.FadeTo(1f, 200); - return base.OnExiting(next); + //pause screen override logic. + if (pauseOverlay?.State == Visibility.Hidden && !canPause) return true; + + if (!IsPaused && sourceClock.IsRunning) // For if the user presses escape quickly when entering the map + { + Pause(); + return true; + } } + + fadeOut(); + return base.OnExiting(next); + } + + private void fadeOut() + { + const float fade_out_duration = 250; + + HitRenderer?.FadeOut(fade_out_duration); + Content.FadeOut(fade_out_duration); + + hudOverlay.ScaleTo(0.7f, fade_out_duration * 3, EasingTypes.In); + + Background?.FadeTo(1f, fade_out_duration); } private Bindable mouseWheelDisabled; protected override bool OnWheel(InputState state) => mouseWheelDisabled.Value && !IsPaused; } -} \ No newline at end of file +} diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs index dae909f2b7..ae117254fa 100644 --- a/osu.Game/Screens/Select/BeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs @@ -1,13 +1,10 @@ // 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.Primitives; using osu.Game.Beatmaps; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests; using osu.Game.Screens.Select.Leaderboards; namespace osu.Game.Screens.Select @@ -17,11 +14,9 @@ namespace osu.Game.Screens.Select private readonly Container content; protected override Container Content => content; - public readonly Container Details; //todo: replace with a real details view when added + public readonly BeatmapDetails Details; public readonly Leaderboard Leaderboard; - private APIAccess api; - private WorkingBeatmap beatmap; public WorkingBeatmap Beatmap { @@ -32,7 +27,8 @@ namespace osu.Game.Screens.Select set { beatmap = value; - if (IsLoaded) Schedule(updateScores); + Leaderboard.Beatmap = beatmap?.BeatmapInfo; + Details.Beatmap = beatmap?.Beatmap.BeatmapInfo; } } @@ -51,14 +47,12 @@ namespace osu.Game.Screens.Select Details.Show(); Leaderboard.Hide(); break; + default: Details.Hide(); Leaderboard.Show(); break; } - - //for now let's always update scores. - updateScores(); }, }, content = new Container @@ -70,42 +64,18 @@ namespace osu.Game.Screens.Select Add(new Drawable[] { - Details = new Container + Details = new BeatmapDetails { RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(5), + Alpha = 0, }, Leaderboard = new Leaderboard { RelativeSizeAxes = Axes.Both, + } }); } - - protected override void LoadComplete() - { - base.LoadComplete(); - updateScores(); - } - - [BackgroundDependencyLoader(permitNulls: true)] - private void load(APIAccess api) - { - this.api = api; - } - - private GetScoresRequest getScoresRequest; - private void updateScores() - { - if (!IsLoaded) return; - - Leaderboard.Scores = null; - getScoresRequest?.Cancel(); - - if (api == null || beatmap?.BeatmapInfo == null || !Leaderboard.IsPresent) return; - - getScoresRequest = new GetScoresRequest(beatmap.BeatmapInfo); - getScoresRequest.Success += r => Leaderboard.Scores = r.Scores; - api.Queue(getScoresRequest); - } } -} +} \ No newline at end of file diff --git a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs index c52d0397ed..48a46f0b90 100644 --- a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs +++ b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs @@ -8,7 +8,6 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; @@ -24,7 +23,7 @@ namespace osu.Game.Screens.Select private void invokeOnFilter() { - OnFilter?.Invoke(tabs.SelectedItem, modsCheckbox.State == CheckboxState.Checked); + OnFilter?.Invoke(tabs.Current, modsCheckbox.Current); } [BackgroundDependencyLoader] @@ -61,10 +60,10 @@ namespace osu.Game.Screens.Select }, }; - tabs.SelectedItem.ValueChanged += item => invokeOnFilter(); - modsCheckbox.Action += (sender, e) => invokeOnFilter(); + tabs.Current.ValueChanged += item => invokeOnFilter(); + modsCheckbox.Current.ValueChanged += item => invokeOnFilter(); - tabs.SelectedItem.Value = BeatmapDetailTab.Global; + tabs.Current.Value = BeatmapDetailTab.Global; } } diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs new file mode 100644 index 0000000000..a0d15101e0 --- /dev/null +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -0,0 +1,434 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Sprites; +using osu.Game.Database; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using System.Globalization; +using System.Linq; + +namespace osu.Game.Screens.Select +{ + public class BeatmapDetails : Container + { + private readonly MetadataSegment description; + private readonly MetadataSegment source; + private readonly MetadataSegment tags; + + private readonly DifficultyRow circleSize; + private readonly DifficultyRow drainRate; + private readonly DifficultyRow overallDifficulty; + private readonly DifficultyRow approachRate; + private readonly DifficultyRow stars; + + 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 BeatmapInfo beatmap; + public BeatmapInfo Beatmap + { + get + { + return beatmap; + } + set + { + beatmap = value; + if (beatmap == null) return; + + description.Text = beatmap.Version; + source.Text = beatmap.Metadata.Source; + tags.Text = beatmap.Metadata.Tags; + + 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; + + if (beatmap.Metrics?.Ratings.Any() ?? false) + { + var ratings = beatmap.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); + } + else + ratingsContainer.Hide(); + + if ((beatmap.Metrics?.Retries.Any() ?? false) && beatmap.Metrics.Fails.Any()) + { + var retries = beatmap.Metrics.Retries; + var fails = beatmap.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)); + } + else + retryFailContainer.Hide(); + } + } + + public BeatmapDetails() + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.5f, + }, + new FillFlowContainer() + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.4f, + Direction = FillDirection.Vertical, + LayoutDuration = 200, + LayoutEasing = EasingTypes.OutQuint, + Padding = new MarginPadding(10) { Top = 25 }, + 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,10), + Padding = new MarginPadding(15) { Top = 25 }, + 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 Diffculty"), + }, + }, + }, + }, + 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, + }, + }, + }, + } + }, + }, + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colour) + { + description.AccentColour = colour.GrayB; + source.AccentColour = colour.GrayB; + tags.AccentColour = colour.YellowLight; + + stars.AccentColour = colour.Yellow; + + ratingsBar.BackgroundColour = colour.Green; + ratingsBar.AccentColour = colour.YellowDark; + ratingsGraph.Colour = colour.BlueDark; + + failGraph.Colour = colour.YellowDarker; + retryGraph.Colour = colour.Yellow; + } + + private class DifficultyRow : Container, IHasAccentColour + { + private readonly OsuSpriteText name; + private readonly Bar bar; + private readonly OsuSpriteText valueText; + + private readonly float maxValue; + + private float difficultyValue; + public float Value + { + get + { + return difficultyValue; + } + set + { + difficultyValue = value; + bar.Length = value / maxValue; + valueText.Text = value.ToString(CultureInfo.InvariantCulture); + } + } + + public Color4 AccentColour + { + get + { + return bar.AccentColour; + } + set + { + bar.AccentColour = value; + } + } + + public DifficultyRow(string difficultyName, float maxValue = 10) + { + this.maxValue = maxValue; + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Children = new Drawable[] + { + name = new OsuSpriteText + { + Font = @"Exo2.0-Regular", + Text = difficultyName, + }, + bar = new Bar + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(1, 0.35f), + Padding = new MarginPadding { Left = 100, Right = 25 }, + }, + valueText = new OsuSpriteText + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Font = @"Exo2.0-Regular", + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colour) + { + name.Colour = colour.GrayB; + bar.BackgroundColour = colour.Gray7; + valueText.Colour = colour.GrayB; + } + } + + private class MetadataSegment : Container, IHasAccentColour + { + private readonly OsuSpriteText header; + private readonly FillFlowContainer content; + + public string Text + { + set + { + if (string.IsNullOrEmpty(value)) + Hide(); + else + { + Show(); + if (header.Text == "Tags") + content.Children = value.Split(' ').Select(text => new OsuSpriteText + { + Text = text, + Font = "Exo2.0-Regular", + }); + else + content.Children = new[] + { + new OsuSpriteText + { + Text = value, + Font = "Exo2.0-Regular", + } + }; + } + } + } + + public Color4 AccentColour + { + get + { + return content.Colour; + } + set + { + content.Colour = value; + } + } + + public MetadataSegment(string headerText) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Margin = new MarginPadding { Top = 10 }; + Children = new Drawable[] + { + header = new OsuSpriteText + { + Font = @"Exo2.0-Bold", + Text = headerText, + }, + content = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + Spacing = new Vector2(5,0), + Margin = new MarginPadding { Top = header.TextSize } + } + }; + } + } + } +} \ No newline at end of file diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 6d92b35993..7596af1484 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -93,11 +93,6 @@ namespace osu.Game.Screens.Select searchTextBox = new SearchTextBox { RelativeSizeAxes = Axes.X, - OnChange = (sender, newText) => - { - if (newText) - FilterChanged?.Invoke(CreateCriteria()); - }, Exit = () => Exit?.Invoke(), }, new Box @@ -149,10 +144,12 @@ namespace osu.Game.Screens.Select } }; + searchTextBox.Current.ValueChanged += t => FilterChanged?.Invoke(CreateCriteria()); + groupTabs.PinItem(GroupMode.All); groupTabs.PinItem(GroupMode.RecentlyPlayed); - groupTabs.SelectedItem.ValueChanged += val => Group = val; - sortTabs.SelectedItem.ValueChanged += val => Sort = val; + groupTabs.Current.ValueChanged += val => Group = val; + sortTabs.Current.ValueChanged += val => Sort = val; } public void Deactivate() diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index acf0954418..2654129a44 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -31,7 +31,9 @@ namespace osu.Game.Screens.Select || (set.Metadata.Artist ?? string.Empty).IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) != -1 || (set.Metadata.ArtistUnicode ?? string.Empty).IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) != -1 || (set.Metadata.Title ?? string.Empty).IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) != -1 - || (set.Metadata.TitleUnicode ?? string.Empty).IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) != -1; + || (set.Metadata.TitleUnicode ?? string.Empty).IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) != -1 + || (set.Metadata.Tags ?? string.Empty).IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) != -1 + || (set.Metadata.Source ?? string.Empty).IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) != -1; switch (g.State) { diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 12ff096d16..315611a60c 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -10,7 +10,11 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using System; +using osu.Framework.Allocation; +using osu.Game.Database; using osu.Game.Modes.Scoring; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; namespace osu.Game.Screens.Select.Leaderboards { @@ -26,6 +30,7 @@ namespace osu.Game.Screens.Select.Leaderboards set { scores = value; + getScoresRequest?.Cancel(); int i = 150; if (scores == null) @@ -81,6 +86,41 @@ namespace osu.Game.Screens.Select.Leaderboards }; } + private APIAccess api; + + private BeatmapInfo beatmap; + + public BeatmapInfo Beatmap + { + get { return beatmap; } + set + { + beatmap = value; + Schedule(updateScores); + } + } + + [BackgroundDependencyLoader(permitNulls: true)] + private void load(APIAccess api) + { + this.api = api; + } + + private GetScoresRequest getScoresRequest; + private void updateScores() + { + if (!IsLoaded) return; + + Scores = null; + getScoresRequest?.Cancel(); + + if (api == null || Beatmap == null) return; + + getScoresRequest = new GetScoresRequest(Beatmap); + getScoresRequest.Success += r => Scores = r.Scores; + api.Queue(getScoresRequest); + } + protected override void Update() { base.Update(); diff --git a/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs b/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs index 2bac387c5c..493f351b75 100644 --- a/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs @@ -142,7 +142,7 @@ namespace osu.Game.Screens.Select.Leaderboards Children = new Drawable[] { avatar = new DelayedLoadWrapper( - new Avatar(Score.User ?? new User { Id = Score.UserID }) + new Avatar(Score.User) { RelativeSizeAxes = Axes.Both, CornerRadius = corner_radius, @@ -169,7 +169,7 @@ namespace osu.Game.Screens.Select.Leaderboards { nameLabel = new OsuSpriteText { - Text = Score.User?.Username ?? Score.Username, + Text = Score.User.Username, Font = @"Exo2.0-BoldItalic", TextSize = 23, }, diff --git a/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs b/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs index b80f76d281..08f270741c 100644 --- a/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs +++ b/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs @@ -298,7 +298,7 @@ namespace osu.Game.Screens.Tournament private void speedTo(float value, double duration = 0, EasingTypes easing = EasingTypes.None) { DelayReset(); - TransformTo(speed, value, duration, easing, new TransformScrollSpeed()); + TransformTo(() => speed, value, duration, easing, new TransformScrollSpeed()); } private enum ScrollState diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 35c3575071..aa2926b4e5 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,17 +35,14 @@ false - - $(SolutionDir)\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll - True + + $(SolutionDir)\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll - $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1340\lib\net45\OpenTK.dll - True + $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1341\lib\net45\OpenTK.dll - - ..\packages\SharpCompress.0.15.1\lib\net45\SharpCompress.dll - True + + $(SolutionDir)\packages\SharpCompress.0.15.2\lib\net45\SharpCompress.dll $(SolutionDir)\packages\SQLite.Net.Core-PCL.3.1.1\lib\portable-win8+net45+wp8+wpa81+MonoAndroid1+MonoTouch1\SQLite.Net.dll @@ -79,6 +76,7 @@ + @@ -87,6 +85,7 @@ + @@ -180,6 +179,7 @@ + @@ -204,6 +204,8 @@ + + @@ -391,7 +393,7 @@ - - - - + + +