diff --git a/osu-framework b/osu-framework index d29c8365ba..41e2a0a430 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit d29c8365ba3cf7924b57cf22341f4af55658764c +Subproject commit 41e2a0a4304544fb67779c21cad1435c105982d5 diff --git a/osu-resources b/osu-resources index 92ec3d10b1..7bb0782200 160000 --- a/osu-resources +++ b/osu-resources @@ -1 +1 @@ -Subproject commit 92ec3d10b12c5e9bfc1d3b05d3db174a506efd6d +Subproject commit 7bb0782200abadf73b79ed1a3bc1d5b926c6a81e diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index d3012b1981..0cdc1694f4 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps { public override void PostProcess(Beatmap beatmap) { - if (beatmap.ComboColors.Count == 0) + if (beatmap.ComboColours.Count == 0) return; int index = 0; @@ -31,11 +31,11 @@ namespace osu.Game.Rulesets.Catch.Beatmaps if (obj.NewCombo) { if (lastObj != null) lastObj.LastInCombo = true; - colourIndex = (colourIndex + 1) % beatmap.ComboColors.Count; + colourIndex = (colourIndex + 1) % beatmap.ComboColours.Count; } obj.IndexInBeatmap = index++; - obj.ComboColour = beatmap.ComboColors[colourIndex]; + obj.ComboColour = beatmap.ComboColours[colourIndex]; lastObj = obj; } diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs index 3dad5b508c..bfcdec9321 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps { applyStacking(beatmap); - if (beatmap.ComboColors.Count == 0) + if (beatmap.ComboColours.Count == 0) return; int comboIndex = 0; @@ -25,11 +25,11 @@ namespace osu.Game.Rulesets.Osu.Beatmaps if (obj.NewCombo) { comboIndex = 0; - colourIndex = (colourIndex + 1) % beatmap.ComboColors.Count; + colourIndex = (colourIndex + 1) % beatmap.ComboColours.Count; } obj.IndexInCurrentCombo = comboIndex++; - obj.ComboColour = beatmap.ComboColors[colourIndex]; + obj.ComboColour = beatmap.ComboColours[colourIndex]; } } diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseGameplayCursor.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseGameplayCursor.cs new file mode 100644 index 0000000000..273422f2e9 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Tests/TestCaseGameplayCursor.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; +using osu.Game.Graphics.Cursor; +using osu.Game.Rulesets.Osu.UI.Cursor; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestCaseGameplayCursor : OsuTestCase, IProvideCursor + { + private GameplayCursor cursor; + + public override IReadOnlyList RequiredTypes => new [] { typeof(CursorTrail) }; + + public CursorContainer Cursor => cursor; + + public bool ProvidingUserCursor => true; + + [BackgroundDependencyLoader] + private void load() + { + Add(cursor = new GameplayCursor { RelativeSizeAxes = Axes.Both }); + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 37ca0c021b..dedfa28b7b 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -3,9 +3,9 @@ using System; using System.Diagnostics; +using System.Runtime.InteropServices; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.OpenGL.Buffers; using osu.Framework.Graphics.OpenGL.Vertices; using osu.Framework.Graphics.Primitives; @@ -14,11 +14,12 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.Timing; using OpenTK; +using OpenTK.Graphics; using OpenTK.Graphics.ES30; namespace osu.Game.Rulesets.Osu.UI.Cursor { - internal class CursorTrail : Drawable + internal class CursorTrail : Drawable, IRequireHighFrequencyMousePosition { private int currentIndex; @@ -31,6 +32,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private float time; + public override bool IsPresent => true; + private readonly TrailDrawNodeSharedData trailDrawNodeSharedData = new TrailDrawNodeSharedData(); private const int max_sprites = 2048; @@ -96,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor const int fade_clock_reset_threshold = 1000000; - time = (float)(Time.Current - timeOffset) / 500f; + time = (float)(Time.Current - timeOffset) / 300f; if (time > fade_clock_reset_threshold) resetTime(); } @@ -115,14 +118,16 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor protected override bool OnMouseMove(InputState state) { + Vector2 pos = state.Mouse.NativeState.Position; + if (lastPosition == null) { - lastPosition = state.Mouse.NativeState.Position; + lastPosition = pos; resampler.AddPosition(lastPosition.Value); return base.OnMouseMove(state); } - foreach (Vector2 pos2 in resampler.AddPosition(state.Mouse.NativeState.Position)) + foreach (Vector2 pos2 in resampler.AddPosition(pos)) { Trace.Assert(lastPosition.HasValue); @@ -162,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private class TrailDrawNodeSharedData { - public VertexBuffer VertexBuffer; + public VertexBuffer VertexBuffer; } private class TrailDrawNode : DrawNode @@ -188,7 +193,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor public override void Draw(Action vertexAction) { if (Shared.VertexBuffer == null) - Shared.VertexBuffer = new QuadVertexBuffer(max_sprites, BufferUsageHint.DynamicDraw); + Shared.VertexBuffer = new QuadVertexBuffer(max_sprites, BufferUsageHint.DynamicDraw); Shader.GetUniform("g_FadeClock").Value = Time; @@ -205,17 +210,19 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor int end = start; Vector2 pos = Parts[i].Position; - ColourInfo colour = DrawInfo.Colour; - colour.TopLeft.Linear.A = Parts[i].Time + colour.TopLeft.Linear.A; - colour.TopRight.Linear.A = Parts[i].Time + colour.TopRight.Linear.A; - colour.BottomLeft.Linear.A = Parts[i].Time + colour.BottomLeft.Linear.A; - colour.BottomRight.Linear.A = Parts[i].Time + colour.BottomRight.Linear.A; + float time = Parts[i].Time; Texture.DrawQuad( new Quad(pos.X - Size.X / 2, pos.Y - Size.Y / 2, Size.X, Size.Y), - colour, + DrawInfo.Colour, null, - v => Shared.VertexBuffer.Vertices[end++] = v); + v => Shared.VertexBuffer.Vertices[end++] = new TexturedTrailVertex + { + Position = v.Position, + TexturePosition = v.TexturePosition, + Time = time + 1, + Colour = v.Colour, + }); Parts[i].WasUpdated = false; } @@ -240,5 +247,26 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor Shader.Unbind(); } } + + [StructLayout(LayoutKind.Sequential)] + public struct TexturedTrailVertex : IEquatable, IVertex + { + [VertexMember(2, VertexAttribPointerType.Float)] + public Vector2 Position; + [VertexMember(4, VertexAttribPointerType.Float)] + public Color4 Colour; + [VertexMember(2, VertexAttribPointerType.Float)] + public Vector2 TexturePosition; + [VertexMember(1, VertexAttribPointerType.Float)] + public float Time; + + public bool Equals(TexturedTrailVertex other) + { + return Position.Equals(other.Position) + && TexturePosition.Equals(other.TexturePosition) + && Colour.Equals(other.Colour) + && Time.Equals(other.Time); + } + } } } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs index 0aeb14514d..34940a084a 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs @@ -20,13 +20,66 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { protected override Drawable CreateCursor() => new OsuCursor(); + protected override Container Content => fadeContainer; + + private readonly Container fadeContainer; + public GameplayCursor() { - Add(new CursorTrail { Depth = 1 }); + InternalChild = fadeContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new CursorTrail { Depth = 1 } + } + }; } private int downCount; + public bool OnPressed(OsuAction action) + { + switch (action) + { + case OsuAction.LeftButton: + case OsuAction.RightButton: + downCount++; + ActiveCursor.ScaleTo(1).ScaleTo(1.2f, 100, Easing.OutQuad); + break; + } + + return false; + } + + public bool OnReleased(OsuAction action) + { + switch (action) + { + case OsuAction.LeftButton: + case OsuAction.RightButton: + if (--downCount == 0) + ActiveCursor.ScaleTo(1, 200, Easing.OutQuad); + break; + } + + return false; + } + + public override bool HandleMouseInput => true; // OverlayContainer will set this false when we go hidden, but we always want to receive input. + + protected override void PopIn() + { + fadeContainer.FadeTo(1, 300, Easing.OutQuint); + ActiveCursor.ScaleTo(1, 400, Easing.OutQuint); + } + + protected override void PopOut() + { + fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint); + ActiveCursor.ScaleTo(0.8f, 450, Easing.OutQuint); + } + public class OsuCursor : Container { private Container cursorContainer; @@ -131,45 +184,5 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor cursorContainer.Scale = new Vector2(scale); } } - - public bool OnPressed(OsuAction action) - { - switch (action) - { - case OsuAction.LeftButton: - case OsuAction.RightButton: - downCount++; - ActiveCursor.ScaleTo(1).ScaleTo(1.2f, 100, Easing.OutQuad); - break; - } - - return false; - } - - public bool OnReleased(OsuAction action) - { - switch (action) - { - case OsuAction.LeftButton: - case OsuAction.RightButton: - if (--downCount == 0) - ActiveCursor.ScaleTo(1, 200, Easing.OutQuad); - break; - } - - return false; - } - - protected override void PopIn() - { - ActiveCursor.FadeTo(1, 250, Easing.OutQuint); - ActiveCursor.ScaleTo(1, 400, Easing.OutQuint); - } - - protected override void PopOut() - { - ActiveCursor.FadeTo(0, 250, Easing.OutQuint); - ActiveCursor.ScaleTo(0.6f, 250, Easing.In); - } } } diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index 4a404e9526..8b7383b6b7 100644 --- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -131,6 +131,7 @@ + diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index b74be134c1..2c46a124d8 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -167,7 +167,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { - var comboColors = decoder.Decode(stream).ComboColors; + var comboColors = decoder.Decode(stream).ComboColours; Color4[] expectedColors = { diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 80dea9d01d..c36e825252 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -102,9 +102,9 @@ namespace osu.Game.Tests.Beatmaps.Formats new Color4(255, 187, 255, 255), new Color4(255, 177, 140, 255), }; - Assert.AreEqual(expected.Length, beatmap.ComboColors.Count); + Assert.AreEqual(expected.Length, beatmap.ComboColours.Count); for (int i = 0; i < expected.Length; i++) - Assert.AreEqual(expected[i], beatmap.ComboColors[i]); + Assert.AreEqual(expected[i], beatmap.ComboColours[i]); } [Test] diff --git a/osu.Game.Tests/Visual/TestCaseSkipButton.cs b/osu.Game.Tests/Visual/TestCaseSkipButton.cs index a4d2019cd7..df94d5147f 100644 --- a/osu.Game.Tests/Visual/TestCaseSkipButton.cs +++ b/osu.Game.Tests/Visual/TestCaseSkipButton.cs @@ -13,7 +13,7 @@ namespace osu.Game.Tests.Visual { base.LoadComplete(); - Add(new SkipButton(Clock.CurrentTime + 5000)); + Add(new SkipOverlay(Clock.CurrentTime + 5000)); } } } diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 9b00993b6e..93817b9b8f 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -9,6 +9,7 @@ using System.Linq; using osu.Game.Beatmaps.ControlPoints; using osu.Game.IO.Serialization; using Newtonsoft.Json; +using osu.Game.Beatmaps.Formats; using osu.Game.IO.Serialization.Converters; namespace osu.Game.Beatmaps @@ -16,14 +17,14 @@ namespace osu.Game.Beatmaps /// /// A Beatmap containing converted HitObjects. /// - public class Beatmap : IJsonSerializable + public class Beatmap : IJsonSerializable, IHasComboColours where T : HitObject { public BeatmapInfo BeatmapInfo = new BeatmapInfo(); public ControlPointInfo ControlPointInfo = new ControlPointInfo(); public List Breaks = new List(); - public List ComboColors = new List + public List ComboColours { get; set; } = new List { new Color4(17, 136, 170, 255), new Color4(102, 136, 0, 255), @@ -55,7 +56,7 @@ namespace osu.Game.Beatmaps BeatmapInfo = original?.BeatmapInfo.DeepClone() ?? BeatmapInfo; ControlPointInfo = original?.ControlPointInfo ?? ControlPointInfo; Breaks = original?.Breaks ?? Breaks; - ComboColors = original?.ComboColors ?? ComboColors; + ComboColours = original?.ComboColours ?? ComboColours; HitObjects = original?.HitObjects ?? HitObjects; if (original == null && Metadata == null) diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index 711e220b88..c35c5df89b 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -50,9 +50,14 @@ namespace osu.Game.Beatmaps protected virtual Beatmap ConvertBeatmap(Beatmap original) { var beatmap = CreateBeatmap(); + + // todo: this *must* share logic (or directly use) Beatmap's constructor. + // right now this isn't easily possible due to generic entanglement. beatmap.BeatmapInfo = original.BeatmapInfo; beatmap.ControlPointInfo = original.ControlPointInfo; beatmap.HitObjects = original.HitObjects.SelectMany(h => convert(h, original)).ToList(); + beatmap.Breaks = original.Breaks; + beatmap.ComboColours = original.ComboColours; return beatmap; } diff --git a/osu.Game/Beatmaps/Formats/IHasComboColours.cs b/osu.Game/Beatmaps/Formats/IHasComboColours.cs new file mode 100644 index 0000000000..93c6c18eec --- /dev/null +++ b/osu.Game/Beatmaps/Formats/IHasComboColours.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using OpenTK.Graphics; + +namespace osu.Game.Beatmaps.Formats +{ + public interface IHasComboColours + { + List ComboColours { get; set; } + } +} diff --git a/osu.Game/Beatmaps/Formats/IHasCustomColours.cs b/osu.Game/Beatmaps/Formats/IHasCustomColours.cs new file mode 100644 index 0000000000..14614a6728 --- /dev/null +++ b/osu.Game/Beatmaps/Formats/IHasCustomColours.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using OpenTK.Graphics; + +namespace osu.Game.Beatmaps.Formats +{ + public interface IHasCustomColours + { + Dictionary CustomColours { get; set; } + } +} diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 915ea9b587..1bb67f9e75 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -5,7 +5,6 @@ using System; using System.Globalization; using System.IO; using System.Linq; -using OpenTK.Graphics; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Beatmaps.ControlPoints; @@ -19,7 +18,6 @@ namespace osu.Game.Beatmaps.Formats private Beatmap beatmap; - private bool hasCustomColours; private ConvertHitObjectParser parser; private LegacySampleBank defaultSampleBank; @@ -72,29 +70,28 @@ namespace osu.Game.Beatmaps.Formats { case Section.General: handleGeneral(line); - break; + return; case Section.Editor: handleEditor(line); - break; + return; case Section.Metadata: handleMetadata(line); - break; + return; case Section.Difficulty: handleDifficulty(line); - break; + return; case Section.Events: handleEvents(line); - break; + return; case Section.TimingPoints: handleTimingPoints(line); - break; - case Section.Colours: - handleColours(line); - break; + return; case Section.HitObjects: handleHitObjects(line); - break; + return; } + + base.ParseLine(beatmap, section, line); } private void handleGeneral(string line) @@ -364,38 +361,6 @@ namespace osu.Game.Beatmaps.Formats } } - private void handleColours(string line) - { - var pair = SplitKeyVal(line, ':'); - - string[] split = pair.Value.Split(','); - - if (split.Length != 3) - throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B): {pair.Value}"); - - byte r, g, b; - if (!byte.TryParse(split[0], out r) || !byte.TryParse(split[1], out g) || !byte.TryParse(split[2], out b)) - throw new InvalidOperationException(@"Color must be specified with 8-bit integer components"); - - if (!hasCustomColours) - { - beatmap.ComboColors.Clear(); - hasCustomColours = true; - } - - // Note: the combo index specified in the beatmap is discarded - if (pair.Key.StartsWith(@"Combo")) - { - beatmap.ComboColors.Add(new Color4 - { - R = r / 255f, - G = g / 255f, - B = b / 255f, - A = 1f, - }); - } - } - private void handleHitObjects(string line) { // If the ruleset wasn't specified, assume the osu!standard ruleset. diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 6a3fb82586..e4aa9f5091 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using OpenTK.Graphics; namespace osu.Game.Beatmaps.Formats { @@ -40,7 +41,53 @@ namespace osu.Game.Beatmaps.Formats protected virtual bool ShouldSkipLine(string line) => string.IsNullOrWhiteSpace(line) || line.StartsWith("//"); - protected abstract void ParseLine(T output, Section section, string line); + protected virtual void ParseLine(T output, Section section, string line) + { + switch (section) + { + case Section.Colours: + handleColours(output, line); + return; + } + } + + private bool hasComboColours; + + private void handleColours(T output, string line) + { + var pair = SplitKeyVal(line, ':'); + + bool isCombo = pair.Key.StartsWith(@"Combo"); + + string[] split = pair.Value.Split(','); + + if (split.Length != 3) + throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B): {pair.Value}"); + + if (!byte.TryParse(split[0], out var r) || !byte.TryParse(split[1], out var g) || !byte.TryParse(split[2], out var b)) + throw new InvalidOperationException(@"Color must be specified with 8-bit integer components"); + + Color4 colour = new Color4(r, g, b, 255); + + if (isCombo) + { + if (!(output is IHasComboColours tHasComboColours)) return; + + if (!hasComboColours) + { + // remove default colours. + tHasComboColours.ComboColours.Clear(); + hasComboColours = true; + } + + tHasComboColours.ComboColours.Add(colour); + } + else + { + if (!(output is IHasCustomColours tHasCustomColours)) return; + tHasCustomColours.CustomColours[pair.Key] = colour; + } + } protected KeyValuePair SplitKeyVal(string line, char separator) { @@ -65,6 +112,7 @@ namespace osu.Game.Beatmaps.Formats Colours, HitObjects, Variables, + Fonts } internal enum LegacySampleBank diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index e35276ae1a..85b0f8d42e 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -46,11 +46,13 @@ namespace osu.Game.Beatmaps.Formats { case Section.Events: handleEvents(line); - break; + return; case Section.Variables: handleVariables(line); - break; + return; } + + base.ParseLine(storyboard, section, line); } private void handleEvents(string line) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index bab53cb462..2cb8424bcc 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -8,15 +8,17 @@ using System.Diagnostics; using System.Net; using System.Threading; using osu.Framework.Configuration; +using osu.Framework.Graphics; using osu.Framework.Logging; -using osu.Framework.Threading; +using osu.Game.Configuration; using osu.Game.Online.API.Requests; using osu.Game.Users; namespace osu.Game.Online.API { - public class APIAccess : IAPIProvider + public class APIAccess : Component, IAPIProvider { + private readonly OsuConfigManager config; private readonly OAuth authentication; public string Endpoint = @"https://osu.ppy.sh"; @@ -25,13 +27,12 @@ namespace osu.Game.Online.API private ConcurrentQueue queue = new ConcurrentQueue(); - public Scheduler Scheduler = new Scheduler(); + /// + /// The username/email provided by the user when initiating a login. + /// + public string ProvidedUsername { get; private set; } - public string Username; - - //private SecurePassword password; - - public string Password; + private string password; public Bindable LocalUser { get; } = new Bindable(createGuestUser()); @@ -41,24 +42,31 @@ namespace osu.Game.Online.API set { authentication.Token = string.IsNullOrEmpty(value) ? null : OAuthToken.Parse(value); } } - protected bool HasLogin => Token != null || !string.IsNullOrEmpty(Username) && !string.IsNullOrEmpty(Password); + protected bool HasLogin => Token != null || !string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password); // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable (should dispose of this or at very least keep a reference). private readonly Thread thread; private readonly Logger log; - public APIAccess() + public APIAccess(OsuConfigManager config) { + this.config = config; + authentication = new OAuth(client_id, client_secret, Endpoint); log = Logger.GetLogger(LoggingTarget.Network); + ProvidedUsername = config.Get(OsuSetting.Username); + Token = config.Get(OsuSetting.Token); + thread = new Thread(run) { IsBackground = true }; thread.Start(); } private readonly List components = new List(); + internal void Schedule(Action action) => base.Schedule(action); + public void Register(IOnlineComponent component) { Scheduler.Add(delegate @@ -111,12 +119,15 @@ namespace osu.Game.Online.API State = APIState.Connecting; - if (!authentication.HasValidAccessToken && !authentication.AuthenticateWithLogin(Username, Password)) + // save the username at this point, if the user requested for it to be. + config.Set(OsuSetting.Username, config.Get(OsuSetting.SaveUsername) ? ProvidedUsername : string.Empty); + + if (!authentication.HasValidAccessToken && !authentication.AuthenticateWithLogin(ProvidedUsername, password)) { //todo: this fails even on network-related issues. we should probably handle those differently. //NotificationOverlay.ShowMessage("Login failed!"); log.Add(@"Login failed!"); - Password = null; + password = null; authentication.Clear(); continue; } @@ -173,8 +184,8 @@ namespace osu.Game.Online.API { Debug.Assert(State == APIState.Offline); - Username = username; - Password = password; + ProvidedUsername = username; + this.password = password; } /// @@ -283,8 +294,8 @@ namespace osu.Game.Online.API public void Logout(bool clearUsername = true) { flushQueue(); - if (clearUsername) Username = null; - Password = null; + if (clearUsername) ProvidedUsername = null; + password = null; authentication.Clear(); LocalUser.Value = createGuestUser(); } @@ -295,9 +306,12 @@ namespace osu.Game.Online.API Id = 1, }; - public void Update() + protected override void Dispose(bool isDisposing) { - Scheduler.Update(); + base.Dispose(isDisposing); + + config.Set(OsuSetting.Token, config.Get(OsuSetting.SavePassword) ? Token : string.Empty); + config.Save(); } } diff --git a/osu.Game/Online/API/APIDownloadRequest.cs b/osu.Game/Online/API/APIDownloadRequest.cs index 2dff07a847..0a5210723d 100644 --- a/osu.Game/Online/API/APIDownloadRequest.cs +++ b/osu.Game/Online/API/APIDownloadRequest.cs @@ -14,7 +14,7 @@ namespace osu.Game.Online.API return request; } - private void request_Progress(long current, long total) => API.Scheduler.Add(delegate { Progress?.Invoke(current, total); }); + private void request_Progress(long current, long total) => API.Schedule(() => Progress?.Invoke(current, total)); protected APIDownloadRequest() { diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 35af8eefd7..4b05df661b 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -85,7 +85,7 @@ namespace osu.Game.Online.API if (checkAndProcessFailure()) return; - api.Scheduler.Add(delegate { Success?.Invoke(); }); + api.Schedule(delegate { Success?.Invoke(); }); } public void Cancel() => Fail(new OperationCanceledException(@"Request cancelled")); @@ -108,7 +108,7 @@ namespace osu.Game.Online.API { if (API == null || pendingFailure == null) return cancelled; - API.Scheduler.Add(pendingFailure); + API.Schedule(pendingFailure); pendingFailure = null; return true; } diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index b3c8774209..4119691c85 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -1,13 +1,12 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework; using osu.Framework.Configuration; using osu.Game.Users; namespace osu.Game.Online.API { - public interface IAPIProvider : IUpdateable + public interface IAPIProvider { /// /// The local user. diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index f3c46269d5..45fd45b4b5 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -34,7 +34,7 @@ using osu.Game.Skinning; namespace osu.Game { - public class OsuGameBase : Framework.Game, IOnlineComponent, ICanAcceptFiles + public class OsuGameBase : Framework.Game, ICanAcceptFiles { protected OsuConfigManager LocalConfig; @@ -56,8 +56,6 @@ namespace osu.Game protected override string MainResourceFile => @"osu.Game.Resources.dll"; - public APIAccess API; - private Container content; protected override Container Content => content; @@ -108,16 +106,14 @@ namespace osu.Game dependencies.Cache(SkinManager = new SkinManager(Host.Storage, contextFactory, Host, Audio)); - dependencies.Cache(API = new APIAccess - { - Username = LocalConfig.Get(OsuSetting.Username), - Token = LocalConfig.Get(OsuSetting.Token) - }); - dependencies.CacheAs(API); + var api = new APIAccess(LocalConfig); + + dependencies.Cache(api); + dependencies.CacheAs(api); dependencies.Cache(RulesetStore = new RulesetStore(contextFactory)); dependencies.Cache(FileStore = new FileStore(contextFactory, Host.Storage)); - dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory, RulesetStore, API, Host)); + dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory, RulesetStore, api, Host)); dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, contextFactory, Host, BeatmapManager, RulesetStore)); dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); @@ -183,9 +179,9 @@ namespace osu.Game lastBeatmap = b; }; - API.Register(this); - FileStore.Cleanup(); + + AddInternal(api); } private void runMigrations() @@ -211,16 +207,6 @@ namespace osu.Game private WorkingBeatmap lastBeatmap; - public void APIStateChanged(APIAccess api, APIState state) - { - switch (state) - { - case APIState.Online: - LocalConfig.Set(OsuSetting.Username, LocalConfig.Get(OsuSetting.SaveUsername) ? API.Username : string.Empty); - break; - } - } - protected override void LoadComplete() { base.LoadComplete(); @@ -253,24 +239,6 @@ namespace osu.Game base.SetHost(host); } - protected override void Update() - { - base.Update(); - API.Update(); - } - - protected override void Dispose(bool isDisposing) - { - //refresh token may have changed. - if (LocalConfig != null && API != null) - { - LocalConfig.Set(OsuSetting.Token, LocalConfig.Get(OsuSetting.SavePassword) ? API.Token : string.Empty); - LocalConfig.Save(); - } - - base.Dispose(isDisposing); - } - private readonly List fileImporters = new List(); public void Import(params string[] paths) diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index e0d806c90f..cba63b4a49 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -186,7 +186,7 @@ namespace osu.Game.Overlays.Direct progressBar.FadeOut(500); }; - request.DownloadProgressed += progress => progressBar.Current.Value = progress; + request.DownloadProgressed += progress => Schedule(() => progressBar.Current.Value = progress); request.Success += data => { diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index a5d068adbd..4a4fc7363e 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -210,7 +210,7 @@ namespace osu.Game.Overlays.Settings.Sections.General { PlaceholderText = "Email address", RelativeSizeAxes = Axes.X, - Text = api?.Username ?? string.Empty, + Text = api?.ProvidedUsername ?? string.Empty, TabbableContentContainer = this }, password = new OsuPasswordTextBox diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 615c124ea7..29b68abc21 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -18,7 +18,7 @@ using System.Collections.Generic; namespace osu.Game.Screens.Play { - public abstract class GameplayMenuOverlay : OverlayContainer, IRequireHighFrequencyMousePosition + public abstract class GameplayMenuOverlay : OverlayContainer { private const int transition_duration = 200; private const int button_height = 70; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index c8ff261a93..8502812f26 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -164,7 +164,7 @@ namespace osu.Game.Screens.Play Alpha = 0, }, RulesetContainer, - new SkipButton(firstObjectTime) + new SkipOverlay(firstObjectTime) { Clock = Clock, // skip button doesn't want to use the audio clock directly ProcessCustomClock = false, diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 784dcf7657..31e7313c0b 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -5,12 +5,14 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using OpenTK; using osu.Framework.Localisation; +using osu.Framework.Threading; using osu.Game.Screens.Menu; using osu.Game.Screens.Play.PlayerSettings; @@ -21,7 +23,6 @@ namespace osu.Game.Screens.Play private Player player; private BeatmapMetadataDisplay info; - private VisualSettings visualSettings; private bool showOverlays = true; public override bool ShowOverlaysOnEnter => showOverlays; @@ -46,7 +47,8 @@ namespace osu.Game.Screens.Play Anchor = Anchor.Centre, Origin = Anchor.Centre, }); - Add(visualSettings = new VisualSettings + + Add(new VisualSettings { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, @@ -93,7 +95,7 @@ namespace osu.Game.Screens.Play contentIn(); info.Delay(750).FadeIn(500); - this.Delay(2150).Schedule(pushWhenLoaded); + this.Delay(1800).Schedule(pushWhenLoaded); } protected override void LogoArriving(OsuLogo logo, bool resuming) @@ -109,29 +111,65 @@ namespace osu.Game.Screens.Play logo.Delay(resuming ? 0 : 500).MoveToOffset(new Vector2(0, -0.24f), 500, Easing.InOutExpo); } + private bool weHandledMouseDown; + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + weHandledMouseDown = true; + return base.OnMouseDown(state, args); + } + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + { + weHandledMouseDown = false; + return base.OnMouseUp(state, args); + } + + private ScheduledDelegate pushDebounce; + + private bool readyForPush => player.LoadState == LoadState.Ready && IsHovered && (!GetContainingInputManager().CurrentState.Mouse.HasAnyButtonPressed || weHandledMouseDown); + private void pushWhenLoaded() { - if (player.LoadState != LoadState.Ready || visualSettings.IsHovered) + if (!IsCurrentScreen) return; + + try + { + if (!readyForPush) + { + // as the pushDebounce below has a delay, we need to keep checking and cancel a future debounce + // if we become unready for push during the delay. + pushDebounce?.Cancel(); + pushDebounce = null; + return; + } + + if (pushDebounce != null) + return; + + pushDebounce = Scheduler.AddDelayed(() => + { + contentOut(); + + this.Delay(250).Schedule(() => + { + if (!IsCurrentScreen) return; + + if (!Push(player)) + Exit(); + else + { + //By default, we want to load the player and never be returned to. + //Note that this may change if the player we load requested a re-run. + ValidForResume = false; + } + }); + }, 500); + } + finally { Schedule(pushWhenLoaded); - return; } - - contentOut(); - - this.Delay(250).Schedule(() => - { - if (!IsCurrentScreen) return; - - if (!Push(player)) - Exit(); - else - { - //By default, we want to load the player and never be returned to. - //Note that this may change if the player we load requested a re-run. - ValidForResume = false; - } - }); } protected override bool OnExiting(Screen next) diff --git a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs index 95b464154a..e0de89535e 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -133,5 +134,8 @@ namespace osu.Game.Screens.Play.PlayerSettings } protected override Container Content => content; + + protected override bool OnHover(InputState state) => true; + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; } } diff --git a/osu.Game/Screens/Play/SkipButton.cs b/osu.Game/Screens/Play/SkipOverlay.cs similarity index 89% rename from osu.Game/Screens/Play/SkipButton.cs rename to osu.Game/Screens/Play/SkipOverlay.cs index 08bb26c72b..19ee0cb989 100644 --- a/osu.Game/Screens/Play/SkipButton.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -21,7 +21,7 @@ using osu.Game.Input.Bindings; namespace osu.Game.Screens.Play { - public class SkipButton : OverlayContainer, IKeyBindingHandler + public class SkipOverlay : OverlayContainer, IKeyBindingHandler { private readonly double startTime; @@ -35,8 +35,9 @@ namespace osu.Game.Screens.Play private double displayTime; public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; + protected override bool BlockPassThroughMouse => false; - public SkipButton(double startTime) + public SkipOverlay(double startTime) { this.startTime = startTime; @@ -51,12 +52,6 @@ namespace osu.Game.Screens.Play Origin = Anchor.Centre; } - protected override bool OnMouseMove(InputState state) - { - fadeContainer.State = Visibility.Visible; - return base.OnMouseMove(state); - } - [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -121,15 +116,9 @@ namespace osu.Game.Screens.Play Expire(); } - protected override void PopIn() - { - this.FadeIn(); - } + protected override void PopIn() => this.FadeIn(); - protected override void PopOut() - { - this.FadeOut(); - } + protected override void PopOut() => this.FadeOut(); protected override void Update() { @@ -137,6 +126,13 @@ namespace osu.Game.Screens.Play remainingTimeBox.ResizeWidthTo((float)Math.Max(0, 1 - (Time.Current - displayTime) / (beginFadeTime - displayTime)), 120, Easing.OutQuint); } + protected override bool OnMouseMove(InputState state) + { + if (!state.Mouse.HasAnyButtonPressed) + fadeContainer.State = Visibility.Visible; + return base.OnMouseMove(state); + } + public bool OnPressed(GlobalAction action) { switch (action) @@ -176,7 +172,7 @@ namespace osu.Game.Screens.Play if (stateChanged) this.FadeIn(500, Easing.OutExpo); - if (!IsHovered) + if (!IsHovered && !IsDragged) using (BeginDelayedSequence(1000)) scheduledHide = Schedule(() => State = Visibility.Hidden); break; @@ -194,6 +190,18 @@ namespace osu.Game.Screens.Play base.LoadComplete(); State = Visibility.Visible; } + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + scheduledHide?.Cancel(); + return base.OnMouseDown(state, args); + } + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + { + State = Visibility.Visible; + return base.OnMouseUp(state, args); + } } private class Button : OsuClickableContainer @@ -274,7 +282,7 @@ namespace osu.Game.Screens.Play flow.TransformSpacingTo(new Vector2(5), 500, Easing.OutQuint); box.FadeColour(colourHover, 500, Easing.OutQuint); background.FadeTo(0.4f, 500, Easing.OutQuint); - return base.OnHover(state); + return true; } protected override void OnHoverLost(InputState state) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 86833a6d19..1e381545d4 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -267,6 +267,8 @@ + + @@ -809,7 +811,7 @@ - +