diff --git a/.vscode/launch.json b/.vscode/launch.json index 0e07b0a067..b3b86da42f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,46 +2,60 @@ "version": "0.2.0", "configurations": [ { - "name": "Launch VisualTests", + "name": "VisualTests (debug)", "windows": { "type": "clr" }, "type": "mono", "request": "launch", "program": "${workspaceRoot}/osu.Desktop.VisualTests/bin/Debug/osu!.exe", - "args": [], "cwd": "${workspaceRoot}", - "preLaunchTask": "build", + "preLaunchTask": "Build (Debug)", "runtimeExecutable": null, "env": {}, "console": "internalConsole" }, { - "name": "Launch Desktop", + "name": "VisualTests (release)", + "windows": { + "type": "clr" + }, + "type": "mono", + "request": "launch", + "program": "${workspaceRoot}/osu.Desktop.VisualTests/bin/Release/osu!.exe", + "cwd": "${workspaceRoot}", + "preLaunchTask": "Build (Release)", + "runtimeExecutable": null, + "env": {}, + "console": "internalConsole" + }, + { + "name": "osu! (debug)", "windows": { "type": "clr" }, "type": "mono", "request": "launch", "program": "${workspaceRoot}/osu.Desktop/bin/Debug/osu!.exe", - "args": [], "cwd": "${workspaceRoot}", - "preLaunchTask": "build", + "preLaunchTask": "Build (Debug)", "runtimeExecutable": null, "env": {}, "console": "internalConsole" }, { - "name": "Attach", + "name": "osu! (release)", "windows": { - "type": "clr", - "request": "attach", - "processName": "osu!" + "type": "clr" }, "type": "mono", - "request": "attach", - "address": "localhost", - "port": 55555 + "request": "launch", + "program": "${workspaceRoot}/osu.Desktop/bin/Release/osu!.exe", + "cwd": "${workspaceRoot}", + "preLaunchTask": "Build (Release)", + "runtimeExecutable": null, + "env": {}, + "console": "internalConsole" } ] } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 5eaeaa9899..f285ebde67 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,51 +1,50 @@ { // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format - "version": "0.1.0", - "taskSelector": "/t:", + "version": "2.0.0", + "problemMatcher": "$msCompile", + "isShellCommand": true, + "command": "msbuild", + "suppressTaskName": true, + "showOutput": "silent", + "args": [ + "/property:GenerateFullPaths=true", + "/property:DebugType=portable" + ], + "windows": { + "args": [ + "/property:GenerateFullPaths=true", + "/property:DebugType=portable", + "/m" //parallel compiling support. doesn't work well with mono atm + ] + }, "tasks": [ { - "taskName": "build", - "isShellCommand": true, - "showOutput": "silent", - "command": "msbuild", - "args": [ - "/property:GenerateFullPaths=true", - "/property:DebugType=portable" - ], - "windows": { - "args": [ - "/property:GenerateFullPaths=true", - "/property:DebugType=portable", - "/m" //parallel compiling support. doesn't work well with mono atm - ] - }, - // Use the standard MS compiler pattern to detect errors, warnings and infos - "problemMatcher": "$msCompile", + "taskName": "Build (Debug)", "isBuildCommand": true }, { - "taskName": "rebuild", - "isShellCommand": true, - "showOutput": "silent", - "command": "msbuild", + "taskName": "Build (Release)", "args": [ - // Ask msbuild to generate full paths for file names. - "/property:GenerateFullPaths=true", - "/property:DebugType=portable", - "/target:Clean,Build" - ], - "windows": { - "args": [ - "/property:GenerateFullPaths=true", - "/property:DebugType=portable", - "/target:Clean,Build", - "/m" //parallel compiling support. doesn't work well with mono atm - ] - }, - // Use the standard MS compiler pattern to detect errors, warnings and infos - "problemMatcher": "$msCompile", - "isBuildCommand": true + "/property:Configuration=Release" + ] + }, + { + "taskName": "Clean All", + "dependsOn": ["Clean (Debug)", "Clean (Release)"] + }, + { + "taskName": "Clean (Debug)", + "args": [ + "/target:Clean" + ] + }, + { + "taskName": "Clean (Release)", + "args": [ + "/target:Clean", + "/property:Configuration=Release" + ] } ] } \ No newline at end of file diff --git a/osu-framework b/osu-framework index cebdfb1bbb..e1ac6316aa 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit cebdfb1bbb260e5aaca0a01e06d7128b3d1faae4 +Subproject commit e1ac6316aa3862efb8e79e585a7b4c901a4e1b3c diff --git a/osu.Desktop.VisualTests/Beatmaps/TestWorkingBeatmap.cs b/osu.Desktop.VisualTests/Beatmaps/TestWorkingBeatmap.cs index 5e3f5b5133..b45574b761 100644 --- a/osu.Desktop.VisualTests/Beatmaps/TestWorkingBeatmap.cs +++ b/osu.Desktop.VisualTests/Beatmaps/TestWorkingBeatmap.cs @@ -10,7 +10,7 @@ namespace osu.Desktop.VisualTests.Beatmaps public class TestWorkingBeatmap : WorkingBeatmap { public TestWorkingBeatmap(Beatmap beatmap) - : base(beatmap.BeatmapInfo, beatmap.BeatmapInfo.BeatmapSet) + : base(beatmap.BeatmapInfo) { this.beatmap = beatmap; } diff --git a/osu.Desktop.VisualTests/Tests/TestCaseManiaHitObjects.cs b/osu.Desktop.VisualTests/Tests/TestCaseManiaHitObjects.cs new file mode 100644 index 0000000000..3ad83beb73 --- /dev/null +++ b/osu.Desktop.VisualTests/Tests/TestCaseManiaHitObjects.cs @@ -0,0 +1,89 @@ +// 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.Containers; +using osu.Framework.Testing; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using OpenTK.Graphics; +using OpenTK; + +namespace osu.Desktop.VisualTests.Tests +{ + internal class TestCaseManiaHitObjects : TestCase + { + public override void Reset() + { + base.Reset(); + + Add(new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + // Imagine that the containers containing the drawable notes are the "columns" + Children = new Drawable[] + { + new Container + { + Name = "Normal note column", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Width = 50, + Children = new[] + { + new Container + { + Name = "Timing section", + RelativeSizeAxes = Axes.Both, + RelativeCoordinateSpace = new Vector2(1, 10000), + Children = new[] + { + new DrawableNote(new Note + { + StartTime = 5000 + }) + { + AccentColour = Color4.Red + } + } + } + } + }, + new Container + { + Name = "Hold note column", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Width = 50, + Children = new[] + { + new Container + { + Name = "Timing section", + RelativeSizeAxes = Axes.Both, + RelativeCoordinateSpace = new Vector2(1, 10000), + Children = new[] + { + new DrawableHoldNote(new HoldNote + { + StartTime = 5000, + Duration = 1000 + }) + { + AccentColour = Color4.Red + } + } + } + } + } + } + }); + } + } +} diff --git a/osu.Desktop.VisualTests/Tests/TestCaseModSelectOverlay.cs b/osu.Desktop.VisualTests/Tests/TestCaseMods.cs similarity index 65% rename from osu.Desktop.VisualTests/Tests/TestCaseModSelectOverlay.cs rename to osu.Desktop.VisualTests/Tests/TestCaseMods.cs index d1c137191f..3f3a9d82f5 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseModSelectOverlay.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseMods.cs @@ -6,16 +6,21 @@ using osu.Framework.Graphics; using osu.Game.Overlays.Mods; using osu.Framework.Testing; using osu.Game.Database; +using osu.Game.Screens.Play.HUD; +using OpenTK; namespace osu.Desktop.VisualTests.Tests { - internal class TestCaseModSelectOverlay : TestCase + internal class TestCaseMods : TestCase { - public override string Description => @"Tests the mod select overlay"; + public override string Description => @"Mod select overlay and in-game display"; private ModSelectOverlay modSelect; + private ModDisplay modDisplay; + private RulesetDatabase rulesets; + [BackgroundDependencyLoader] private void load(RulesetDatabase rulesets) { @@ -33,6 +38,16 @@ namespace osu.Desktop.VisualTests.Tests Anchor = Anchor.BottomCentre, }); + Add(modDisplay = new ModDisplay + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Position = new Vector2(0, 25), + }); + + modDisplay.Current.BindTo(modSelect.SelectedMods); + AddStep("Toggle", modSelect.ToggleVisibility); foreach (var ruleset in rulesets.AllRulesets) diff --git a/osu.Desktop.VisualTests/Tests/TestCaseScoreCounter.cs b/osu.Desktop.VisualTests/Tests/TestCaseScoreCounter.cs index d8dac63980..cfa66f12ed 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseScoreCounter.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseScoreCounter.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.MathUtils; using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play.HUD; namespace osu.Desktop.VisualTests.Tests { diff --git a/osu.Desktop.VisualTests/Tests/TestCaseSongProgress.cs b/osu.Desktop.VisualTests/Tests/TestCaseSongProgress.cs index 6d8aac1d09..e3c343f5f8 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseSongProgress.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseSongProgress.cs @@ -18,10 +18,14 @@ namespace osu.Desktop.VisualTests.Tests private SongProgress progress; private SongProgressGraph graph; + private StopwatchClock clock; + public override void Reset() { base.Reset(); + clock = new StopwatchClock(true); + Add(progress = new SongProgress { RelativeSizeAxes = Axes.X, @@ -55,6 +59,9 @@ namespace osu.Desktop.VisualTests.Tests progress.Objects = objects; graph.Objects = objects; + + progress.AudioClock = clock; + progress.OnSeek = pos => clock.Seek(pos); } } } diff --git a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj index 6a5d082aa3..66cad48964 100644 --- a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj +++ b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj @@ -190,6 +190,7 @@ + @@ -212,7 +213,7 @@ - + diff --git a/osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs b/osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs index abc45d82ec..8c896646bf 100644 --- a/osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs +++ b/osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs @@ -14,7 +14,7 @@ namespace osu.Desktop.Beatmaps.IO { public static void Register() => AddReader((storage, path) => Directory.Exists(path)); - private string basePath { get; } + private readonly string basePath; public LegacyFilesystemReader(string path) { diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 847af965cc..10bebbdba7 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -11,11 +11,11 @@ using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Mania.Beatmaps { - internal class ManiaBeatmapConverter : BeatmapConverter + internal class ManiaBeatmapConverter : BeatmapConverter { protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) }; - protected override IEnumerable ConvertHitObject(HitObject original, Beatmap beatmap) + protected override IEnumerable ConvertHitObject(HitObject original, Beatmap beatmap) { yield return null; } diff --git a/osu.Game.Rulesets.Mania/Judgements/HitWindows.cs b/osu.Game.Rulesets.Mania/Judgements/HitWindows.cs new file mode 100644 index 0000000000..2a0ce88506 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Judgements/HitWindows.cs @@ -0,0 +1,179 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Database; + +namespace osu.Game.Rulesets.Mania.Judgements +{ + public class HitWindows + { + #region Constants + + /// + /// PERFECT hit window at OD = 10. + /// + private const double perfect_min = 27.8; + /// + /// PERFECT hit window at OD = 5. + /// + private const double perfect_mid = 38.8; + /// + /// PERFECT hit window at OD = 0. + /// + private const double perfect_max = 44.8; + + /// + /// GREAT hit window at OD = 10. + /// + private const double great_min = 68; + /// + /// GREAT hit window at OD = 5. + /// + private const double great_mid = 98; + /// + /// GREAT hit window at OD = 0. + /// + private const double great_max = 128; + + /// + /// GOOD hit window at OD = 10. + /// + private const double good_min = 134; + /// + /// GOOD hit window at OD = 5. + /// + private const double good_mid = 164; + /// + /// GOOD hit window at OD = 0. + /// + private const double good_max = 194; + + /// + /// OK hit window at OD = 10. + /// + private const double ok_min = 194; + /// + /// OK hit window at OD = 5. + /// + private const double ok_mid = 224; + /// + /// OK hit window at OD = 0. + /// + private const double ok_max = 254; + + /// + /// BAD hit window at OD = 10. + /// + private const double bad_min = 242; + /// + /// BAD hit window at OD = 5. + /// + private const double bad_mid = 272; + /// + /// BAD hit window at OD = 0. + /// + private const double bad_max = 302; + + /// + /// MISS hit window at OD = 10. + /// + private const double miss_min = 316; + /// + /// MISS hit window at OD = 5. + /// + private const double miss_mid = 346; + /// + /// MISS hit window at OD = 0. + /// + private const double miss_max = 376; + + #endregion + + /// + /// Hit window for a PERFECT hit. + /// + public double Perfect = perfect_mid; + + /// + /// Hit window for a GREAT hit. + /// + public double Great = great_mid; + + /// + /// Hit window for a GOOD hit. + /// + public double Good = good_mid; + + /// + /// Hit window for an OK hit. + /// + public double Ok = ok_mid; + + /// + /// Hit window for a BAD hit. + /// + public double Bad = bad_mid; + + /// + /// Hit window for a MISS hit. + /// + public double Miss = miss_mid; + + /// + /// Constructs default hit windows. + /// + public HitWindows() + { + } + + /// + /// Constructs hit windows by fitting a parameter to a 2-part piecewise linear function for each hit window. + /// + /// The parameter. + public HitWindows(double difficulty) + { + Perfect = BeatmapDifficulty.DifficultyRange(difficulty, perfect_max, perfect_mid, perfect_min); + Great = BeatmapDifficulty.DifficultyRange(difficulty, great_max, great_mid, great_min); + Good = BeatmapDifficulty.DifficultyRange(difficulty, good_max, good_mid, good_min); + Ok = BeatmapDifficulty.DifficultyRange(difficulty, ok_max, ok_mid, ok_min); + Bad = BeatmapDifficulty.DifficultyRange(difficulty, bad_max, bad_mid, bad_min); + Miss = BeatmapDifficulty.DifficultyRange(difficulty, miss_max, miss_mid, miss_min); + } + + /// + /// Constructs new hit windows which have been multiplied by a value. + /// + /// The original hit windows. + /// The value to multiply each hit window by. + public static HitWindows operator *(HitWindows windows, double value) + { + return new HitWindows + { + Perfect = windows.Perfect * value, + Great = windows.Great * value, + Good = windows.Good * value, + Ok = windows.Ok * value, + Bad = windows.Bad * value, + Miss = windows.Miss * value + }; + } + + /// + /// Constructs new hit windows which have been divided by a value. + /// + /// The original hit windows. + /// The value to divide each hit window by. + public static HitWindows operator /(HitWindows windows, double value) + { + return new HitWindows + { + Perfect = windows.Perfect / value, + Great = windows.Great / value, + Good = windows.Good / value, + Ok = windows.Ok / value, + Bad = windows.Bad / value, + Miss = windows.Miss / value + }; + } + } +} diff --git a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs index e9bcc60d2c..aaba4d94f0 100644 --- a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs @@ -9,7 +9,7 @@ using System.Collections.Generic; namespace osu.Game.Rulesets.Mania { - public class ManiaDifficultyCalculator : DifficultyCalculator + public class ManiaDifficultyCalculator : DifficultyCalculator { public ManiaDifficultyCalculator(Beatmap beatmap) : base(beatmap) @@ -21,6 +21,6 @@ namespace osu.Game.Rulesets.Mania return 0; } - protected override BeatmapConverter CreateBeatmapConverter() => new ManiaBeatmapConverter(); + protected override BeatmapConverter CreateBeatmapConverter() => new ManiaBeatmapConverter(); } } \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaMod.cs b/osu.Game.Rulesets.Mania/Mods/ManiaMod.cs index 68458caeac..b402d3a010 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaMod.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaMod.cs @@ -64,6 +64,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override string Name => "FadeIn"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_hidden; + public override ModType Type => ModType.DifficultyIncrease; public override double ScoreMultiplier => 1; public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight) }; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawable/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawable/DrawableNote.cs deleted file mode 100644 index 07a27b1643..0000000000 --- a/osu.Game.Rulesets.Mania/Objects/Drawable/DrawableNote.cs +++ /dev/null @@ -1,36 +0,0 @@ -// 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.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Framework.Graphics.Transforms; -using osu.Framework.Graphics; -using OpenTK; - -namespace osu.Game.Rulesets.Mania.Objects.Drawable -{ - public class DrawableNote : Sprite - { - private readonly ManiaBaseHit note; - - public DrawableNote(ManiaBaseHit note) - { - this.note = note; - Origin = Anchor.Centre; - Scale = new Vector2(0.1f); - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - Texture = textures.Get(@"Menu/logo"); - - const double duration = 0; - - Transforms.Add(new TransformPositionY { StartTime = note.StartTime - 200, EndTime = note.StartTime, StartValue = -0.1f, EndValue = 0.9f }); - Transforms.Add(new TransformAlpha { StartTime = note.StartTime + duration + 200, EndTime = note.StartTime + duration + 400, StartValue = 1, EndValue = 0 }); - Expire(true); - } - } -} diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs new file mode 100644 index 0000000000..61dc2638a6 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -0,0 +1,69 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Drawables; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Objects.Drawables +{ + public class DrawableHoldNote : DrawableManiaHitObject + { + private readonly NotePiece headPiece; + private readonly BodyPiece bodyPiece; + private readonly NotePiece tailPiece; + + public DrawableHoldNote(HoldNote hitObject) + : base(hitObject) + { + RelativeSizeAxes = Axes.Both; + Height = (float)HitObject.Duration; + + Add(new Drawable[] + { + // For now the body piece covers the entire height of the container + // whereas possibly in the future we don't want to extend under the head/tail. + // This will be fixed when new designs are given or the current design is finalized. + bodyPiece = new BodyPiece + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + }, + headPiece = new NotePiece + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre + }, + tailPiece = new NotePiece + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + } + }); + + // The "length" of the hold note stops at the "base" of the tail piece + // but we want to contain the tail piece within our bounds + Height += (float)HitObject.Duration / headPiece.Height; + } + + public override Color4 AccentColour + { + get { return base.AccentColour; } + set + { + if (base.AccentColour == value) + return; + base.AccentColour = value; + + headPiece.AccentColour = value; + bodyPiece.AccentColour = value; + tailPiece.AccentColour = value; + } + } + + protected override void UpdateState(ArmedState state) + { + } + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs new file mode 100644 index 0000000000..29e4137bba --- /dev/null +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -0,0 +1,67 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Mania.Judgements; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Mania.Objects.Drawables +{ + public abstract class DrawableManiaHitObject : DrawableHitObject + where TObject : ManiaHitObject + { + public new TObject HitObject; + + private readonly Container glowContainer; + + protected DrawableManiaHitObject(TObject hitObject) + : base(hitObject) + { + HitObject = hitObject; + + Anchor = Anchor.TopCentre; + Origin = Anchor.BottomCentre; + + RelativePositionAxes = Axes.Y; + Y = (float)HitObject.StartTime; + + Add(glowContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + } + }); + } + + public override Color4 AccentColour + { + get { return base.AccentColour; } + set + { + if (base.AccentColour == value) + return; + base.AccentColour = value; + + glowContainer.EdgeEffect = new EdgeEffect + { + Type = EdgeEffectType.Glow, + Radius = 5, + Colour = value + }; + } + } + + protected override ManiaJudgement CreateJudgement() => new ManiaJudgement(); + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs new file mode 100644 index 0000000000..bc194b7703 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -0,0 +1,45 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Mania.Objects.Drawables +{ + public class DrawableNote : DrawableManiaHitObject + { + private readonly NotePiece headPiece; + + public DrawableNote(Note hitObject) + : base(hitObject) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Add(headPiece = new NotePiece + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre + }); + } + + public override Color4 AccentColour + { + get { return base.AccentColour; } + set + { + if (base.AccentColour == value) + return; + base.AccentColour = value; + + headPiece.AccentColour = value; + } + } + + protected override void UpdateState(ArmedState state) + { + } + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs new file mode 100644 index 0000000000..ce61a7a86f --- /dev/null +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs @@ -0,0 +1,48 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces +{ + /// + /// Represents length-wise portion of a hold note. + /// + internal class BodyPiece : Container, IHasAccentColour + { + private readonly Box box; + + public BodyPiece() + { + RelativeSizeAxes = Axes.Both; + Masking = true; + + Children = new[] + { + box = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.3f + } + }; + } + + private Color4 accentColour; + public Color4 AccentColour + { + get { return accentColour; } + set + { + if (accentColour == value) + return; + accentColour = value; + + box.Colour = accentColour; + } + } + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs new file mode 100644 index 0000000000..e01199e929 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs @@ -0,0 +1,59 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces +{ + /// + /// Represents the static hit markers of notes. + /// + internal class NotePiece : Container, IHasAccentColour + { + private const float head_height = 10; + private const float head_colour_height = 6; + + private readonly Box colouredBox; + + public NotePiece() + { + RelativeSizeAxes = Axes.X; + Height = head_height; + + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both + }, + colouredBox = new Box + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + Height = head_colour_height, + Alpha = 0.2f + } + }; + } + + private Color4 accentColour; + public Color4 AccentColour + { + get { return accentColour; } + set + { + if (accentColour == value) + return; + accentColour = value; + + colouredBox.Colour = AccentColour.Lighten(0.9f); + } + } + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index e8ce1da77f..a25b8fbf2a 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -1,9 +1,37 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Game.Beatmaps.Timing; +using osu.Game.Database; +using osu.Game.Rulesets.Mania.Judgements; +using osu.Game.Rulesets.Objects.Types; + namespace osu.Game.Rulesets.Mania.Objects { - public class HoldNote : Note + /// + /// Represents a hit object which requires pressing, holding, and releasing a key. + /// + public class HoldNote : Note, IHasEndTime { + /// + /// Lenience of release hit windows. This is to make cases where the hold note release + /// is timed alongside presses of other hit objects less awkward. + /// + private const double release_window_lenience = 1.5; + + public double Duration { get; set; } + public double EndTime => StartTime + Duration; + + /// + /// The key-release hit windows for this hold note. + /// + protected HitWindows ReleaseHitWindows = new HitWindows(); + + public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty) + { + base.ApplyDefaults(timing, difficulty); + + ReleaseHitWindows = HitWindows * release_window_lenience; + } } } diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaBaseHit.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs similarity index 60% rename from osu.Game.Rulesets.Mania/Objects/ManiaBaseHit.cs rename to osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs index 4c15b69eb7..93aaa94f45 100644 --- a/osu.Game.Rulesets.Mania/Objects/ManiaBaseHit.cs +++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs @@ -1,12 +1,13 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Game.Rulesets.Mania.Objects.Types; using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Mania.Objects { - public abstract class ManiaBaseHit : HitObject + public abstract class ManiaHitObject : HitObject, IHasColumn { - public int Column; + public int Column { get; set; } } } diff --git a/osu.Game.Rulesets.Mania/Objects/Note.cs b/osu.Game.Rulesets.Mania/Objects/Note.cs index 5a6d6003db..1d2e4169b5 100644 --- a/osu.Game.Rulesets.Mania/Objects/Note.cs +++ b/osu.Game.Rulesets.Mania/Objects/Note.cs @@ -1,9 +1,27 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Game.Beatmaps.Timing; +using osu.Game.Database; +using osu.Game.Rulesets.Mania.Judgements; + namespace osu.Game.Rulesets.Mania.Objects { - public class Note : ManiaBaseHit + /// + /// Represents a hit object which has a single hit press. + /// + public class Note : ManiaHitObject { + /// + /// The key-press hit window for this note. + /// + protected HitWindows HitWindows = new HitWindows(); + + public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty) + { + base.ApplyDefaults(timing, difficulty); + + HitWindows = new HitWindows(difficulty.OverallDifficulty); + } } } diff --git a/osu.Game.Rulesets.Mania/Objects/Types/IHasColumn.cs b/osu.Game.Rulesets.Mania/Objects/Types/IHasColumn.cs new file mode 100644 index 0000000000..8281d0d9e4 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Objects/Types/IHasColumn.cs @@ -0,0 +1,16 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Mania.Objects.Types +{ + /// + /// A type of hit object which lies in one of a number of predetermined columns. + /// + public interface IHasColumn + { + /// + /// The column which the hit object lies in. + /// + int Column { get; } + } +} diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index ba0304a44a..96f04f79d4 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -8,13 +8,13 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mania.Scoring { - internal class ManiaScoreProcessor : ScoreProcessor + internal class ManiaScoreProcessor : ScoreProcessor { public ManiaScoreProcessor() { } - public ManiaScoreProcessor(HitRenderer hitRenderer) + public ManiaScoreProcessor(HitRenderer hitRenderer) : base(hitRenderer) { } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs b/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs index 7fb8f95b4c..c007cdc80e 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs @@ -13,7 +13,7 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mania.UI { - public class ManiaHitRenderer : HitRenderer + public class ManiaHitRenderer : HitRenderer { private readonly int columns; @@ -25,10 +25,10 @@ namespace osu.Game.Rulesets.Mania.UI public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this); - protected override BeatmapConverter CreateBeatmapConverter() => new ManiaBeatmapConverter(); + protected override BeatmapConverter CreateBeatmapConverter() => new ManiaBeatmapConverter(); - protected override Playfield CreatePlayfield() => new ManiaPlayfield(columns); + protected override Playfield CreatePlayfield() => new ManiaPlayfield(columns); - protected override DrawableHitObject GetVisualRepresentation(ManiaBaseHit h) => null; + protected override DrawableHitObject GetVisualRepresentation(ManiaHitObject h) => null; } } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index fa55768382..438c1f4b5f 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -19,7 +19,7 @@ using System.Collections.Generic; namespace osu.Game.Rulesets.Mania.UI { - public class ManiaPlayfield : Playfield + public class ManiaPlayfield : Playfield { /// /// Default column keys, expanding outwards from the middle as more column are added. diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj index e2c6ad9a9f..0f2d7e7c1c 100644 --- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj +++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj @@ -48,12 +48,18 @@ + + + + + + + - - + diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs index d2cd7a1156..65ed9530f2 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs @@ -58,8 +58,8 @@ namespace osu.Game.Rulesets.Osu.Replays { public int Compare(ReplayFrame f1, ReplayFrame f2) { - if (f1 == null) throw new NullReferenceException($@"{nameof(f1)} cannot be null"); - if (f2 == null) throw new NullReferenceException($@"{nameof(f2)} cannot be null"); + if (f1 == null) throw new ArgumentNullException(nameof(f1)); + if (f2 == null) throw new ArgumentNullException(nameof(f2)); return f1.Time.CompareTo(f2.Time); } diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 0784c94059..355ce1cf68 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps protected override Beatmap ConvertBeatmap(Beatmap original) { // Rewrite the beatmap info to add the slider velocity multiplier - BeatmapInfo info = original.BeatmapInfo.DeepClone(); + BeatmapInfo info = original.BeatmapInfo.DeepClone(); info.Difficulty.SliderMultiplier *= legacy_velocity_multiplier; Beatmap converted = base.ConvertBeatmap(original); diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index eec614426f..c3e5597f43 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -109,7 +109,7 @@ namespace osu.Game.Rulesets.Taiko.Replays Frames.Add(new ReplayFrame(h.StartTime, null, null, button)); } else - throw new Exception("Unknown hit object type."); + throw new InvalidOperationException("Unknown hit object type."); Frames.Add(new ReplayFrame(endTime + KEY_UP_DELAY, null, null, ReplayButtonState.None)); diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 8e6f1c8556..23c7606a15 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -54,33 +54,33 @@ namespace osu.Game.Rulesets.Taiko.UI { AddInternal(new Drawable[] { - rightBackgroundContainer = new Container - { - Name = "Transparent playfield background", - RelativeSizeAxes = Axes.Both, - BorderThickness = 2, - Masking = true, - EdgeEffect = new EdgeEffect - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.2f), - Radius = 5, - }, - Children = new Drawable[] - { - rightBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.6f - }, - } - }, new ScaleFixContainer { RelativeSizeAxes = Axes.X, Height = DEFAULT_PLAYFIELD_HEIGHT, Children = new[] { + rightBackgroundContainer = new Container + { + Name = "Transparent playfield background", + RelativeSizeAxes = Axes.Both, + BorderThickness = 2, + Masking = true, + EdgeEffect = new EdgeEffect + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.2f), + Radius = 5, + }, + Children = new Drawable[] + { + rightBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.6f + }, + } + }, new Container { Name = "Transparent playfield elements", diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index b39db30fe8..0e456941a1 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -135,14 +135,13 @@ namespace osu.Game.Tests.Beatmaps.IO waitAction = () => { while ((resultBeatmaps = host.Dependencies.Get() - .Query().Where(s => s.OnlineBeatmapSetID == 241526 && s.BaseDifficultyID > 0)).Count() != 12) + .GetAllWithChildren(s => s.OnlineBeatmapSetID == 241526 && s.BaseDifficultyID > 0)).Count() != 12) Thread.Sleep(50); }; Assert.IsTrue(waitAction.BeginInvoke(null, null).AsyncWaitHandle.WaitOne(timeout), @"Beatmaps did not import to the database in allocated time"); - //fetch children and check we can load from the post-storage path... var set = host.Dependencies.Get().GetChildren(resultSets.First()); Assert.IsTrue(set.Beatmaps.Count == resultBeatmaps.Count(), diff --git a/osu.Game/Audio/SampleInfoList.cs b/osu.Game/Audio/SampleInfoList.cs index 594341bbb1..06dd716a4a 100644 --- a/osu.Game/Audio/SampleInfoList.cs +++ b/osu.Game/Audio/SampleInfoList.cs @@ -11,9 +11,8 @@ namespace osu.Game.Audio { } - public SampleInfoList(IEnumerable elements) + public SampleInfoList(IEnumerable elements) : base(elements) { - AddRange(elements); } } } \ No newline at end of file diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs index db14a48af1..8431e5f812 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs @@ -51,14 +51,12 @@ namespace osu.Game.Beatmaps.Drawables title = new OsuSpriteText { Font = @"Exo2.0-BoldItalic", - Text = beatmap.BeatmapSetInfo.Metadata.Title, TextSize = 22, Shadow = true, }, artist = new OsuSpriteText { Font = @"Exo2.0-SemiBoldItalic", - Text = beatmap.BeatmapSetInfo.Metadata.Artist, TextSize = 17, Shadow = true, }, @@ -81,8 +79,8 @@ namespace osu.Game.Beatmaps.Drawables [BackgroundDependencyLoader] private void load(LocalisationEngine localisation) { - title.Current = localisation.GetUnicodePreference(beatmap.BeatmapSetInfo.Metadata.TitleUnicode, beatmap.BeatmapSetInfo.Metadata.Title); - artist.Current = localisation.GetUnicodePreference(beatmap.BeatmapSetInfo.Metadata.ArtistUnicode, beatmap.BeatmapSetInfo.Metadata.Artist); + title.Current = localisation.GetUnicodePreference(beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title); + artist.Current = localisation.GetUnicodePreference(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist); } private class PanelBackground : BufferedContainer diff --git a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs index cc9d367a59..21a3ab9842 100644 --- a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs @@ -11,7 +11,7 @@ namespace osu.Game.Beatmaps.Formats { public abstract class BeatmapDecoder { - private static Dictionary decoders { get; } = new Dictionary(); + private static readonly Dictionary decoders = new Dictionary(); public static BeatmapDecoder GetDecoder(StreamReader stream) { diff --git a/osu.Game/Beatmaps/IO/ArchiveReader.cs b/osu.Game/Beatmaps/IO/ArchiveReader.cs index 6c6b6be23c..9d7d67007e 100644 --- a/osu.Game/Beatmaps/IO/ArchiveReader.cs +++ b/osu.Game/Beatmaps/IO/ArchiveReader.cs @@ -13,11 +13,11 @@ namespace osu.Game.Beatmaps.IO { private class Reader { - public Func Test { get; set; } - public Type Type { get; set; } + public Func Test; + public Type Type; } - private static List readers { get; } = new List(); + private static readonly List readers = new List(); public static ArchiveReader GetReader(Storage storage, string path) { @@ -58,11 +58,9 @@ namespace osu.Game.Beatmaps.IO if (input == null) return null; - using (MemoryStream ms = new MemoryStream()) - { - input.CopyTo(ms); - return ms.ToArray(); - } + byte[] buffer = new byte[input.Length]; + input.Read(buffer, 0, buffer.Length); + return buffer; } } } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 589557b088..0e8d8a9546 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -18,14 +18,17 @@ namespace osu.Game.Beatmaps public readonly BeatmapSetInfo BeatmapSetInfo; + public readonly BeatmapMetadata Metadata; + public readonly Bindable> Mods = new Bindable>(new Mod[] { }); public readonly bool WithStoryboard; - protected WorkingBeatmap(BeatmapInfo beatmapInfo, BeatmapSetInfo beatmapSetInfo, bool withStoryboard = false) + protected WorkingBeatmap(BeatmapInfo beatmapInfo, bool withStoryboard = false) { BeatmapInfo = beatmapInfo; - BeatmapSetInfo = beatmapSetInfo; + BeatmapSetInfo = beatmapInfo.BeatmapSet; + Metadata = beatmapInfo.Metadata ?? BeatmapSetInfo.Metadata; WithStoryboard = withStoryboard; Mods.ValueChanged += mods => applyRateAdjustments(); diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 3907496ca2..ba19d8592a 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -3,6 +3,7 @@ using osu.Framework.Configuration; using osu.Framework.Platform; +using osu.Game.Overlays; using osu.Game.Screens.Select; namespace osu.Game.Configuration @@ -19,6 +20,8 @@ namespace osu.Game.Configuration Set(OsuConfig.DisplayStarsMinimum, 0.0, 0, 10); Set(OsuConfig.DisplayStarsMaximum, 10.0, 0, 10); + Set(OsuConfig.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2, 1); + // Online settings Set(OsuConfig.Username, string.Empty); @@ -102,6 +105,7 @@ namespace osu.Game.Configuration DisplayStarsMaximum, SnakingInSliders, SnakingOutSliders, - ShowFpsDisplay + ShowFpsDisplay, + ChatDisplayHeight } } diff --git a/osu.Game/Database/BeatmapDatabase.cs b/osu.Game/Database/BeatmapDatabase.cs index 0e814dea82..bd25ebe8a9 100644 --- a/osu.Game/Database/BeatmapDatabase.cs +++ b/osu.Game/Database/BeatmapDatabase.cs @@ -36,14 +36,12 @@ namespace osu.Game.Database private void deletePending() { - foreach (var b in Query().Where(b => b.DeletePending)) + foreach (var b in GetAllWithChildren(b => b.DeletePending)) { try { Storage.Delete(b.Path); - GetChildren(b, true); - foreach (var i in b.Beatmaps) { if (i.Metadata != null) Connection.Delete(i.Metadata); @@ -269,20 +267,16 @@ namespace osu.Game.Database public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, WorkingBeatmap previous = null, bool withStoryboard = false) { - var beatmapSetInfo = Query().FirstOrDefault(s => s.ID == beatmapInfo.BeatmapSetInfoID); + if (beatmapInfo.BeatmapSet == null || beatmapInfo.Ruleset == null) + beatmapInfo = GetChildren(beatmapInfo, true); - if (beatmapSetInfo == null) + if (beatmapInfo.BeatmapSet == null) throw new InvalidOperationException($@"Beatmap set {beatmapInfo.BeatmapSetInfoID} is not in the local database."); - //we need metadata - GetChildren(beatmapSetInfo); - //we also need a ruleset - GetChildren(beatmapInfo); - if (beatmapInfo.Metadata == null) - beatmapInfo.Metadata = beatmapSetInfo.Metadata; + beatmapInfo.Metadata = beatmapInfo.BeatmapSet.Metadata; - WorkingBeatmap working = new DatabaseWorkingBeatmap(this, beatmapInfo, beatmapSetInfo, withStoryboard); + WorkingBeatmap working = new DatabaseWorkingBeatmap(this, beatmapInfo, withStoryboard); previous?.TransferTo(working); diff --git a/osu.Game/Database/Database.cs b/osu.Game/Database/Database.cs index 23851b3b2e..a55c0f570b 100644 --- a/osu.Game/Database/Database.cs +++ b/osu.Game/Database/Database.cs @@ -48,11 +48,9 @@ namespace osu.Game.Database return Connection.Table(); } - public T GetWithChildren(object id) where T : class - { - return Connection.GetWithChildren(id); - } - + /// + /// This is expensive. Use with caution. + /// public List GetAllWithChildren(Expression> filter = null, bool recursive = true) where T : class { diff --git a/osu.Game/Database/DatabaseWorkingBeatmap.cs b/osu.Game/Database/DatabaseWorkingBeatmap.cs index 9fb3bed1e7..c56d6cea51 100644 --- a/osu.Game/Database/DatabaseWorkingBeatmap.cs +++ b/osu.Game/Database/DatabaseWorkingBeatmap.cs @@ -14,8 +14,8 @@ namespace osu.Game.Database { private readonly BeatmapDatabase database; - public DatabaseWorkingBeatmap(BeatmapDatabase database, BeatmapInfo beatmapInfo, BeatmapSetInfo beatmapSetInfo, bool withStoryboard = false) - : base(beatmapInfo, beatmapSetInfo, withStoryboard) + public DatabaseWorkingBeatmap(BeatmapDatabase database, BeatmapInfo beatmapInfo, bool withStoryboard = false) + : base(beatmapInfo, withStoryboard) { this.database = database; } @@ -51,13 +51,13 @@ namespace osu.Game.Database protected override Texture GetBackground() { - if (BeatmapInfo?.Metadata?.BackgroundFile == null) + if (Metadata?.BackgroundFile == null) return null; try { using (var reader = getReader()) - return new TextureStore(new RawTextureLoaderStore(reader), false).Get(BeatmapInfo.Metadata.BackgroundFile); + return new TextureStore(new RawTextureLoaderStore(reader), false).Get(Metadata.BackgroundFile); } catch { return null; } } @@ -66,7 +66,7 @@ namespace osu.Game.Database { try { - var trackData = getReader()?.GetStream(BeatmapInfo.Metadata.AudioFile); + var trackData = getReader()?.GetStream(Metadata.AudioFile); return trackData == null ? null : new TrackBass(trackData); } catch { return null; } diff --git a/osu.Game/Database/ScoreDatabase.cs b/osu.Game/Database/ScoreDatabase.cs index 8ea836aceb..adca76d2ac 100644 --- a/osu.Game/Database/ScoreDatabase.cs +++ b/osu.Game/Database/ScoreDatabase.cs @@ -94,13 +94,13 @@ namespace osu.Game.Database { byte[] properties = new byte[5]; if (replayInStream.Read(properties, 0, 5) != 5) - throw new Exception("input .lzma is too short"); + throw new IOException("input .lzma is too short"); long outSize = 0; for (int i = 0; i < 8; i++) { int v = replayInStream.ReadByte(); if (v < 0) - throw new Exception("Can't Read 1"); + throw new IOException("Can't Read 1"); outSize |= (long)(byte)v << (8 * i); } diff --git a/osu.Game/Graphics/Cursor/CursorTrail.cs b/osu.Game/Graphics/Cursor/CursorTrail.cs index 09d1b99d13..11475a0f19 100644 --- a/osu.Game/Graphics/Cursor/CursorTrail.cs +++ b/osu.Game/Graphics/Cursor/CursorTrail.cs @@ -14,6 +14,7 @@ using OpenTK.Graphics.ES30; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Colour; using osu.Framework.Timing; +using System.Diagnostics; namespace osu.Game.Graphics.Cursor { @@ -39,6 +40,8 @@ namespace osu.Game.Graphics.Cursor private Vector2? lastPosition; + private readonly InputResampler resampler = new InputResampler(); + protected override DrawNode CreateDrawNode() => new TrailDrawNode(); protected override void ApplyDrawNode(DrawNode node) @@ -75,7 +78,7 @@ namespace osu.Game.Graphics.Cursor [BackgroundDependencyLoader] private void load(ShaderManager shaders, TextureStore textures) { - shader = shaders?.Load(@"CursorTrail", FragmentShaderDescriptor.Texture); + shader = shaders?.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE); texture = textures.Get(@"Cursor/cursortrail"); Scale = new Vector2(1 / texture.ScaleAdjust); } @@ -116,22 +119,26 @@ namespace osu.Game.Graphics.Cursor if (lastPosition == null) { lastPosition = state.Mouse.NativeState.Position; + resampler.AddPosition(lastPosition.Value); return base.OnMouseMove(state); } - Vector2 pos1 = lastPosition.Value; - Vector2 pos2 = state.Mouse.NativeState.Position; - - Vector2 diff = pos2 - pos1; - float distance = diff.Length; - Vector2 direction = diff / distance; - - float interval = size.X / 2 * 0.9f; - - for (float d = interval; d < distance; d += interval) + foreach (Vector2 pos2 in resampler.AddPosition(state.Mouse.NativeState.Position)) { - lastPosition = pos1 + direction * d; - addPosition(lastPosition.Value); + Trace.Assert(lastPosition.HasValue); + + Vector2 pos1 = lastPosition.Value; + Vector2 diff = pos2 - pos1; + float distance = diff.Length; + Vector2 direction = diff / distance; + + float interval = size.X / 2 * 0.9f; + + for (float d = interval; d < distance; d += interval) + { + lastPosition = pos1 + direction * d; + addPosition(lastPosition.Value); + } } return base.OnMouseMove(state); diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index cd719431e7..3d83668d07 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -16,7 +16,7 @@ namespace osu.Game.Graphics switch (hex.Length) { default: - throw new Exception(@"Invalid hex string length!"); + throw new ArgumentException(@"Invalid hex string length!"); case 3: return new Color4( (byte)(Convert.ToByte(hex.Substring(0, 1), 16) * 17), @@ -33,57 +33,59 @@ namespace osu.Game.Graphics } // See https://github.com/ppy/osu-web/blob/master/resources/assets/less/colors.less - public Color4 PurpleLighter = FromHex(@"eeeeff"); - public Color4 PurpleLight = FromHex(@"aa88ff"); - public Color4 Purple = FromHex(@"8866ee"); - public Color4 PurpleDark = FromHex(@"6644cc"); - public Color4 PurpleDarker = FromHex(@"441188"); + public readonly Color4 PurpleLighter = FromHex(@"eeeeff"); + public readonly Color4 PurpleLight = FromHex(@"aa88ff"); + public readonly Color4 Purple = FromHex(@"8866ee"); + public readonly Color4 PurpleDark = FromHex(@"6644cc"); + public readonly Color4 PurpleDarker = FromHex(@"441188"); - public Color4 PinkLighter = FromHex(@"ffddee"); - public Color4 PinkLight = FromHex(@"ff99cc"); - public Color4 Pink = FromHex(@"ff66aa"); - public Color4 PinkDark = FromHex(@"cc5288"); - public Color4 PinkDarker = FromHex(@"bb1177"); + public readonly Color4 PinkLighter = FromHex(@"ffddee"); + public readonly Color4 PinkLight = FromHex(@"ff99cc"); + public readonly Color4 Pink = FromHex(@"ff66aa"); + public readonly Color4 PinkDark = FromHex(@"cc5288"); + public readonly Color4 PinkDarker = FromHex(@"bb1177"); - public Color4 BlueLighter = FromHex(@"ddffff"); - public Color4 BlueLight = FromHex(@"99eeff"); - public Color4 Blue = FromHex(@"66ccff"); - public Color4 BlueDark = FromHex(@"44aadd"); - public Color4 BlueDarker = FromHex(@"2299bb"); + public readonly Color4 BlueLighter = FromHex(@"ddffff"); + public readonly Color4 BlueLight = FromHex(@"99eeff"); + public readonly Color4 Blue = FromHex(@"66ccff"); + public readonly Color4 BlueDark = FromHex(@"44aadd"); + public readonly Color4 BlueDarker = FromHex(@"2299bb"); - public Color4 YellowLighter = FromHex(@"ffffdd"); - public Color4 YellowLight = FromHex(@"ffdd55"); - public Color4 Yellow = FromHex(@"ffcc22"); - public Color4 YellowDark = FromHex(@"eeaa00"); - public Color4 YellowDarker = FromHex(@"cc6600"); + public readonly Color4 YellowLighter = FromHex(@"ffffdd"); + public readonly Color4 YellowLight = FromHex(@"ffdd55"); + public readonly Color4 Yellow = FromHex(@"ffcc22"); + public readonly Color4 YellowDark = FromHex(@"eeaa00"); + public readonly Color4 YellowDarker = FromHex(@"cc6600"); - public Color4 GreenLighter = FromHex(@"eeffcc"); - public Color4 GreenLight = FromHex(@"b3d944"); - public Color4 Green = FromHex(@"88b300"); - public Color4 GreenDark = FromHex(@"668800"); - public Color4 GreenDarker = FromHex(@"445500"); + public readonly Color4 GreenLighter = FromHex(@"eeffcc"); + public readonly Color4 GreenLight = FromHex(@"b3d944"); + public readonly Color4 Green = FromHex(@"88b300"); + public readonly Color4 GreenDark = FromHex(@"668800"); + public readonly Color4 GreenDarker = FromHex(@"445500"); - public Color4 Gray0 = FromHex(@"000"); - public Color4 Gray1 = FromHex(@"111"); - public Color4 Gray2 = FromHex(@"222"); - public Color4 Gray3 = FromHex(@"333"); - public Color4 Gray4 = FromHex(@"444"); - public Color4 Gray5 = FromHex(@"555"); - public Color4 Gray6 = FromHex(@"666"); - public Color4 Gray7 = FromHex(@"777"); - public Color4 Gray8 = FromHex(@"888"); - public Color4 Gray9 = FromHex(@"999"); - public Color4 GrayA = FromHex(@"aaa"); - public Color4 GrayB = FromHex(@"bbb"); - public Color4 GrayC = FromHex(@"ccc"); - public Color4 GrayD = FromHex(@"ddd"); - public Color4 GrayE = FromHex(@"eee"); - public Color4 GrayF = FromHex(@"fff"); + public readonly Color4 Gray0 = FromHex(@"000"); + public readonly Color4 Gray1 = FromHex(@"111"); + public readonly Color4 Gray2 = FromHex(@"222"); + public readonly Color4 Gray3 = FromHex(@"333"); + public readonly Color4 Gray4 = FromHex(@"444"); + public readonly Color4 Gray5 = FromHex(@"555"); + public readonly Color4 Gray6 = FromHex(@"666"); + public readonly Color4 Gray7 = FromHex(@"777"); + public readonly Color4 Gray8 = FromHex(@"888"); + public readonly Color4 Gray9 = FromHex(@"999"); + public readonly Color4 GrayA = FromHex(@"aaa"); + public readonly Color4 GrayB = FromHex(@"bbb"); + public readonly Color4 GrayC = FromHex(@"ccc"); + public readonly Color4 GrayD = FromHex(@"ddd"); + public readonly Color4 GrayE = FromHex(@"eee"); + public readonly Color4 GrayF = FromHex(@"fff"); - public Color4 RedLighter = FromHex(@"ffeded"); - public Color4 RedLight = FromHex(@"ed7787"); - public Color4 Red = FromHex(@"ed1121"); - public Color4 RedDark = FromHex(@"ba0011"); - public Color4 RedDarker = FromHex(@"870000"); + public readonly Color4 RedLighter = FromHex(@"ffeded"); + public readonly Color4 RedLight = FromHex(@"ed7787"); + public readonly Color4 Red = FromHex(@"ed1121"); + public readonly Color4 RedDark = FromHex(@"ba0011"); + public readonly Color4 RedDarker = FromHex(@"870000"); + + public readonly Color4 ChatBlue = FromHex(@"17292e"); } } diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index 242a9a8f6a..8b72eedbfd 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -25,15 +25,15 @@ namespace osu.Game.Graphics.UserInterface protected override bool InternalContains(Vector2 screenSpacePos) => base.InternalContains(screenSpacePos) || Dropdown.Contains(screenSpacePos); + private bool isEnumType => typeof(T).IsEnum; + public OsuTabControl() { TabContainer.Spacing = new Vector2(10f, 0f); - if (!typeof(T).IsEnum) - throw new InvalidOperationException("OsuTabControl only supports enums as the generic type argument"); - - foreach (var val in (T[])Enum.GetValues(typeof(T))) - AddItem(val); + if (isEnumType) + foreach (var val in (T[])Enum.GetValues(typeof(T))) + AddItem(val); } [BackgroundDependencyLoader] @@ -136,7 +136,7 @@ namespace osu.Game.Graphics.UserInterface Margin = new MarginPadding { Top = 5, Bottom = 5 }, Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, - Text = (value as Enum)?.GetDescription(), + Text = (value as Enum)?.GetDescription() ?? value.ToString(), TextSize = 14, Font = @"Exo2.0-Bold", // Font should only turn bold when active? }, diff --git a/osu.Game/Screens/Select/SearchTextBox.cs b/osu.Game/Graphics/UserInterface/SearchTextBox.cs similarity index 91% rename from osu.Game/Screens/Select/SearchTextBox.cs rename to osu.Game/Graphics/UserInterface/SearchTextBox.cs index d451f63a96..6a8236529b 100644 --- a/osu.Game/Screens/Select/SearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/SearchTextBox.cs @@ -1,14 +1,12 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Input; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Input; -using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; +using OpenTK.Input; -namespace osu.Game.Screens.Select +namespace osu.Game.Graphics.UserInterface { /// /// A textbox which holds focus eagerly. diff --git a/osu.Game/IO/Serialization/IJsonSerializable.cs b/osu.Game/IO/Serialization/IJsonSerializable.cs index 33d0801e47..7dbc860979 100644 --- a/osu.Game/IO/Serialization/IJsonSerializable.cs +++ b/osu.Game/IO/Serialization/IJsonSerializable.cs @@ -16,12 +16,13 @@ namespace osu.Game.IO.Serialization return JsonConvert.SerializeObject(obj); } - public static T Deserialize(string objString) + public static T Deserialize(this string objString) { return JsonConvert.DeserializeObject(objString); } - public static T DeepClone(this IJsonSerializable obj) + public static T DeepClone(this T obj) + where T : IJsonSerializable { return Deserialize(Serialize(obj)); } diff --git a/osu.Game/Input/GlobalHotkeys.cs b/osu.Game/Input/GlobalHotkeys.cs deleted file mode 100644 index b1b22f7069..0000000000 --- a/osu.Game/Input/GlobalHotkeys.cs +++ /dev/null @@ -1,26 +0,0 @@ -// 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.Input; - -namespace osu.Game.Input -{ - public class GlobalHotkeys : Drawable - { - public Func Handler; - - public override bool HandleInput => true; - - public GlobalHotkeys() - { - RelativeSizeAxes = Axes.Both; - } - - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) - { - return Handler(state, args); - } - } -} \ No newline at end of file diff --git a/osu.Game/Online/API/OAuth.cs b/osu.Game/Online/API/OAuth.cs index 0ee7e0e030..c96b21a855 100644 --- a/osu.Game/Online/API/OAuth.cs +++ b/osu.Game/Online/API/OAuth.cs @@ -72,21 +72,28 @@ namespace osu.Game.Online.API } } + private static readonly object access_token_retrieval_lock = new object(); + /// /// Should be run before any API request to make sure we have a valid key. /// private bool ensureAccessToken() { - //todo: we need to mutex this to ensure only one authentication request is running at a time. - - //If we already have a valid access token, let's use it. + // if we already have a valid access token, let's use it. if (accessTokenValid) return true; - //If not, let's try using our refresh token to request a new access token. - if (!string.IsNullOrEmpty(Token?.RefreshToken)) - AuthenticateWithRefresh(Token.RefreshToken); + // we want to ensure only a single authentication update is happening at once. + lock (access_token_retrieval_lock) + { + // re-check if valid, in case another request completed and revalidated our access. + if (accessTokenValid) return true; - return accessTokenValid; + // if not, let's try using our refresh token to request a new access token. + if (!string.IsNullOrEmpty(Token?.RefreshToken)) + AuthenticateWithRefresh(Token.RefreshToken); + + return accessTokenValid; + } } private bool accessTokenValid => Token?.IsValid ?? false; diff --git a/osu.Game/Online/API/OAuthToken.cs b/osu.Game/Online/API/OAuthToken.cs index 4085e5602a..328888ab8a 100644 --- a/osu.Game/Online/API/OAuthToken.cs +++ b/osu.Game/Online/API/OAuthToken.cs @@ -41,13 +41,13 @@ namespace osu.Game.Online.API [JsonProperty(@"refresh_token")] public string RefreshToken; - public override string ToString() => $@"{AccessToken}/{AccessTokenExpiry.ToString(NumberFormatInfo.InvariantInfo)}/{RefreshToken}"; + public override string ToString() => $@"{AccessToken}|{AccessTokenExpiry.ToString(NumberFormatInfo.InvariantInfo)}|{RefreshToken}"; public static OAuthToken Parse(string value) { try { - string[] parts = value.Split('/'); + string[] parts = value.Split('|'); return new OAuthToken { AccessToken = parts[0], diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index 04ebf0a389..d159f482a9 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -23,10 +23,12 @@ namespace osu.Game.Online.Chat [JsonProperty(@"channel_id")] public int Id; - public SortedList Messages = new SortedList((m1, m2) => m1.Id.CompareTo(m2.Id)); + public readonly SortedList Messages = new SortedList((m1, m2) => m1.Id.CompareTo(m2.Id)); //internal bool Joined; + public bool ReadOnly => Name != "#lazer"; + public const int MAX_HISTORY = 300; [JsonConstructor] @@ -53,5 +55,7 @@ namespace osu.Game.Online.Chat if (messageCount > MAX_HISTORY) Messages.RemoveRange(0, messageCount - MAX_HISTORY); } + + public override string ToString() => Name; } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index c9f41de5f2..a84333df8c 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Overlays; using osu.Framework.Input; -using osu.Game.Input; using OpenTK.Input; using osu.Framework.Logging; using osu.Game.Graphics.UserInterface.Volume; @@ -161,7 +160,7 @@ namespace osu.Game }); //overlay elements - LoadComponentAsync(chat = new ChatOverlay { Depth = 0 }, overlayContent.Add); + LoadComponentAsync(chat = new ChatOverlay { Depth = -1 }, mainContent.Add); LoadComponentAsync(options = new OptionsOverlay { Depth = -1 }, overlayContent.Add); LoadComponentAsync(musicController = new MusicController { @@ -321,8 +320,7 @@ namespace osu.Game { base.UpdateAfterChildren(); - if (intro?.ChildScreen != null) - intro.ChildScreen.Padding = new MarginPadding { Top = Toolbar.Position.Y + Toolbar.DrawHeight }; + mainContent.Padding = new MarginPadding { Top = Toolbar.Position.Y + Toolbar.DrawHeight }; Cursor.State = currentScreen?.HasLocalCursorDisplayed == false ? Visibility.Visible : Visibility.Hidden; } diff --git a/osu.Game/Online/Chat/Drawables/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs similarity index 87% rename from osu.Game/Online/Chat/Drawables/ChatLine.cs rename to osu.Game/Overlays/Chat/ChatLine.cs index 6bfa25755f..d4063efbb5 100644 --- a/osu.Game/Online/Chat/Drawables/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -6,10 +6,11 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Online.Chat; using OpenTK; using OpenTK.Graphics; -namespace osu.Game.Online.Chat.Drawables +namespace osu.Game.Overlays.Chat { public class ChatLine : Container { @@ -62,7 +63,10 @@ namespace osu.Game.Online.Chat.Drawables return username_colours[message.UserId % username_colours.Length]; } - private const float padding = 200; + public const float LEFT_PADDING = message_padding + padding * 2; + + private const float padding = 15; + private const float message_padding = 200; private const float text_size = 20; public ChatLine(Message message) @@ -72,13 +76,13 @@ namespace osu.Game.Online.Chat.Drawables RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - Padding = new MarginPadding { Left = 15, Right = 15 }; + Padding = new MarginPadding { Left = padding, Right = padding }; Children = new Drawable[] { new Container { - Size = new Vector2(padding, text_size), + Size = new Vector2(message_padding, text_size), Children = new Drawable[] { new OsuSpriteText @@ -106,7 +110,7 @@ namespace osu.Game.Online.Chat.Drawables { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Left = padding + 15 }, + Padding = new MarginPadding { Left = message_padding + padding }, Children = new Drawable[] { new OsuSpriteText diff --git a/osu.Game/Overlays/Chat/ChatTabControl.cs b/osu.Game/Overlays/Chat/ChatTabControl.cs new file mode 100644 index 0000000000..c7e1382255 --- /dev/null +++ b/osu.Game/Overlays/Chat/ChatTabControl.cs @@ -0,0 +1,182 @@ +// 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.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Chat; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Overlays.Chat +{ + public class ChatTabControl : OsuTabControl + { + protected override TabItem CreateTabItem(Channel value) => new ChannelTabItem(value); + + private const float shear_width = 10; + + public ChatTabControl() + { + TabContainer.Margin = new MarginPadding { Left = 50 }; + TabContainer.Spacing = new Vector2(-shear_width, 0); + TabContainer.Masking = false; + + AddInternal(new TextAwesome + { + Icon = FontAwesome.fa_comments, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + TextSize = 20, + Padding = new MarginPadding(10), + }); + } + + private class ChannelTabItem : TabItem + { + private Color4 backgroundInactive; + private Color4 backgroundHover; + private Color4 backgroundActive; + + private readonly SpriteText text; + private readonly Box box; + private readonly Box highlightBox; + + public override bool Active + { + get { return base.Active; } + set + { + if (Active == value) return; + + base.Active = value; + updateState(); + } + } + + private void updateState() + { + if (Active) + fadeActive(); + else + fadeInactive(); + } + + private const float transition_length = 400; + + private void fadeActive() + { + ResizeTo(new Vector2(Width, 1.1f), transition_length, EasingTypes.OutQuint); + + box.FadeColour(backgroundActive, transition_length, EasingTypes.OutQuint); + highlightBox.FadeIn(transition_length, EasingTypes.OutQuint); + text.Font = @"Exo2.0-Bold"; + } + + private void fadeInactive() + { + ResizeTo(new Vector2(Width, 1), transition_length, EasingTypes.OutQuint); + + box.FadeColour(backgroundInactive, transition_length, EasingTypes.OutQuint); + highlightBox.FadeOut(transition_length, EasingTypes.OutQuint); + text.Font = @"Exo2.0-Regular"; + } + + protected override bool OnHover(InputState state) + { + if (!Active) + box.FadeColour(backgroundHover, transition_length, EasingTypes.OutQuint); + return true; + } + + protected override void OnHoverLost(InputState state) + { + updateState(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + backgroundActive = colours.ChatBlue; + backgroundInactive = colours.Gray4; + backgroundHover = colours.Gray7; + + highlightBox.Colour = colours.Yellow; + + updateState(); + } + + public ChannelTabItem(Channel value) : base(value) + { + Width = 150; + + RelativeSizeAxes = Axes.Y; + + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + + Shear = new Vector2(shear_width / ChatOverlay.TAB_AREA_HEIGHT, 0); + + Masking = true; + EdgeEffect = new EdgeEffect + { + Type = EdgeEffectType.Shadow, + Radius = 10, + Colour = Color4.Black.Opacity(0.2f), + }; + + Children = new Drawable[] + { + box = new Box + { + EdgeSmoothness = new Vector2(1, 0), + RelativeSizeAxes = Axes.Both, + }, + highlightBox = new Box + { + Width = 5, + Alpha = 0, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + EdgeSmoothness = new Vector2(1, 0), + RelativeSizeAxes = Axes.Y, + }, + new Container + { + Shear = new Vector2(-shear_width / ChatOverlay.TAB_AREA_HEIGHT, 0), + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new TextAwesome + { + Icon = FontAwesome.fa_hashtag, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Colour = Color4.Black, + X = -10, + Alpha = 0.2f, + TextSize = ChatOverlay.TAB_AREA_HEIGHT, + }, + text = new OsuSpriteText + { + Margin = new MarginPadding(5), + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Text = value.ToString(), + TextSize = 18, + }, + } + } + }; + } + } + } +} diff --git a/osu.Game/Online/Chat/Drawables/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs similarity index 80% rename from osu.Game/Online/Chat/Drawables/DrawableChannel.cs rename to osu.Game/Overlays/Chat/DrawableChannel.cs index e3101e8fd7..39dc1914ab 100644 --- a/osu.Game/Online/Chat/Drawables/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -7,32 +7,24 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; -using osu.Game.Graphics.Sprites; +using osu.Game.Online.Chat; -namespace osu.Game.Online.Chat.Drawables +namespace osu.Game.Overlays.Chat { public class DrawableChannel : Container { - private readonly Channel channel; + public readonly Channel Channel; private readonly FillFlowContainer flow; private readonly ScrollContainer scroll; public DrawableChannel(Channel channel) { - this.channel = channel; + Channel = channel; RelativeSizeAxes = Axes.Both; Children = new Drawable[] { - new OsuSpriteText - { - Text = channel.Name, - TextSize = 50, - Alpha = 0.3f, - Anchor = Anchor.Centre, - Origin = Anchor.Centre - }, scroll = new ScrollContainer { RelativeSizeAxes = Axes.Both, @@ -56,14 +48,14 @@ namespace osu.Game.Online.Chat.Drawables { base.LoadComplete(); - newMessagesArrived(channel.Messages); + newMessagesArrived(Channel.Messages); scrollToEnd(); } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - channel.NewMessagesArrived -= newMessagesArrived; + Channel.NewMessagesArrived -= newMessagesArrived; } private void newMessagesArrived(IEnumerable newMessages) @@ -93,4 +85,4 @@ namespace osu.Game.Online.Chat.Drawables private void scrollToEnd() => Scheduler.AddDelayed(() => scroll.ScrollToEnd(), 50); } -} \ No newline at end of file +} diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 457611dfab..15f8586125 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Linq; using OpenTK; using osu.Framework.Allocation; +using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -15,24 +16,24 @@ using osu.Game.Graphics.Sprites; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.Chat; -using osu.Game.Online.Chat.Drawables; using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; using OpenTK.Graphics; using osu.Framework.Input; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Overlays.Chat; namespace osu.Game.Overlays { public class ChatOverlay : FocusedOverlayContainer, IOnlineComponent { - private const float textbox_height = 40; + private const float textbox_height = 60; private ScheduledDelegate messageRequest; - private readonly Container content; - - protected override Container Content => content; + private readonly Container currentChannelContainer; private readonly FocusedTextBox inputTextBox; @@ -40,50 +41,114 @@ namespace osu.Game.Overlays private const int transition_length = 500; + public const float DEFAULT_HEIGHT = 0.4f; + + public const float TAB_AREA_HEIGHT = 50; + private GetMessagesRequest fetchReq; + private readonly ChatTabControl channelTabs; + + private readonly Box chatBackground; + private readonly Box tabBackground; + + private Bindable chatHeight; + public ChatOverlay() { - RelativeSizeAxes = Axes.X; - Size = new Vector2(1, 300); + RelativeSizeAxes = Axes.Both; + RelativePositionAxes = Axes.Both; + Size = new Vector2(1, DEFAULT_HEIGHT); Anchor = Anchor.BottomLeft; Origin = Anchor.BottomLeft; - AddInternal(new Drawable[] + const float padding = 5; + + Children = new Drawable[] { - new Box + new Container { - Depth = float.MaxValue, + Name = @"chat area", RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.9f, - }, - content = new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 5, Bottom = textbox_height + 5 }, + Padding = new MarginPadding { Top = TAB_AREA_HEIGHT }, + Children = new Drawable[] + { + chatBackground = new Box + { + RelativeSizeAxes = Axes.Both, + }, + currentChannelContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding + { + Top = padding, + Bottom = textbox_height + padding + }, + }, + new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = textbox_height, + Padding = new MarginPadding + { + Top = padding * 2, + Bottom = padding * 2, + Left = ChatLine.LEFT_PADDING + padding * 2, + Right = padding * 2, + }, + Children = new Drawable[] + { + inputTextBox = new FocusedTextBox + { + RelativeSizeAxes = Axes.Both, + Height = 1, + PlaceholderText = "type your message", + Exit = () => State = Visibility.Hidden, + OnCommit = postMessage, + HoldFocus = true, + } + } + } + } }, new Container { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, + Name = @"tabs area", RelativeSizeAxes = Axes.X, - Height = textbox_height, - Padding = new MarginPadding(5), + Height = TAB_AREA_HEIGHT, Children = new Drawable[] { - inputTextBox = new FocusedTextBox + tabBackground = new Box { RelativeSizeAxes = Axes.Both, - Height = 1, - PlaceholderText = "type your message", - Exit = () => State = Visibility.Hidden, - OnCommit = postMessage, - HoldFocus = true, - } + Colour = Color4.Black, + }, + channelTabs = new ChatTabControl + { + RelativeSizeAxes = Axes.Both, + }, } - } - }); + }, + }; + + channelTabs.Current.ValueChanged += newChannel => CurrentChannel = newChannel; + } + + protected override bool OnDragStart(InputState state) + { + if (channelTabs.Hovering) + return true; + + return base.OnDragStart(state); + } + + protected override bool OnDrag(InputState state) + { + chatHeight.Value = Height - state.Mouse.Delta.Y / Parent.DrawSize.Y; + return base.OnDrag(state); } public void APIStateChanged(APIAccess api, APIState state) @@ -110,28 +175,48 @@ namespace osu.Game.Overlays { MoveToY(0, transition_length, EasingTypes.OutQuint); FadeIn(transition_length, EasingTypes.OutQuint); + + inputTextBox.HoldFocus = true; + base.PopIn(); } protected override void PopOut() { - MoveToY(DrawSize.Y, transition_length, EasingTypes.InSine); + MoveToY(Height, transition_length, EasingTypes.InSine); FadeOut(transition_length, EasingTypes.InSine); + + inputTextBox.HoldFocus = false; + base.PopOut(); } [BackgroundDependencyLoader] - private void load(APIAccess api) + private void load(APIAccess api, OsuConfigManager config, OsuColour colours) { this.api = api; api.Register(this); + + chatHeight = config.GetBindable(OsuConfig.ChatDisplayHeight); + chatHeight.ValueChanged += h => + { + Height = (float)h; + tabBackground.FadeTo(Height == 1 ? 1 : 0.8f, 200); + }; + chatHeight.TriggerChange(); + + chatBackground.Colour = colours.ChatBlue; } private long? lastMessageId; private List careChannels; + private readonly List loadedChannels = new List(); + private void initializeChannels() { - Clear(); + currentChannelContainer.Clear(); + + loadedChannels.Clear(); careChannels = new List(); @@ -154,25 +239,67 @@ namespace osu.Game.Overlays Scheduler.Add(delegate { loading.FadeOut(100); + addChannel(channels.Find(c => c.Name == @"#lazer")); + addChannel(channels.Find(c => c.Name == @"#osu")); + addChannel(channels.Find(c => c.Name == @"#lobby")); }); - messageRequest = Scheduler.AddDelayed(fetchNewMessages, 1000, true); + messageRequest = Scheduler.AddDelayed(() => fetchNewMessages(), 1000, true); }; + api.Queue(req); } + private Channel currentChannel; + + protected Channel CurrentChannel + { + get + { + return currentChannel; + } + + set + { + if (currentChannel == value) return; + + if (currentChannel != null) + currentChannelContainer.Clear(false); + + currentChannel = value; + + var loaded = loadedChannels.Find(d => d.Channel == value); + if (loaded == null) + loadedChannels.Add(loaded = new DrawableChannel(currentChannel)); + + inputTextBox.Current.Disabled = currentChannel.ReadOnly; + + currentChannelContainer.Add(loaded); + + channelTabs.Current.Value = value; + } + } + private void addChannel(Channel channel) { - Add(new DrawableChannel(channel)); + if (channel == null) return; + careChannels.Add(channel); + channelTabs.AddItem(channel); + + // we need to get a good number of messages initially for each channel we care about. + fetchNewMessages(channel); + + if (CurrentChannel == null) + CurrentChannel = channel; } - private void fetchNewMessages() + private void fetchNewMessages(Channel specificChannel = null) { if (fetchReq != null) return; - fetchReq = new GetMessagesRequest(careChannels, lastMessageId); + fetchReq = new GetMessagesRequest(specificChannel != null ? new List { specificChannel } : careChannels, lastMessageId); fetchReq.Success += delegate (List messages) { var ids = messages.Where(m => m.TargetType == TargetType.Channel).Select(m => m.TargetId).Distinct(); @@ -201,8 +328,6 @@ namespace osu.Game.Overlays if (!string.IsNullOrEmpty(postText) && api.LocalUser.Value != null) { - var currentChannel = careChannels.FirstOrDefault(); - if (currentChannel == null) return; var message = new Message diff --git a/osu.Game/Overlays/Mods/AssistedSection.cs b/osu.Game/Overlays/Mods/AssistedSection.cs index b4263fa309..b3cbb410e4 100644 --- a/osu.Game/Overlays/Mods/AssistedSection.cs +++ b/osu.Game/Overlays/Mods/AssistedSection.cs @@ -16,7 +16,6 @@ namespace osu.Game.Overlays.Mods [BackgroundDependencyLoader] private void load(OsuColour colours) { - ButtonColour = colours.Blue; SelectedColour = colours.BlueLight; } diff --git a/osu.Game/Overlays/Mods/DifficultyIncreaseSection.cs b/osu.Game/Overlays/Mods/DifficultyIncreaseSection.cs index 0a293416dc..fc759eb7d9 100644 --- a/osu.Game/Overlays/Mods/DifficultyIncreaseSection.cs +++ b/osu.Game/Overlays/Mods/DifficultyIncreaseSection.cs @@ -16,7 +16,6 @@ namespace osu.Game.Overlays.Mods [BackgroundDependencyLoader] private void load(OsuColour colours) { - ButtonColour = colours.Yellow; SelectedColour = colours.YellowLight; } diff --git a/osu.Game/Overlays/Mods/DifficultyReductionSection.cs b/osu.Game/Overlays/Mods/DifficultyReductionSection.cs index 3a373e6f09..dd3b5965f3 100644 --- a/osu.Game/Overlays/Mods/DifficultyReductionSection.cs +++ b/osu.Game/Overlays/Mods/DifficultyReductionSection.cs @@ -16,7 +16,6 @@ namespace osu.Game.Overlays.Mods [BackgroundDependencyLoader] private void load(OsuColour colours) { - ButtonColour = colours.Green; SelectedColour = colours.GreenLight; } diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index f380c19d8a..fe1b3b0192 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -19,9 +19,13 @@ using System.Linq; namespace osu.Game.Overlays.Mods { - public class ModButton : FillFlowContainer + + /// + /// Represents a clickable button which can cycle through one of more mods. + /// + public class ModButton : ModButtonEmpty { - private ModIcon foregroundIcon { get; set; } + private ModIcon foregroundIcon; private readonly SpriteText text; private readonly Container iconsContainer; private SampleChannel sampleOn, sampleOff; @@ -51,7 +55,7 @@ namespace osu.Game.Overlays.Mods iconsContainer.RotateTo(Selected ? 5f : 0f, 300, EasingTypes.OutElastic); iconsContainer.ScaleTo(Selected ? 1.1f : 1f, 300, EasingTypes.OutElastic); - foregroundIcon.Colour = Selected ? SelectedColour : ButtonColour; + foregroundIcon.Highlighted = Selected; if (mod != null) displayMod(SelectedMod ?? Mods[0]); @@ -60,23 +64,6 @@ namespace osu.Game.Overlays.Mods public bool Selected => selectedIndex != -1; - private Color4 buttonColour; - public Color4 ButtonColour - { - get - { - return buttonColour; - } - set - { - if (value == buttonColour) return; - buttonColour = value; - foreach (ModIcon icon in iconsContainer.Children) - { - icon.Colour = value; - } - } - } private Color4 selectedColour; public Color4 SelectedColour @@ -127,7 +114,7 @@ namespace osu.Game.Overlays.Mods // the mods from Mod, only multiple if Mod is a MultiMod - public Mod SelectedMod => Mods.ElementAtOrDefault(selectedIndex); + public override Mod SelectedMod => Mods.ElementAtOrDefault(selectedIndex); [BackgroundDependencyLoader] private void load(AudioManager audio) @@ -180,50 +167,35 @@ namespace osu.Game.Overlays.Mods { iconsContainer.Add(new[] { - new ModIcon + new ModIcon(Mods[0]) { Origin = Anchor.Centre, Anchor = Anchor.Centre, AutoSizeAxes = Axes.Both, Position = new Vector2(1.5f), - Colour = ButtonColour }, - foregroundIcon = new ModIcon + foregroundIcon = new ModIcon(Mods[0]) { Origin = Anchor.Centre, Anchor = Anchor.Centre, AutoSizeAxes = Axes.Both, Position = new Vector2(-1.5f), - Colour = ButtonColour }, }); } else { - iconsContainer.Add(foregroundIcon = new ModIcon + iconsContainer.Add(foregroundIcon = new ModIcon(Mod) { Origin = Anchor.Centre, Anchor = Anchor.Centre, AutoSizeAxes = Axes.Both, - Colour = ButtonColour }); } } - protected override void LoadComplete() + public ModButton(Mod mod) { - base.LoadComplete(); - foreach (ModIcon icon in iconsContainer.Children) - icon.Colour = ButtonColour; - } - - public ModButton(Mod m) - { - Direction = FillDirection.Vertical; - Spacing = new Vector2(0f, -5f); - Size = new Vector2(100f); - AlwaysPresent = true; - Children = new Drawable[] { new Container @@ -243,13 +215,14 @@ namespace osu.Game.Overlays.Mods }, text = new OsuSpriteText { + Y = 75, Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, TextSize = 18, }, }; - Mod = m; + Mod = mod; } } } diff --git a/osu.Game/Overlays/Mods/ModButtonEmpty.cs b/osu.Game/Overlays/Mods/ModButtonEmpty.cs new file mode 100644 index 0000000000..638c2a0e47 --- /dev/null +++ b/osu.Game/Overlays/Mods/ModButtonEmpty.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Overlays.Mods +{ + /// + /// A mod button used exclusively for providing an empty space the size of a mod button. + /// + public class ModButtonEmpty : Container + { + public virtual Mod SelectedMod => null; + + public ModButtonEmpty() + { + Size = new Vector2(100f); + AlwaysPresent = true; + } + } +} diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index c2af12f49e..40bd1e8b07 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -11,6 +11,8 @@ using osu.Framework.Input; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; using System; +using System.Linq; +using System.Collections.Generic; namespace osu.Game.Overlays.Mods { @@ -18,7 +20,7 @@ namespace osu.Game.Overlays.Mods { private readonly OsuSpriteText headerLabel; - public FillFlowContainer ButtonsContainer { get; } + public FillFlowContainer ButtonsContainer { get; } public Action Action; protected abstract Key[] ToggleKeys { get; } @@ -36,47 +38,30 @@ namespace osu.Game.Overlays.Mods } } + public IEnumerable SelectedMods => buttons.Select(b => b.SelectedMod).Where(m => m != null); + + public IEnumerable Mods + { + set + { + var modContainers = value.Select(m => + { + if (m == null) + return new ModButtonEmpty(); + else + return new ModButton(m) + { + SelectedColour = selectedColour, + Action = Action, + }; + }).ToArray(); + + ButtonsContainer.Children = modContainers; + buttons = modContainers.OfType().ToArray(); + } + } + private ModButton[] buttons = { }; - public ModButton[] Buttons - { - get - { - return buttons; - } - set - { - if (value == buttons) return; - buttons = value; - - foreach (ModButton button in value) - { - button.ButtonColour = ButtonColour; - button.SelectedColour = selectedColour; - button.Action = Action; - } - - ButtonsContainer.Children = value; - } - } - - private Color4 buttonsBolour = Color4.White; - public Color4 ButtonColour - { - get - { - return buttonsBolour; - } - set - { - if (value == buttonsBolour) return; - buttonsBolour = value; - - foreach (ModButton button in buttons) - { - button.ButtonColour = value; - } - } - } private Color4 selectedColour = Color4.White; public Color4 SelectedColour @@ -91,17 +76,15 @@ namespace osu.Game.Overlays.Mods selectedColour = value; foreach (ModButton button in buttons) - { button.SelectedColour = value; - } } } protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) { var index = Array.IndexOf(ToggleKeys, args.Key); - if (index > -1 && index < Buttons.Length) - Buttons[index].SelectNext(); + if (index > -1 && index < buttons.Length) + buttons[index].SelectNext(); return base.OnKeyDown(state, args); } @@ -109,8 +92,18 @@ namespace osu.Game.Overlays.Mods public void DeselectAll() { foreach (ModButton button in buttons) - { button.Deselect(); + } + + public void DeselectTypes(Type[] modTypes) + { + foreach (var button in buttons) + { + Mod selected = button.SelectedMod; + if (selected == null) continue; + foreach (Type type in modTypes) + if (type.IsInstanceOfType(selected)) + button.Deselect(); } } @@ -127,7 +120,7 @@ namespace osu.Game.Overlays.Mods Position = new Vector2(0f, 0f), Font = @"Exo2.0-Bold" }, - ButtonsContainer = new FillFlowContainer + ButtonsContainer = new FillFlowContainer { AutoSizeAxes = Axes.Both, Origin = Anchor.BottomLeft, diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index dadfb808f7..2840ffd1e4 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -44,7 +44,7 @@ namespace osu.Game.Overlays.Mods var instance = newRuleset.CreateInstance(); foreach (ModSection section in modSectionsContainer.Children) - section.Buttons = instance.GetModsFor(section.ModType).Select(m => new ModButton(m)).ToArray(); + section.Mods = instance.GetModsFor(section.ModType); refreshSelectedMods(); } @@ -103,14 +103,7 @@ namespace osu.Game.Overlays.Mods { if (modTypes.Length == 0) return; foreach (ModSection section in modSectionsContainer.Children) - foreach (ModButton button in section.Buttons) - { - Mod selected = button.SelectedMod; - if (selected == null) continue; - foreach (Type type in modTypes) - if (type.IsInstanceOfType(selected)) - button.Deselect(); - } + section.DeselectTypes(modTypes); } private void modButtonPressed(Mod selectedMod) @@ -122,7 +115,7 @@ namespace osu.Game.Overlays.Mods private void refreshSelectedMods() { - SelectedMods.Value = modSectionsContainer.Children.SelectMany(s => s.Buttons.Select(x => x.SelectedMod).Where(x => x != null)).ToArray(); + SelectedMods.Value = modSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray(); double multiplier = 1.0; bool ranked = true; diff --git a/osu.Game/Overlays/Music/FilterControl.cs b/osu.Game/Overlays/Music/FilterControl.cs index 5b528b7e89..f6188d941c 100644 --- a/osu.Game/Overlays/Music/FilterControl.cs +++ b/osu.Game/Overlays/Music/FilterControl.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Select; using OpenTK; using OpenTK.Graphics; using System; diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 91d35db3bb..e3edaa0ca7 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -315,7 +315,7 @@ namespace osu.Game.Overlays } else { - BeatmapMetadata metadata = beatmap.Beatmap.BeatmapInfo.Metadata; + BeatmapMetadata metadata = beatmap.Metadata; title.Current = localisation.GetUnicodePreference(metadata.TitleUnicode, metadata.Title); artist.Current = localisation.GetUnicodePreference(metadata.ArtistUnicode, metadata.Artist); } diff --git a/osu.Game/Overlays/Options/OptionCheckbox.cs b/osu.Game/Overlays/Options/OptionCheckbox.cs new file mode 100644 index 0000000000..de7b138c3c --- /dev/null +++ b/osu.Game/Overlays/Options/OptionCheckbox.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.Options +{ + public class OptionCheckbox : OptionItem + { + private OsuCheckbox checkbox; + + protected override Drawable CreateControl() => checkbox = new OsuCheckbox(); + + public override string LabelText + { + get { return checkbox.LabelText; } + set { checkbox.LabelText = value; } + } + } +} diff --git a/osu.Game/Overlays/Options/OptionDropdown.cs b/osu.Game/Overlays/Options/OptionDropdown.cs index 6837e71c2c..1427eafe39 100644 --- a/osu.Game/Overlays/Options/OptionDropdown.cs +++ b/osu.Game/Overlays/Options/OptionDropdown.cs @@ -2,45 +2,18 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; -using osu.Framework.Configuration; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Options { - public class OptionDropdown : FillFlowContainer + public class OptionDropdown : OptionItem { - private readonly Dropdown dropdown; - private readonly SpriteText text; + private Dropdown dropdown; - public string LabelText - { - get { return text.Text; } - set - { - text.Text = value; - text.Alpha = !string.IsNullOrEmpty(value) ? 1 : 0; - } - } - - public Bindable Bindable - { - get { return bindable; } - set - { - bindable = value; - dropdown.Current.BindTo(bindable); - } - } - - private Bindable bindable; - - private IEnumerable> items; + private IEnumerable> items = new KeyValuePair[] { }; public IEnumerable> Items { get @@ -55,30 +28,11 @@ namespace osu.Game.Overlays.Options } } - public OptionDropdown() + protected override Drawable CreateControl() => dropdown = new OsuDropdown { - Items = new KeyValuePair[0]; - - Direction = FillDirection.Vertical; - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Children = new Drawable[] - { - text = new OsuSpriteText { - Alpha = 0, - }, - dropdown = new OsuDropdown - { - Margin = new MarginPadding { Top = 5 }, - RelativeSizeAxes = Axes.X, - Items = Items, - } - }; - - dropdown.Current.DisabledChanged += disabled => - { - Alpha = disabled ? 0.3f : 1; - }; - } + Margin = new MarginPadding { Top = 5 }, + RelativeSizeAxes = Axes.X, + Items = Items, + }; } } diff --git a/osu.Game/Overlays/Options/OptionItem.cs b/osu.Game/Overlays/Options/OptionItem.cs new file mode 100644 index 0000000000..2124796089 --- /dev/null +++ b/osu.Game/Overlays/Options/OptionItem.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 OpenTK.Graphics; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Overlays.Options +{ + public abstract class OptionItem : FillFlowContainer, IFilterable + { + protected abstract Drawable CreateControl(); + + protected Drawable Control { get; } + + private IHasCurrentValue controlWithCurrent => Control as IHasCurrentValue; + + private SpriteText text; + + public virtual string LabelText + { + get { return text?.Text ?? string.Empty; } + set + { + if (text == null) + { + // construct lazily for cases where the label is not needed (may be provided by the Control). + Add(text = new OsuSpriteText() { Depth = 1 }); + } + + text.Text = value; + } + } + + // hold a reference to the provided bindable so we don't have to in every options section. + private Bindable bindable; + + public Bindable Bindable + { + get + { + return bindable; + } + + set + { + bindable = value; + controlWithCurrent?.Current.BindTo(bindable); + } + } + + public string[] FilterTerms => new[] { LabelText }; + + public bool MatchingCurrentFilter + { + set + { + // probably needs a better transition. + FadeTo(value ? 1 : 0); + } + } + + protected OptionItem() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Padding = new MarginPadding { Right = 5 }; + + if ((Control = CreateControl()) != null) + { + if (controlWithCurrent != null) + controlWithCurrent.Current.DisabledChanged += disabled => { Colour = disabled ? Color4.Gray : Color4.White; }; + Add(Control); + } + } + } +} diff --git a/osu.Game/Overlays/Options/OptionLabel.cs b/osu.Game/Overlays/Options/OptionLabel.cs index 4b0f1e4ec0..3f3c569f3a 100644 --- a/osu.Game/Overlays/Options/OptionLabel.cs +++ b/osu.Game/Overlays/Options/OptionLabel.cs @@ -2,13 +2,15 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays.Options { - internal class OptionLabel : OsuSpriteText + internal class OptionLabel : OptionItem { + protected override Drawable CreateControl() => null; + [BackgroundDependencyLoader] private void load(OsuColour colour) { diff --git a/osu.Game/Overlays/Options/OptionSlider.cs b/osu.Game/Overlays/Options/OptionSlider.cs index 2d98bc991a..2cceb085a7 100644 --- a/osu.Game/Overlays/Options/OptionSlider.cs +++ b/osu.Game/Overlays/Options/OptionSlider.cs @@ -1,13 +1,9 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Configuration; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Options @@ -17,52 +13,14 @@ namespace osu.Game.Overlays.Options { } - public class OptionSlider : FillFlowContainer + public class OptionSlider : OptionItem where T : struct where U : SliderBar, new() { - private readonly SliderBar slider; - private readonly SpriteText text; - - public string LabelText + protected override Drawable CreateControl() => new U() { - get { return text.Text; } - set - { - text.Text = value; - text.Alpha = string.IsNullOrEmpty(value) ? 0 : 1; - } - } - - private Bindable bindable; - - public Bindable Bindable - { - set - { - bindable = value; - slider.Current.BindTo(bindable); - } - } - - public OptionSlider() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Padding = new MarginPadding { Right = 5 }; - - Children = new Drawable[] - { - text = new OsuSpriteText - { - Alpha = 0, - }, - slider = new U() - { - Margin = new MarginPadding { Top = 5, Bottom = 5 }, - RelativeSizeAxes = Axes.X - } - }; - } + Margin = new MarginPadding { Top = 5, Bottom = 5 }, + RelativeSizeAxes = Axes.X + }; } } diff --git a/osu.Game/Overlays/Options/OptionTextBox.cs b/osu.Game/Overlays/Options/OptionTextBox.cs index 4927122181..498f27796a 100644 --- a/osu.Game/Overlays/Options/OptionTextBox.cs +++ b/osu.Game/Overlays/Options/OptionTextBox.cs @@ -1,22 +1,13 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Configuration; +using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Options { - public class OptionTextBox : OsuTextBox + public class OptionTextBox : OptionItem { - private Bindable bindable; - - public Bindable Bindable - { - set - { - bindable = value; - Current.BindTo(bindable); - } - } + protected override Drawable CreateControl() => new OsuTextBox(); } } diff --git a/osu.Game/Overlays/Options/OptionsHeader.cs b/osu.Game/Overlays/Options/OptionsHeader.cs new file mode 100644 index 0000000000..8b97a607a2 --- /dev/null +++ b/osu.Game/Overlays/Options/OptionsHeader.cs @@ -0,0 +1,108 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using OpenTK.Graphics; + +namespace osu.Game.Overlays.Options +{ + public class OptionsHeader : Container + { + public SearchTextBox SearchTextBox; + + private Box background; + + private readonly Func currentScrollOffset; + + public Action Exit; + + /// A reference to the current scroll position of the ScrollContainer we are contained within. + public OptionsHeader(Func currentScrollOffset) + { + this.currentScrollOffset = currentScrollOffset; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Children = new Drawable[] + { + background = new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = "settings", + TextSize = 40, + Margin = new MarginPadding { + Left = OptionsOverlay.CONTENT_MARGINS, + Top = Toolbar.Toolbar.TOOLTIP_HEIGHT + }, + }, + new OsuSpriteText + { + Colour = colours.Pink, + Text = "Change the way osu! behaves", + TextSize = 18, + Margin = new MarginPadding { + Left = OptionsOverlay.CONTENT_MARGINS, + Bottom = 30 + }, + }, + SearchTextBox = new SearchTextBox + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Width = 0.95f, + Margin = new MarginPadding { + Top = 20, + Bottom = 20 + }, + Exit = () => Exit(), + }, + } + } + }; + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + // the point at which we will start anchoring to the top. + float anchorOffset = SearchTextBox.Y; + + float scrollPosition = currentScrollOffset(); + + // we want to anchor the search field to the top of the screen when scrolling. + Margin = new MarginPadding { Top = Math.Max(0, scrollPosition - anchorOffset) }; + + // we don't want the header to scroll when scrolling beyond the upper extent. + Y = Math.Min(0, scrollPosition); + + // we get darker as scroll progresses + background.Alpha = Math.Min(1, scrollPosition / anchorOffset) * 0.5f; + } + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/Options/OptionsSection.cs b/osu.Game/Overlays/Options/OptionsSection.cs index d5dafad9ba..5e36f79467 100644 --- a/osu.Game/Overlays/Options/OptionsSection.cs +++ b/osu.Game/Overlays/Options/OptionsSection.cs @@ -10,10 +10,12 @@ using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using System.Collections.Generic; +using System.Linq; namespace osu.Game.Overlays.Options { - public abstract class OptionsSection : Container + public abstract class OptionsSection : Container, IHasFilterableChildren { protected FillFlowContainer FlowContent; protected override Container Content => FlowContent; @@ -21,6 +23,16 @@ namespace osu.Game.Overlays.Options public abstract FontAwesome Icon { get; } public abstract string Header { get; } + public IEnumerable FilterableChildren => Children.OfType(); + public string[] FilterTerms => new[] { Header }; + public bool MatchingCurrentFilter + { + set + { + FadeTo(value ? 1 : 0); + } + } + private readonly SpriteText headerLabel; protected OptionsSection() diff --git a/osu.Game/Overlays/Options/OptionsSubsection.cs b/osu.Game/Overlays/Options/OptionsSubsection.cs index 9fd2e8fb1e..ff9e60c273 100644 --- a/osu.Game/Overlays/Options/OptionsSubsection.cs +++ b/osu.Game/Overlays/Options/OptionsSubsection.cs @@ -6,10 +6,12 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Game.Graphics.Sprites; +using System.Collections.Generic; +using System.Linq; namespace osu.Game.Overlays.Options { - public abstract class OptionsSubsection : FillFlowContainer + public abstract class OptionsSubsection : FillFlowContainer, IHasFilterableChildren { protected override Container Content => content; @@ -17,6 +19,16 @@ namespace osu.Game.Overlays.Options protected abstract string Header { get; } + public IEnumerable FilterableChildren => Children.OfType(); + public string[] FilterTerms => new[] { Header }; + public bool MatchingCurrentFilter + { + set + { + FadeTo(value ? 1 : 0); + } + } + protected OptionsSubsection() { RelativeSizeAxes = Axes.X; diff --git a/osu.Game/Overlays/Options/Sections/Audio/MainMenuOptions.cs b/osu.Game/Overlays/Options/Sections/Audio/MainMenuOptions.cs index 9de74e1b02..b2d1235b97 100644 --- a/osu.Game/Overlays/Options/Sections/Audio/MainMenuOptions.cs +++ b/osu.Game/Overlays/Options/Sections/Audio/MainMenuOptions.cs @@ -3,7 +3,6 @@ using osu.Framework.Allocation; using osu.Game.Configuration; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Options.Sections.Audio { @@ -16,12 +15,12 @@ namespace osu.Game.Overlays.Options.Sections.Audio { Children = new[] { - new OsuCheckbox + new OptionCheckbox { LabelText = "Interface voices", Bindable = config.GetBindable(OsuConfig.MenuVoice) }, - new OsuCheckbox + new OptionCheckbox { LabelText = "osu! music theme", Bindable = config.GetBindable(OsuConfig.MenuMusic) @@ -29,4 +28,4 @@ namespace osu.Game.Overlays.Options.Sections.Audio }; } } -} \ No newline at end of file +} diff --git a/osu.Game/Overlays/Options/Sections/Debug/GeneralOptions.cs b/osu.Game/Overlays/Options/Sections/Debug/GeneralOptions.cs index 34901e1dac..9258b8fbeb 100644 --- a/osu.Game/Overlays/Options/Sections/Debug/GeneralOptions.cs +++ b/osu.Game/Overlays/Options/Sections/Debug/GeneralOptions.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Options.Sections.Debug { @@ -17,7 +16,7 @@ namespace osu.Game.Overlays.Options.Sections.Debug { Children = new Drawable[] { - new OsuCheckbox + new OptionCheckbox { LabelText = "Bypass caching", Bindable = config.GetBindable(FrameworkDebugConfig.BypassCaching) diff --git a/osu.Game/Overlays/Options/Sections/Gameplay/GeneralOptions.cs b/osu.Game/Overlays/Options/Sections/Gameplay/GeneralOptions.cs index c278b59a51..2598d95949 100644 --- a/osu.Game/Overlays/Options/Sections/Gameplay/GeneralOptions.cs +++ b/osu.Game/Overlays/Options/Sections/Gameplay/GeneralOptions.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Configuration; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Options.Sections.Gameplay { @@ -22,12 +21,12 @@ namespace osu.Game.Overlays.Options.Sections.Gameplay LabelText = "Background dim", Bindable = config.GetBindable(OsuConfig.DimLevel) }, - new OsuCheckbox + new OptionCheckbox { LabelText = "Show score overlay", Bindable = config.GetBindable(OsuConfig.ShowInterface) }, - new OsuCheckbox + new OptionCheckbox { LabelText = "Always show key overlay", Bindable = config.GetBindable(OsuConfig.KeyOverlay) diff --git a/osu.Game/Overlays/Options/Sections/General/LanguageOptions.cs b/osu.Game/Overlays/Options/Sections/General/LanguageOptions.cs index 7bc1a54455..2778f2567d 100644 --- a/osu.Game/Overlays/Options/Sections/General/LanguageOptions.cs +++ b/osu.Game/Overlays/Options/Sections/General/LanguageOptions.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Options.Sections.General { @@ -17,7 +16,7 @@ namespace osu.Game.Overlays.Options.Sections.General { Children = new Drawable[] { - new OsuCheckbox + new OptionCheckbox { LabelText = "Prefer metadata in original language", Bindable = frameworkConfig.GetBindable(FrameworkConfig.ShowUnicode) diff --git a/osu.Game/Overlays/Options/Sections/General/LoginOptions.cs b/osu.Game/Overlays/Options/Sections/General/LoginOptions.cs index a5e38dd284..a3612a6396 100644 --- a/osu.Game/Overlays/Options/Sections/General/LoginOptions.cs +++ b/osu.Game/Overlays/Options/Sections/General/LoginOptions.cs @@ -132,12 +132,12 @@ namespace osu.Game.Overlays.Options.Sections.General TabbableContentContainer = this, OnCommit = (sender, newText) => performLogin() }, - new OsuCheckbox + new OptionCheckbox { LabelText = "Remember username", Bindable = config.GetBindable(OsuConfig.SaveUsername), }, - new OsuCheckbox + new OptionCheckbox { LabelText = "Stay logged in", Bindable = config.GetBindable(OsuConfig.SavePassword), diff --git a/osu.Game/Overlays/Options/Sections/Graphics/DetailOptions.cs b/osu.Game/Overlays/Options/Sections/Graphics/DetailOptions.cs index d8906d74ba..6503a7ea90 100644 --- a/osu.Game/Overlays/Options/Sections/Graphics/DetailOptions.cs +++ b/osu.Game/Overlays/Options/Sections/Graphics/DetailOptions.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Configuration; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Options.Sections.Graphics { @@ -17,12 +16,12 @@ namespace osu.Game.Overlays.Options.Sections.Graphics { Children = new Drawable[] { - new OsuCheckbox + new OptionCheckbox { LabelText = "Snaking in sliders", Bindable = config.GetBindable(OsuConfig.SnakingInSliders) }, - new OsuCheckbox + new OptionCheckbox { LabelText = "Snaking out sliders", Bindable = config.GetBindable(OsuConfig.SnakingOutSliders) @@ -30,4 +29,4 @@ namespace osu.Game.Overlays.Options.Sections.Graphics }; } } -} \ No newline at end of file +} diff --git a/osu.Game/Overlays/Options/Sections/Graphics/LayoutOptions.cs b/osu.Game/Overlays/Options/Sections/Graphics/LayoutOptions.cs index aed39ca764..1b4b0b3c7d 100644 --- a/osu.Game/Overlays/Options/Sections/Graphics/LayoutOptions.cs +++ b/osu.Game/Overlays/Options/Sections/Graphics/LayoutOptions.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Options.Sections.Graphics { @@ -29,7 +28,7 @@ namespace osu.Game.Overlays.Options.Sections.Graphics LabelText = "Screen mode", Bindable = config.GetBindable(FrameworkConfig.WindowMode), }, - new OsuCheckbox + new OptionCheckbox { LabelText = "Letterboxing", Bindable = letterboxing, @@ -64,4 +63,4 @@ namespace osu.Game.Overlays.Options.Sections.Graphics } } } -} \ No newline at end of file +} diff --git a/osu.Game/Overlays/Options/Sections/Graphics/MainMenuOptions.cs b/osu.Game/Overlays/Options/Sections/Graphics/MainMenuOptions.cs index 83a2a382ad..6ebb8f263d 100644 --- a/osu.Game/Overlays/Options/Sections/Graphics/MainMenuOptions.cs +++ b/osu.Game/Overlays/Options/Sections/Graphics/MainMenuOptions.cs @@ -3,7 +3,6 @@ using osu.Framework.Allocation; using osu.Game.Configuration; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Options.Sections.Graphics { @@ -16,7 +15,7 @@ namespace osu.Game.Overlays.Options.Sections.Graphics { Children = new[] { - new OsuCheckbox + new OptionCheckbox { LabelText = "Parallax", Bindable = config.GetBindable(OsuConfig.MenuParallax) @@ -24,4 +23,4 @@ namespace osu.Game.Overlays.Options.Sections.Graphics }; } } -} \ No newline at end of file +} diff --git a/osu.Game/Overlays/Options/Sections/Graphics/RendererOptions.cs b/osu.Game/Overlays/Options/Sections/Graphics/RendererOptions.cs index 58bf2b7996..f11c18d3b4 100644 --- a/osu.Game/Overlays/Options/Sections/Graphics/RendererOptions.cs +++ b/osu.Game/Overlays/Options/Sections/Graphics/RendererOptions.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Game.Configuration; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Options.Sections.Graphics { @@ -25,7 +24,7 @@ namespace osu.Game.Overlays.Options.Sections.Graphics LabelText = "Frame limiter", Bindable = config.GetBindable(FrameworkConfig.FrameSync) }, - new OsuCheckbox + new OptionCheckbox { LabelText = "Show FPS", Bindable = osuConfig.GetBindable(OsuConfig.ShowFpsDisplay) @@ -33,4 +32,4 @@ namespace osu.Game.Overlays.Options.Sections.Graphics }; } } -} \ No newline at end of file +} diff --git a/osu.Game/Overlays/Options/Sections/Input/MouseOptions.cs b/osu.Game/Overlays/Options/Sections/Input/MouseOptions.cs index 12789aa0ec..b4ce11e2bc 100644 --- a/osu.Game/Overlays/Options/Sections/Input/MouseOptions.cs +++ b/osu.Game/Overlays/Options/Sections/Input/MouseOptions.cs @@ -24,12 +24,12 @@ namespace osu.Game.Overlays.Options.Sections.Input LabelText = "Confine mouse cursor", Bindable = config.GetBindable(FrameworkConfig.ConfineMouseMode), }, - new OsuCheckbox + new OptionCheckbox { LabelText = "Disable mouse wheel during gameplay", Bindable = osuConfig.GetBindable(OsuConfig.MouseDisableWheel) }, - new OsuCheckbox + new OptionCheckbox { LabelText = "Disable mouse buttons during gameplay", Bindable = osuConfig.GetBindable(OsuConfig.MouseDisableButtons) diff --git a/osu.Game/Overlays/OptionsOverlay.cs b/osu.Game/Overlays/OptionsOverlay.cs index dcbce80c69..6d1a3a64fc 100644 --- a/osu.Game/Overlays/OptionsOverlay.cs +++ b/osu.Game/Overlays/OptionsOverlay.cs @@ -10,9 +10,8 @@ using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Options; using System; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Overlays.Options.Sections; +using osu.Framework.Input; namespace osu.Game.Overlays { @@ -32,6 +31,13 @@ namespace osu.Game.Overlays private Sidebar sidebar; private SidebarButton[] sidebarButtons; private OptionsSection[] sections; + + private OptionsHeader header; + + private OptionsFooter footer; + + private SearchContainer searchContainer; + private float lastKnownScroll; public OptionsOverlay() @@ -41,7 +47,7 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuGame game, OsuColour colours) + private void load(OsuGame game) { sections = new OptionsSection[] { @@ -69,39 +75,20 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Y, Width = width, Margin = new MarginPadding { Left = SIDEBAR_WIDTH }, - Children = new[] + Children = new Drawable[] { - new FillFlowContainer + searchContainer = new SearchContainer { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Direction = FillDirection.Vertical, - - Children = new Drawable[] - { - new OsuSpriteText - { - Text = "settings", - TextSize = 40, - Margin = new MarginPadding { Left = CONTENT_MARGINS, Top = Toolbar.Toolbar.TOOLTIP_HEIGHT }, - }, - new OsuSpriteText - { - Colour = colours.Pink, - Text = "Change the way osu! behaves", - TextSize = 18, - Margin = new MarginPadding { Left = CONTENT_MARGINS, Bottom = 30 }, - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Children = sections, - }, - new OptionsFooter() - } - } + Children = sections, + }, + footer = new OptionsFooter(), + header = new OptionsHeader(() => scrollContainer.Current) + { + Exit = Hide, + }, } }, sidebar = new Sidebar @@ -118,9 +105,20 @@ namespace osu.Game.Overlays } }; + header.SearchTextBox.Current.ValueChanged += newValue => searchContainer.SearchTerm = newValue; + scrollContainer.Padding = new MarginPadding { Top = game?.Toolbar.DrawHeight ?? 0 }; } + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + //we need to update these manually because we can't put the OptionsHeader inside the SearchContainer (due to its anchoring). + searchContainer.Y = header.DrawHeight; + footer.Y = searchContainer.Y + searchContainer.DrawHeight; + } + protected override void Update() { base.Update(); @@ -157,6 +155,8 @@ namespace osu.Game.Overlays scrollContainer.MoveToX(0, TRANSITION_LENGTH, EasingTypes.OutQuint); sidebar.MoveToX(0, TRANSITION_LENGTH, EasingTypes.OutQuint); FadeTo(1, TRANSITION_LENGTH / 2); + + header.SearchTextBox.HoldFocus = true; } protected override void PopOut() @@ -166,6 +166,15 @@ namespace osu.Game.Overlays scrollContainer.MoveToX(-width, TRANSITION_LENGTH, EasingTypes.OutQuint); sidebar.MoveToX(-SIDEBAR_WIDTH, TRANSITION_LENGTH, EasingTypes.OutQuint); FadeTo(0, TRANSITION_LENGTH / 2); + + header.SearchTextBox.HoldFocus = false; + header.SearchTextBox.TriggerFocusLost(); + } + + protected override bool OnFocus(InputState state) + { + header.SearchTextBox.TriggerFocus(state); + return false; } } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs index ca612662e1..39909b8d5b 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs @@ -17,7 +17,6 @@ namespace osu.Game.Overlays.Toolbar private void load(ChatOverlay chat) { StateContainer = chat; - Action = chat.ToggleVisibility; } } } \ No newline at end of file diff --git a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs index 725af09ee7..82599b9a0d 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs @@ -17,7 +17,6 @@ namespace osu.Game.Overlays.Toolbar private void load(MusicController music) { StateContainer = music; - Action = music.ToggleVisibility; } } } \ No newline at end of file diff --git a/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs b/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs index 4da87a0568..5126f6a2a4 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs @@ -22,7 +22,6 @@ namespace osu.Game.Overlays.Toolbar private void load(NotificationManager notificationManager) { StateContainer = notificationManager; - Action = notificationManager.ToggleVisibility; } } } \ No newline at end of file diff --git a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs index 023d9cfea7..fa668bad60 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs @@ -21,6 +21,7 @@ namespace osu.Game.Overlays.Toolbar set { stateContainer = value; + Action = stateContainer.ToggleVisibility; stateContainer.StateChanged += stateChanged; } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs index 9f8170b604..ae54b98aca 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs @@ -19,7 +19,6 @@ namespace osu.Game.Overlays.Toolbar private void load(OptionsOverlay options) { StateContainer = options; - Action = options.ToggleVisibility; } } } \ No newline at end of file diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index a2846c1d2f..c00847b7d8 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -21,6 +21,11 @@ namespace osu.Game.Rulesets.Mods /// public virtual FontAwesome Icon => FontAwesome.fa_question; + /// + /// The type of this mod. + /// + public virtual ModType Type => ModType.Special; + /// /// The user readable description of this mod. /// diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index 377a4c2180..1aab56a9fc 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -11,6 +11,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Double Time"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_doubletime; + public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Zoooooooooom"; public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModHalfTime) }; diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index bef3f04af3..8eef9c70d6 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -10,6 +10,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Easy"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_easy; + public override ModType Type => ModType.DifficultyReduction; public override string Description => "Reduces overall difficulty - larger circles, more forgiving HP drain, less accuracy required."; public override double ScoreMultiplier => 0.5; public override bool Ranked => true; diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 63c534dc7d..b5ad859172 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Flashlight"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_flashlight; + public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Restricted view area."; public override bool Ranked => true; } diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 235fc7ad76..207a6ec2a1 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -11,6 +11,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Half Time"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_halftime; + public override ModType Type => ModType.DifficultyReduction; public override string Description => "Less zoom"; public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModDoubleTime) }; diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs index b729b5ae15..2516c02526 100644 --- a/osu.Game/Rulesets/Mods/ModHardRock.cs +++ b/osu.Game/Rulesets/Mods/ModHardRock.cs @@ -10,6 +10,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Hard Rock"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_hardrock; + public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Everything just got a bit harder..."; public override Type[] IncompatibleMods => new[] { typeof(ModEasy) }; } diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index 12c788ce54..89b9b3b62d 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Hidden"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_hidden; + public override ModType Type => ModType.DifficultyIncrease; public override bool Ranked => true; } } \ No newline at end of file diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs index 0c8726bbc1..613e1e1d4d 100644 --- a/osu.Game/Rulesets/Mods/ModNoFail.cs +++ b/osu.Game/Rulesets/Mods/ModNoFail.cs @@ -10,6 +10,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "NoFail"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_nofail; + public override ModType Type => ModType.DifficultyReduction; public override string Description => "You can't fail, no matter what."; public override double ScoreMultiplier => 0.5; public override bool Ranked => true; diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index a7dcbbc9ed..2bf0278046 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -10,6 +10,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Sudden Death"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_suddendeath; + public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Miss a note and fail."; public override double ScoreMultiplier => 1; public override bool Ranked => true; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index a300eeab31..c5dbc27fd3 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -12,12 +11,23 @@ using Container = osu.Framework.Graphics.Containers.Container; using osu.Game.Rulesets.Objects.Types; using OpenTK.Graphics; using osu.Game.Audio; +using System.Linq; namespace osu.Game.Rulesets.Objects.Drawables { - public abstract class DrawableHitObject : Container, IStateful + public abstract class DrawableHitObject : Container + where TObject : HitObject where TJudgement : Judgement { + public event Action> OnJudgement; + + public TObject HitObject; + + /// + /// The colour used for various elements of this DrawableHitObject. + /// + public virtual Color4 AccentColour { get; set; } + public override bool HandleInput => Interactive; public bool Interactive = true; @@ -56,14 +66,6 @@ namespace osu.Game.Rulesets.Objects.Drawables Samples.ForEach(s => s?.Play()); } - [BackgroundDependencyLoader] - private void load() - { - //we may be setting a custom judgement in test cases or what not. - if (Judgement == null) - Judgement = CreateJudgement(); - } - protected override void LoadComplete() { base.LoadComplete(); @@ -71,20 +73,11 @@ namespace osu.Game.Rulesets.Objects.Drawables //force application of the state that was set before we loaded. UpdateState(State); } - } - - public abstract class DrawableHitObject : DrawableHitObject - where TObject : HitObject - where TJudgement : Judgement - { - public event Action> OnJudgement; - - public TObject HitObject; /// - /// The colour used for various elements of this DrawableHitObject. + /// Whether this hit object and all of its nested hit objects have been judged. /// - public Color4 AccentColour { get; protected set; } + public bool Judged => (Judgement?.Result ?? HitResult.None) != HitResult.None && (NestedHitObjects?.All(h => h.Judged) ?? true); protected DrawableHitObject(TObject hitObject) { @@ -97,7 +90,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// Whether a hit was processed. protected bool UpdateJudgement(bool userTriggered) { - IPartialJudgement partial = Judgement as IPartialJudgement; + var partial = Judgement as IPartialJudgement; // Never re-process non-partial hits if (Judgement.Result != HitResult.None && partial == null) @@ -166,10 +159,13 @@ namespace osu.Game.Rulesets.Objects.Drawables channel.Volume.Value = sample.Volume; Samples.Add(channel); } + + //we may be setting a custom judgement in test cases or what not. + if (Judgement == null) + Judgement = CreateJudgement(); } private List> nestedHitObjects; - protected IEnumerable> NestedHitObjects => nestedHitObjects; protected void AddNested(DrawableHitObject h) diff --git a/osu.Game/Rulesets/UI/HitRenderer.cs b/osu.Game/Rulesets/UI/HitRenderer.cs index 69e0e73664..3ec5c353a0 100644 --- a/osu.Game/Rulesets/UI/HitRenderer.cs +++ b/osu.Game/Rulesets/UI/HitRenderer.cs @@ -187,17 +187,19 @@ namespace osu.Game.Rulesets.UI public sealed override bool ProvidingUserCursor => !HasReplayLoaded && Playfield.ProvidingUserCursor; - protected override Container Content => content; - protected override bool AllObjectsJudged => Playfield.HitObjects.Children.All(h => h.Judgement.Result != HitResult.None); + public override IEnumerable Objects => Beatmap.HitObjects; + + protected override bool AllObjectsJudged => drawableObjects.All(h => h.Judged); /// /// The playfield. /// protected Playfield Playfield; + protected override Container Content => content; private readonly Container content; - public override IEnumerable Objects => Beatmap.HitObjects; + private readonly List> drawableObjects = new List>(); protected HitRenderer(WorkingBeatmap beatmap) : base(beatmap) @@ -224,6 +226,8 @@ namespace osu.Game.Rulesets.UI private void loadObjects() { + drawableObjects.Capacity = Beatmap.HitObjects.Count; + foreach (TObject h in Beatmap.HitObjects) { var drawableObject = GetVisualRepresentation(h); @@ -233,6 +237,7 @@ namespace osu.Game.Rulesets.UI drawableObject.OnJudgement += onJudgement; + drawableObjects.Add(drawableObject); Playfield.Add(drawableObject); } @@ -279,7 +284,7 @@ namespace osu.Game.Rulesets.UI protected abstract Playfield CreatePlayfield(); } - public class BeatmapInvalidForRulesetException : Exception + public class BeatmapInvalidForRulesetException : ArgumentException { public BeatmapInvalidForRulesetException(string text) : base(text) diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 8301796c1f..b23028098f 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -1,10 +1,13 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using OpenTK.Graphics; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; +using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.UI { @@ -13,72 +16,88 @@ namespace osu.Game.Rulesets.UI private readonly TextAwesome modIcon; private readonly TextAwesome background; - private float iconSize = 80; - public float IconSize - { - get - { - return iconSize; - } - set - { - iconSize = value; - reapplySize(); - } - } - - public new Color4 Colour - { - get - { - return background.Colour; - } - set - { - background.Colour = value; - } - } + private const float icon_size = 80; public FontAwesome Icon { - get - { - return modIcon.Icon; - } - set - { - modIcon.Icon = value; - } + get { return modIcon.Icon; } + set { modIcon.Icon = value; } } - private void reapplySize() - { - background.TextSize = iconSize; - modIcon.TextSize = iconSize - 35; - } + private readonly ModType type; - public ModIcon() + public ModIcon(Mod mod) { + if (mod == null) throw new ArgumentNullException(nameof(mod)); + + type = mod.Type; + Children = new Drawable[] { background = new TextAwesome { Origin = Anchor.Centre, Anchor = Anchor.Centre, + TextSize = icon_size, Icon = FontAwesome.fa_osu_mod_bg, Shadow = true, - TextSize = 20 }, modIcon = new TextAwesome { Origin = Anchor.Centre, Anchor = Anchor.Centre, Colour = OsuColour.Gray(84), - TextSize = 20 + TextSize = icon_size - 35, + Icon = mod.Icon }, }; + } - reapplySize(); + private Color4 backgroundColour; + private Color4 highlightedColour; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + switch (type) + { + default: + case ModType.DifficultyIncrease: + backgroundColour = colours.Yellow; + highlightedColour = colours.YellowLight; + break; + case ModType.DifficultyReduction: + backgroundColour = colours.Green; + highlightedColour = colours.GreenLight; + break; + case ModType.Special: + backgroundColour = colours.Blue; + highlightedColour = colours.BlueLight; + break; + } + + applyStyle(); + } + + private bool highlighted; + + public bool Highlighted + { + get + { + return highlighted; + } + + set + { + highlighted = value; + applyStyle(); + } + } + + private void applyStyle() + { + background.Colour = highlighted ? highlightedColour : backgroundColour; } } -} \ No newline at end of file +} diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 0586c0385a..612569a9ae 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.UI /// /// The HitObjects contained in this Playfield. /// - public HitObjectContainer> HitObjects; + protected HitObjectContainer> HitObjects; internal Container ScaledContent; diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index a23efcd9d4..c8a00e0671 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -1,13 +1,16 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.Threading.Tasks; +using OpenTK; +using OpenTK.Input; using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Configuration; using osu.Framework.Graphics; +using osu.Framework.Input; using osu.Framework.MathUtils; using osu.Framework.Screens; -using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Graphics.Containers; @@ -16,14 +19,8 @@ using osu.Game.Screens.Charts; using osu.Game.Screens.Direct; using osu.Game.Screens.Edit; using osu.Game.Screens.Multiplayer; -using OpenTK; using osu.Game.Screens.Select; using osu.Game.Screens.Tournament; -using osu.Framework.Input; -using OpenTK.Input; -using System.Threading.Tasks; -using System.Collections.Generic; -using System.Linq; namespace osu.Game.Screens.Menu { @@ -65,7 +62,6 @@ namespace osu.Game.Screens.Menu private Bindable menuMusic; private TrackManager trackManager; - private WorkingBeatmap song; [BackgroundDependencyLoader] private void load(OsuGame game, OsuConfigManager config, BeatmapDatabase beatmaps) @@ -76,11 +72,15 @@ namespace osu.Game.Screens.Menu if (!menuMusic) { trackManager = game.Audio.Track; - List choosableBeatmapSets = beatmaps.Query().ToList(); - if (choosableBeatmapSets.Count > 0) + + var query = beatmaps.Query().Where(b => !b.DeletePending); + int count = query.Count(); + + if (count > 0) { - song = beatmaps.GetWorkingBeatmap(beatmaps.GetWithChildren(choosableBeatmapSets[RNG.Next(0, choosableBeatmapSets.Count - 1)].ID).Beatmaps[0]); - Beatmap = song; + var beatmap = query.ElementAt(RNG.Next(0, count - 1)); + beatmaps.GetChildren(beatmap); + Beatmap = beatmaps.GetWorkingBeatmap(beatmap.Beatmaps[0]); } } @@ -106,15 +106,15 @@ namespace osu.Game.Screens.Menu { base.OnEntering(last); buttons.FadeInFromZero(500); - if (last is Intro && song != null) + if (last is Intro && Beatmap != null) { Task.Run(() => { - trackManager.SetExclusive(song.Track); - song.Track.Seek(song.Beatmap.Metadata.PreviewTime); - if (song.Beatmap.Metadata.PreviewTime == -1) - song.Track.Seek(song.Track.Length * 0.4f); - song.Track.Start(); + trackManager.SetExclusive(Beatmap.Track); + Beatmap.Track.Seek(Beatmap.Metadata.PreviewTime); + if (Beatmap.Metadata.PreviewTime == -1) + Beatmap.Track.Seek(Beatmap.Track.Length * 0.4f); + Beatmap.Track.Start(); }); } } diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 29f6cb2967..f3fffedd43 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Menu /// public class OsuLogo : Container { - public Color4 OsuPink = OsuColour.FromHex(@"e967a1"); + public readonly Color4 OsuPink = OsuColour.FromHex(@"e967a1"); private readonly Sprite logo; private readonly CircularContainer logoContainer; diff --git a/osu.Game/Screens/Multiplayer/MatchCreate.cs b/osu.Game/Screens/Multiplayer/MatchCreate.cs index 54800c82f3..f28261fa7f 100644 --- a/osu.Game/Screens/Multiplayer/MatchCreate.cs +++ b/osu.Game/Screens/Multiplayer/MatchCreate.cs @@ -11,5 +11,10 @@ namespace osu.Game.Screens.Multiplayer protected override IEnumerable PossibleChildren => new[] { typeof(Match) }; + + public MatchCreate() + { + ValidForResume = false; + } } } diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 0b3ecb4f5a..16bdd6132f 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -7,6 +7,7 @@ using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Graphics.Containers; +using OpenTK; namespace osu.Game.Screens { @@ -96,6 +97,9 @@ namespace osu.Game.Screens } else if (bg != null) { + // this makes up for the fact our padding changes when the global toolbar is visible. + bg.Scale = new Vector2(1.06f); + AddInternal(new ParallaxContainer { Depth = float.MaxValue, diff --git a/osu.Game/Rulesets/UI/ComboCounter.cs b/osu.Game/Screens/Play/HUD/ComboCounter.cs similarity index 96% rename from osu.Game/Rulesets/UI/ComboCounter.cs rename to osu.Game/Screens/Play/HUD/ComboCounter.cs index d21059cbdb..a130bc2eab 100644 --- a/osu.Game/Rulesets/UI/ComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ComboCounter.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics.Transforms; using osu.Framework.MathUtils; using osu.Game.Graphics.Sprites; -namespace osu.Game.Rulesets.UI +namespace osu.Game.Screens.Play.HUD { public abstract class ComboCounter : Container { diff --git a/osu.Game/Rulesets/UI/ComboResultCounter.cs b/osu.Game/Screens/Play/HUD/ComboResultCounter.cs similarity index 95% rename from osu.Game/Rulesets/UI/ComboResultCounter.cs rename to osu.Game/Screens/Play/HUD/ComboResultCounter.cs index 4b19b2c1ff..a1a166f944 100644 --- a/osu.Game/Rulesets/UI/ComboResultCounter.cs +++ b/osu.Game/Screens/Play/HUD/ComboResultCounter.cs @@ -1,13 +1,13 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Transforms; using osu.Framework.MathUtils; using osu.Game.Graphics.UserInterface; -using System; -namespace osu.Game.Rulesets.UI +namespace osu.Game.Screens.Play.HUD { /// /// Used to display combo with a roll-up animation in results screen. diff --git a/osu.Game/Rulesets/UI/HealthDisplay.cs b/osu.Game/Screens/Play/HUD/HealthDisplay.cs similarity index 91% rename from osu.Game/Rulesets/UI/HealthDisplay.cs rename to osu.Game/Screens/Play/HUD/HealthDisplay.cs index 5c6b9d2fe3..a146172085 100644 --- a/osu.Game/Rulesets/UI/HealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HealthDisplay.cs @@ -4,7 +4,7 @@ using osu.Framework.Configuration; using osu.Framework.Graphics.Containers; -namespace osu.Game.Rulesets.UI +namespace osu.Game.Screens.Play.HUD { public abstract class HealthDisplay : Container { diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs new file mode 100644 index 0000000000..1b67d8dc66 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -0,0 +1,105 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; +using OpenTK; +using osu.Framework.Input; + +namespace osu.Game.Screens.Play.HUD +{ + public class ModDisplay : Container, IHasCurrentValue> + { + private readonly Bindable> mods = new Bindable>(); + + public Bindable> Current => mods; + + private readonly FillFlowContainer iconsContainer; + + public ModDisplay() + { + Children = new Drawable[] + { + iconsContainer = new IconFlow + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { Left = 10, Right = 10 }, + }, + new OsuSpriteText + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.TopCentre, + Text = @"/ UNRANKED /", + Font = @"Venera", + TextSize = 12, + } + }; + + mods.ValueChanged += mods => + { + iconsContainer.Clear(); + foreach (Mod mod in mods) + { + iconsContainer.Add(new ModIcon(mod) + { + AutoSizeAxes = Axes.Both, + Scale = new Vector2(0.6f), + + }); + } + + if (IsLoaded) + appearTransform(); + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + appearTransform(); + } + + private void appearTransform() + { + iconsContainer.Flush(); + iconsContainer.FadeInFromZero(1000, EasingTypes.OutQuint); + expand(); + using (iconsContainer.BeginDelayedSequence(1200)) + contract(); + } + + private void expand() => iconsContainer.TransformSpacingTo(new Vector2(5, 0), 500, EasingTypes.OutQuint); + + private void contract() => iconsContainer.TransformSpacingTo(new Vector2(-25, 0), 500, EasingTypes.OutQuint); + + protected override bool OnHover(InputState state) + { + expand(); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + contract(); + base.OnHoverLost(state); + } + + private class IconFlow : FillFlowContainer + { + // just reverses the depth of flow contents. + protected override IComparer DepthComparer => new ReverseCreationOrderDepthComparer(); + protected override IEnumerable FlowingChildren => base.FlowingChildren.Reverse(); + } + } +} diff --git a/osu.Game/Rulesets/UI/StandardComboCounter.cs b/osu.Game/Screens/Play/HUD/StandardComboCounter.cs similarity index 96% rename from osu.Game/Rulesets/UI/StandardComboCounter.cs rename to osu.Game/Screens/Play/HUD/StandardComboCounter.cs index ad05c83839..525e52d207 100644 --- a/osu.Game/Rulesets/UI/StandardComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/StandardComboCounter.cs @@ -3,7 +3,7 @@ using OpenTK; -namespace osu.Game.Rulesets.UI +namespace osu.Game.Screens.Play.HUD { /// /// Uses the 'x' symbol and has a pop-out effect while rolling over. diff --git a/osu.Game/Rulesets/UI/StandardHealthDisplay.cs b/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs similarity index 96% rename from osu.Game/Rulesets/UI/StandardHealthDisplay.cs rename to osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs index 3d9a5489dc..7ba5dfe1b7 100644 --- a/osu.Game/Rulesets/UI/StandardHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs @@ -1,8 +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 OpenTK.Graphics; +using System; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -10,9 +9,10 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; -using System; +using OpenTK; +using OpenTK.Graphics; -namespace osu.Game.Rulesets.UI +namespace osu.Game.Screens.Play.HUD { public class StandardHealthDisplay : HealthDisplay, IHasAccentColour { diff --git a/osu.Game/Rulesets/UI/HudOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs similarity index 85% rename from osu.Game/Rulesets/UI/HudOverlay.cs rename to osu.Game/Screens/Play/HUDOverlay.cs index 47cf157732..12e2cb197e 100644 --- a/osu.Game/Rulesets/UI/HudOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -5,18 +5,19 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Play; -using osu.Game.Rulesets.Scoring; -using osu.Framework.Input; -using OpenTK.Input; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play.HUD; +using OpenTK.Input; -namespace osu.Game.Rulesets.UI +namespace osu.Game.Screens.Play { - public abstract class HudOverlay : Container + public abstract class HUDOverlay : Container { private const int duration = 100; @@ -27,6 +28,7 @@ namespace osu.Game.Rulesets.UI public readonly RollingCounter AccuracyCounter; public readonly HealthDisplay HealthDisplay; public readonly SongProgress Progress; + public readonly ModDisplay ModDisplay; private Bindable showKeyCounter; private Bindable showHud; @@ -39,8 +41,9 @@ namespace osu.Game.Rulesets.UI protected abstract ScoreCounter CreateScoreCounter(); protected abstract HealthDisplay CreateHealthDisplay(); protected abstract SongProgress CreateProgress(); + protected abstract ModDisplay CreateModsContainer(); - protected HudOverlay() + protected HUDOverlay() { RelativeSizeAxes = Axes.Both; @@ -56,6 +59,7 @@ namespace osu.Game.Rulesets.UI AccuracyCounter = CreateAccuracyCounter(), HealthDisplay = CreateHealthDisplay(), Progress = CreateProgress(), + ModDisplay = CreateModsContainer(), } }); } @@ -105,6 +109,11 @@ namespace osu.Game.Rulesets.UI public virtual void BindHitRenderer(HitRenderer hitRenderer) { hitRenderer.InputManager.Add(KeyCounter.GetReceptor()); + + // in the case a replay isn't loaded, we want some elements to only appear briefly. + if (!hitRenderer.HasReplayLoaded) + using (ModDisplay.BeginDelayedSequence(2000)) + ModDisplay.FadeOut(200); } protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 37b4cf5b45..7b910c11e5 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -72,7 +72,7 @@ namespace osu.Game.Screens.Play private Container hitRendererContainer; - private HudOverlay hudOverlay; + private HUDOverlay hudOverlay; private PauseOverlay pauseOverlay; private FailOverlay failOverlay; @@ -90,7 +90,7 @@ namespace osu.Game.Screens.Play Beatmap = beatmaps.GetWorkingBeatmap(BeatmapInfo, withStoryboard: true); if (Beatmap?.Beatmap == null) - throw new Exception("Beatmap was not loaded"); + throw new InvalidOperationException("Beatmap was not loaded"); ruleset = osu?.Ruleset.Value ?? Beatmap.BeatmapInfo.Ruleset; rulesetInstance = ruleset.CreateInstance(); @@ -109,7 +109,7 @@ namespace osu.Game.Screens.Play } if (!HitRenderer.Objects.Any()) - throw new Exception("Beatmap contains no hit objects!"); + throw new InvalidOperationException("Beatmap contains no hit objects!"); } catch (Exception e) { @@ -154,7 +154,7 @@ namespace osu.Game.Screens.Play scoreProcessor = HitRenderer.CreateScoreProcessor(); - hudOverlay = new StandardHudOverlay() + hudOverlay = new StandardHUDOverlay() { Anchor = Anchor.Centre, Origin = Anchor.Centre @@ -169,6 +169,8 @@ namespace osu.Game.Screens.Play hudOverlay.Progress.AllowSeeking = HitRenderer.HasReplayLoaded; hudOverlay.Progress.OnSeek = pos => decoupledClock.Seek(pos); + hudOverlay.ModDisplay.Current.BindTo(Beatmap.Mods); + //bind HitRenderer to ScoreProcessor and ourselves (for a pass situation) HitRenderer.OnAllJudged += onCompletion; diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index ed57dad644..8cead0684e 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -28,10 +28,12 @@ namespace osu.Game.Screens.Play private readonly SongProgressBar bar; private readonly SongProgressGraph graph; + private readonly SongProgressInfo info; public Action OnSeek; - public IClock AudioClock; + private IClock audioClock; + public IClock AudioClock { set { audioClock = info.AudioClock = value; } } private double lastHitTime => ((objects.Last() as IHasEndTime)?.EndTime ?? objects.Last().StartTime) + 1; @@ -44,6 +46,9 @@ namespace osu.Game.Screens.Play set { graph.Objects = objects = value; + + info.StartTime = firstHitTime; + info.EndTime = lastHitTime; } } @@ -62,6 +67,14 @@ namespace osu.Game.Screens.Play Children = new Drawable[] { + info = new SongProgressInfo + { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Bottom = bottom_bar_height + graph_height }, + }, graph = new SongProgressGraph { RelativeSizeAxes = Axes.X, @@ -130,10 +143,13 @@ namespace osu.Game.Screens.Play if (objects == null) return; - double progress = ((AudioClock?.CurrentTime ?? Time.Current) - firstHitTime) / lastHitTime; + double progress = ((audioClock?.CurrentTime ?? Time.Current) - firstHitTime) / (lastHitTime - firstHitTime); - bar.UpdatePosition((float)progress); - graph.Progress = (int)(graph.ColumnCount * progress); + if(progress < 1) + { + bar.UpdatePosition((float)progress); + graph.Progress = (int)(graph.ColumnCount * progress); + } } } } diff --git a/osu.Game/Screens/Play/SongProgressGraph.cs b/osu.Game/Screens/Play/SongProgressGraph.cs index 20548970e5..4e56f60c31 100644 --- a/osu.Game/Screens/Play/SongProgressGraph.cs +++ b/osu.Game/Screens/Play/SongProgressGraph.cs @@ -20,12 +20,13 @@ namespace osu.Game.Screens.Play const int granularity = 200; + var firstHit = objects.First().StartTime; var lastHit = (objects.Last() as IHasEndTime)?.EndTime ?? 0; if (lastHit == 0) lastHit = objects.Last().StartTime; - var interval = (lastHit + 1) / granularity; + var interval = (lastHit - firstHit + 1) / granularity; var values = new int[granularity]; @@ -33,8 +34,8 @@ namespace osu.Game.Screens.Play { IHasEndTime end = h as IHasEndTime; - int startRange = (int)(h.StartTime / interval); - int endRange = (int)((end?.EndTime > 0 ? end.EndTime : h.StartTime) / interval); + int startRange = (int)((h.StartTime - firstHit)/ interval); + int endRange = (int)(((end?.EndTime > 0 ? end.EndTime : h.StartTime) - firstHit) / interval); for (int i = startRange; i <= endRange; i++) values[i]++; } diff --git a/osu.Game/Screens/Play/SongProgressInfo.cs b/osu.Game/Screens/Play/SongProgressInfo.cs new file mode 100644 index 0000000000..4c53b61313 --- /dev/null +++ b/osu.Game/Screens/Play/SongProgressInfo.cs @@ -0,0 +1,96 @@ +// 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.Framework.Timing; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using System; + +namespace osu.Game.Screens.Play +{ + public class SongProgressInfo : Container + { + private OsuSpriteText timeCurrent; + private OsuSpriteText timeLeft; + private OsuSpriteText progress; + + private double startTime; + private double endTime; + + private int? previousPercent; + private int? previousSecond; + + private double songLength => endTime - startTime; + + private const int margin = 10; + + public IClock AudioClock; + + public double StartTime { set { startTime = value; } } + public double EndTime { set { endTime = value; } } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Children = new Drawable[] + { + timeCurrent = new OsuSpriteText + { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Colour = colours.BlueLighter, + Font = @"Venera", + Margin = new MarginPadding + { + Left = margin, + }, + }, + progress = new OsuSpriteText + { + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + Colour = colours.BlueLighter, + Font = @"Venera", + }, + timeLeft = new OsuSpriteText + { + Origin = Anchor.BottomRight, + Anchor = Anchor.BottomRight, + Colour = colours.BlueLighter, + Font = @"Venera", + Margin = new MarginPadding + { + Right = margin, + }, + } + }; + } + + protected override void Update() + { + base.Update(); + + double songCurrentTime = AudioClock.CurrentTime - startTime; + int currentPercent = Math.Max(0, Math.Min(100, (int)(songCurrentTime / songLength * 100))); + int currentSecond = (int)Math.Floor(songCurrentTime / 1000.0); + + if (currentPercent != previousPercent) + { + progress.Text = currentPercent.ToString() + @"%"; + previousPercent = currentPercent; + } + + if (currentSecond != previousSecond && songCurrentTime < songLength) + { + timeCurrent.Text = TimeSpan.FromSeconds(currentSecond).ToString(songCurrentTime < 0 ? @"\-m\:ss" : @"m\:ss"); + timeLeft.Text = TimeSpan.FromMilliseconds(endTime - AudioClock.CurrentTime).ToString(@"\-m\:ss"); + + previousSecond = currentSecond; + } + } + } +} diff --git a/osu.Game/Rulesets/UI/StandardHudOverlay.cs b/osu.Game/Screens/Play/StandardHUDOverlay.cs similarity index 85% rename from osu.Game/Rulesets/UI/StandardHudOverlay.cs rename to osu.Game/Screens/Play/StandardHUDOverlay.cs index c68e29f98a..41f9ee1394 100644 --- a/osu.Game/Rulesets/UI/StandardHudOverlay.cs +++ b/osu.Game/Screens/Play/StandardHUDOverlay.cs @@ -1,18 +1,18 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Scoring; -using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD; +using OpenTK; -namespace osu.Game.Rulesets.UI +namespace osu.Game.Screens.Play { - public class StandardHudOverlay : HudOverlay + public class StandardHUDOverlay : HUDOverlay { protected override RollingCounter CreateAccuracyCounter() => new PercentageCounter { @@ -64,6 +64,14 @@ namespace osu.Game.Rulesets.UI RelativeSizeAxes = Axes.X, }; + protected override ModDisplay CreateModsContainer() => new ModDisplay + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Top = 20, Right = 10 }, + }; + [BackgroundDependencyLoader] private void load(OsuColour colours) { diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index d05dd43b63..fbdaa948cc 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -220,13 +220,11 @@ namespace osu.Game.Screens.Select private BeatmapGroup createGroup(BeatmapSetInfo beatmapSet) { - database.GetChildren(beatmapSet); - beatmapSet.Beatmaps.ForEach(b => + foreach(var b in beatmapSet.Beatmaps) { - database.GetChildren(b); if (b.Metadata == null) b.Metadata = beatmapSet.Metadata; - }); + } return new BeatmapGroup(beatmapSet, database) { diff --git a/osu.Game/Screens/Select/BeatmapDeleteDialog.cs b/osu.Game/Screens/Select/BeatmapDeleteDialog.cs index e6e9a86124..0890625eb9 100644 --- a/osu.Game/Screens/Select/BeatmapDeleteDialog.cs +++ b/osu.Game/Screens/Select/BeatmapDeleteDialog.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Select Icon = FontAwesome.fa_trash_o; HeaderText = @"Confirm deletion of"; - BodyText = $@"{beatmap.Beatmap?.Metadata?.Artist} - {beatmap.Beatmap?.Metadata?.Title}"; + BodyText = $@"{beatmap.Metadata?.Artist} - {beatmap.Metadata?.Title}"; Buttons = new PopupDialogButton[] { new PopupDialogOkButton diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 44f2aeb0da..6a2f7a26fc 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -124,7 +124,7 @@ namespace osu.Game.Screens.Select Content = getBPMRange(beatmap.Beatmap), })); - //get statistics fromt he current ruleset. + //get statistics from the current ruleset. labels.AddRange(beatmapInfo.Ruleset.CreateInstance().GetBeatmapStatistics(beatmap).Select(s => new InfoLabel(s))); } diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index c15900eb6d..315992c113 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -69,6 +69,13 @@ namespace osu.Game.Screens.Select base.OnResuming(last); } + protected override void OnSuspending(Screen next) + { + modSelect.Hide(); + + base.OnSuspending(next); + } + protected override bool OnExiting(Screen next) { if (modSelect.State == Visibility.Visible) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 7d0648ac11..51b67bdbef 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -183,7 +183,7 @@ namespace osu.Game.Screens.Select initialAddSetsTask = new CancellationTokenSource(); carousel.BeatmapsChanged = beatmapsLoaded; - carousel.Beatmaps = database.Query().Where(b => !b.DeletePending); + carousel.Beatmaps = database.GetAllWithChildren(b => !b.DeletePending); } private void beatmapsLoaded() @@ -343,7 +343,7 @@ namespace osu.Game.Screens.Select { trackManager.SetExclusive(track); if (preview) - track.Seek(Beatmap.Beatmap.Metadata.PreviewTime); + track.Seek(Beatmap.Metadata.PreviewTime); track.Start(); } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 929811226f..ee6153dc55 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -75,9 +75,11 @@ + + @@ -178,10 +180,9 @@ - - - - + + + @@ -228,7 +229,10 @@ + + + @@ -254,7 +258,7 @@ - + @@ -279,11 +283,10 @@ - - + + - @@ -304,8 +307,8 @@ - - + + @@ -371,8 +374,10 @@ + + @@ -380,7 +385,7 @@ - + @@ -390,6 +395,7 @@ + diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index bc2c347d0c..03d9e34805 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -182,6 +182,7 @@ GL GLSL HID + HUD ID IP IPC