diff --git a/osu-framework b/osu-framework index 71bbc98060..e611e186e3 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 71bbc980602829cf7eb0db537ebaa2f9668acda5 +Subproject commit e611e186e3c8951d7e58a6c92c75e1b587e825a2 diff --git a/osu-resources b/osu-resources index 911564f95a..6edd5eacdd 160000 --- a/osu-resources +++ b/osu-resources @@ -1 +1 @@ -Subproject commit 911564f95a1b7820671cb17fadcd11bab8ba144e +Subproject commit 6edd5eacdd25cc8c4f4dbca3414678c0b7dc5deb diff --git a/osu.Desktop.VisualTests/Tests/TestCaseChatDisplay.cs b/osu.Desktop.VisualTests/Tests/TestCaseChatDisplay.cs index cb412f8c97..9e8083310c 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseChatDisplay.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseChatDisplay.cs @@ -14,9 +14,9 @@ using osu.Game; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.Chat; -using osu.Game.Online.Chat.Display; using OpenTK; using osu.Framework.Allocation; +using osu.Game.Online.Chat.Drawables; namespace osu.Desktop.VisualTests.Tests { @@ -45,7 +45,7 @@ namespace osu.Desktop.VisualTests.Tests { base.Reset(); - if (api.State != APIAccess.APIState.Online) + if (api.State != APIState.Online) api.OnStateChange += delegate { initializeChannels(); }; else initializeChannels(); @@ -65,7 +65,7 @@ namespace osu.Desktop.VisualTests.Tests { careChannels = new List(); - if (api.State != APIAccess.APIState.Online) + if (api.State != APIState.Online) return; Add(flow = new FlowContainer diff --git a/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs b/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs index 31a1bc369e..1e18ae4117 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs @@ -12,6 +12,7 @@ using osu.Game.Modes.Objects.Drawables; using osu.Game.Modes.Osu.Objects; using osu.Game.Modes.Osu.Objects.Drawables; using osu.Framework.Graphics.Containers; +using osu.Game.Modes; namespace osu.Desktop.VisualTests.Tests { @@ -31,7 +32,7 @@ namespace osu.Desktop.VisualTests.Tests Clock.ProcessFrame(); - Container approachContainer = new Container { Depth = float.MaxValue, }; + Container approachContainer = new Container { Depth = float.MinValue, }; Add(approachContainer); @@ -49,8 +50,8 @@ namespace osu.Desktop.VisualTests.Tests { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Depth = -i, - State = ArmedState.Armed, + Depth = i, + State = ArmedState.Hit, }; approachContainer.Add(d.ApproachCircle.CreateProxy()); diff --git a/osu.Desktop.VisualTests/Tests/TestCaseMenuButtonSystem.cs b/osu.Desktop.VisualTests/Tests/TestCaseMenuButtonSystem.cs index c7889134bb..6579e8109c 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseMenuButtonSystem.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseMenuButtonSystem.cs @@ -2,7 +2,10 @@ //Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.GameModes.Testing; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Sprites; using osu.Game.Screens.Menu; +using OpenTK.Graphics; namespace osu.Desktop.VisualTests.Tests { @@ -15,6 +18,11 @@ namespace osu.Desktop.VisualTests.Tests { base.Reset(); + Add(new Box + { + ColourInfo = ColourInfo.GradientVertical(Color4.Gray, Color4.WhiteSmoke), + RelativeSizeAxes = Framework.Graphics.Axes.Both, + }); Add(new ButtonSystem()); } } diff --git a/osu.Desktop.VisualTests/Tests/TestCasePlaySongSelect.cs b/osu.Desktop.VisualTests/Tests/TestCasePlaySongSelect.cs index 244982ded3..a291f4f65f 100644 --- a/osu.Desktop.VisualTests/Tests/TestCasePlaySongSelect.cs +++ b/osu.Desktop.VisualTests/Tests/TestCasePlaySongSelect.cs @@ -1,31 +1,32 @@ -//Copyright (c) 2007-2016 ppy Pty Ltd . -//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using osu.Desktop.VisualTests.Platform; -using osu.Framework.GameModes.Testing; -using osu.Game.Database; -using osu.Game.Modes; -using osu.Game.Screens.Select; - -namespace osu.Desktop.VisualTests.Tests -{ - class TestCasePlaySongSelect : TestCase - { - private BeatmapDatabase db; - private TestStorage storage; - - public override string Name => @"Song Select"; +//Copyright (c) 2007-2016 ppy Pty Ltd . +//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Desktop.VisualTests.Platform; +using osu.Framework.GameModes.Testing; +using osu.Game.Database; +using osu.Game.Modes; +using osu.Game.Screens.Select; + +namespace osu.Desktop.VisualTests.Tests +{ + class TestCasePlaySongSelect : TestCase + { + private BeatmapDatabase db, oldDb; + private TestStorage storage; + + public override string Name => @"Song Select"; public override string Description => @"with fake data"; - public override void Reset() - { + public override void Reset() + { base.Reset(); - + oldDb = Dependencies.Get(); if (db == null) { storage = new TestStorage(@"TestCasePlaySongSelect"); db = new BeatmapDatabase(storage); + Dependencies.Cache(db, true); var sets = new List(); @@ -34,8 +35,14 @@ namespace osu.Desktop.VisualTests.Tests db.Import(sets); } - - Add(new PlaySongSelect(db)); + Add(new PlaySongSelect()); + } + + protected override void Dispose(bool isDisposing) + { + if (oldDb != null) + Dependencies.Cache(oldDb, true); + base.Dispose(isDisposing); } private BeatmapSetInfo createTestBeatmapSet(int i) @@ -89,6 +96,6 @@ namespace osu.Desktop.VisualTests.Tests }, }), }; - } - } -} + } + } +} diff --git a/osu.Desktop.VisualTests/Tests/TestCasePlayer.cs b/osu.Desktop.VisualTests/Tests/TestCasePlayer.cs index da21b38ec8..89bacb9b97 100644 --- a/osu.Desktop.VisualTests/Tests/TestCasePlayer.cs +++ b/osu.Desktop.VisualTests/Tests/TestCasePlayer.cs @@ -2,6 +2,7 @@ //Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; +using osu.Framework.Allocation; using osu.Framework.GameModes.Testing; using osu.Framework.MathUtils; using osu.Framework.Timing; @@ -9,6 +10,8 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using OpenTK; using osu.Framework.Graphics.Sprites; +using osu.Game.Database; +using osu.Game.Modes; using osu.Game.Modes.Objects; using osu.Game.Modes.Osu.Objects; using osu.Game.Screens.Play; @@ -18,10 +21,17 @@ namespace osu.Desktop.VisualTests.Tests { class TestCasePlayer : TestCase { + private WorkingBeatmap beatmap; public override string Name => @"Player"; public override string Description => @"Showing everything to play the game."; + [BackgroundDependencyLoader] + private void load(BeatmapDatabase db) + { + beatmap = db.GetWorkingBeatmap(db.Query().Where(b => b.Mode == PlayMode.Osu).FirstOrDefault()); + } + public override void Reset() { base.Reset(); @@ -29,31 +39,37 @@ namespace osu.Desktop.VisualTests.Tests //ensure we are at offset 0 Clock = new FramedClock(); - var objects = new List(); - - int time = 1500; - for (int i = 0; i < 50; i++) + if (beatmap == null) { - objects.Add(new HitCircle() + + var objects = new List(); + + int time = 1500; + for (int i = 0; i < 50; i++) { - StartTime = time, - Position = new Vector2(i % 4 == 0 || i % 4 == 2 ? 0 : 512, - i % 4 < 2 ? 0 : 384), - NewCombo = i % 4 == 0 - }); + objects.Add(new HitCircle() + { + StartTime = time, + Position = new Vector2(i % 4 == 0 || i % 4 == 2 ? 0 : 512, + i % 4 < 2 ? 0 : 384), + NewCombo = i % 4 == 0 + }); - time += 500; + time += 500; + } + + var decoder = new ConstructableBeatmapDecoder(); + + Beatmap b = new Beatmap + { + HitObjects = objects + }; + + decoder.Process(b); + + beatmap = new WorkingBeatmap(b); } - var decoder = new ConstructableBeatmapDecoder(); - - Beatmap b = new Beatmap - { - HitObjects = objects - }; - - decoder.Process(b); - Add(new Box { RelativeSizeAxes = Framework.Graphics.Axes.Both, @@ -62,7 +78,8 @@ namespace osu.Desktop.VisualTests.Tests Add(new Player { - Beatmap = new WorkingBeatmap(b) + PreferredPlayMode = PlayMode.Osu, + Beatmap = beatmap }); } diff --git a/osu.Game.Mode.Osu/Objects/BezierApproximator.cs b/osu.Game.Mode.Osu/Objects/BezierApproximator.cs new file mode 100644 index 0000000000..f08b7aa377 --- /dev/null +++ b/osu.Game.Mode.Osu/Objects/BezierApproximator.cs @@ -0,0 +1,148 @@ +using System.Collections.Generic; +using OpenTK; + +namespace osu.Game.Modes.Osu.Objects +{ + public class BezierApproximator + { + private int count; + private List controlPoints; + private Vector2[] subdivisionBuffer1; + private Vector2[] subdivisionBuffer2; + + private const float TOLERANCE = 0.5f; + private const float TOLERANCE_SQ = TOLERANCE * TOLERANCE; + + public BezierApproximator(List controlPoints) + { + this.controlPoints = controlPoints; + count = controlPoints.Count; + + subdivisionBuffer1 = new Vector2[count]; + subdivisionBuffer2 = new Vector2[count * 2 - 1]; + } + + /// + /// Make sure the 2nd order derivative (approximated using finite elements) is within tolerable bounds. + /// NOTE: The 2nd order derivative of a 2d curve represents its curvature, so intuitively this function + /// checks (as the name suggests) whether our approximation is _locally_ "flat". More curvy parts + /// need to have a denser approximation to be more "flat". + /// + /// The control points to check for flatness. + /// Whether the control points are flat enough. + private static bool IsFlatEnough(Vector2[] controlPoints) + { + for (int i = 1; i < controlPoints.Length - 1; i++) + if ((controlPoints[i - 1] - 2 * controlPoints[i] + controlPoints[i + 1]).LengthSquared > TOLERANCE_SQ) + return false; + + return true; + } + + /// + /// Subdivides n control points representing a bezier curve into 2 sets of n control points, each + /// describing a bezier curve equivalent to a half of the original curve. Effectively this splits + /// the original curve into 2 curves which result in the original curve when pieced back together. + /// + /// The control points to split. + /// Output: The control points corresponding to the left half of the curve. + /// Output: The control points corresponding to the right half of the curve. + private void Subdivide(Vector2[] controlPoints, Vector2[] l, Vector2[] r) + { + Vector2[] midpoints = subdivisionBuffer1; + + for (int i = 0; i < count; ++i) + midpoints[i] = controlPoints[i]; + + for (int i = 0; i < count; i++) + { + l[i] = midpoints[0]; + r[count - i - 1] = midpoints[count - i - 1]; + + for (int j = 0; j < count - i - 1; j++) + midpoints[j] = (midpoints[j] + midpoints[j + 1]) / 2; + } + } + + /// + /// This uses De Casteljau's algorithm to obtain an optimal + /// piecewise-linear approximation of the bezier curve with the same amount of points as there are control points. + /// + /// The control points describing the bezier curve to be approximated. + /// The points representing the resulting piecewise-linear approximation. + private void Approximate(Vector2[] controlPoints, List output) + { + Vector2[] l = subdivisionBuffer2; + Vector2[] r = subdivisionBuffer1; + + Subdivide(controlPoints, l, r); + + for (int i = 0; i < count - 1; ++i) + l[count + i] = r[i + 1]; + + output.Add(controlPoints[0]); + for (int i = 1; i < count - 1; ++i) + { + int index = 2 * i; + Vector2 p = 0.25f * (l[index - 1] + 2 * l[index] + l[index + 1]); + output.Add(p); + } + } + + /// + /// Creates a piecewise-linear approximation of a bezier curve, by adaptively repeatedly subdividing + /// the control points until their approximation error vanishes below a given threshold. + /// + /// The control points describing the curve. + /// A list of vectors representing the piecewise-linear approximation. + public List CreateBezier() + { + List output = new List(); + + if (count == 0) + return output; + + Stack toFlatten = new Stack(); + Stack freeBuffers = new Stack(); + + // "toFlatten" contains all the curves which are not yet approximated well enough. + // We use a stack to emulate recursion without the risk of running into a stack overflow. + // (More specifically, we iteratively and adaptively refine our curve with a + // Depth-first search + // over the tree resulting from the subdivisions we make.) + toFlatten.Push(controlPoints.ToArray()); + + Vector2[] leftChild = subdivisionBuffer2; + + while (toFlatten.Count > 0) + { + Vector2[] parent = toFlatten.Pop(); + if (IsFlatEnough(parent)) + { + // If the control points we currently operate on are sufficiently "flat", we use + // an extension to De Casteljau's algorithm to obtain a piecewise-linear approximation + // of the bezier curve represented by our control points, consisting of the same amount + // of points as there are control points. + Approximate(parent, output); + freeBuffers.Push(parent); + continue; + } + + // If we do not yet have a sufficiently "flat" (in other words, detailed) approximation we keep + // subdividing the curve we are currently operating on. + Vector2[] rightChild = freeBuffers.Count > 0 ? freeBuffers.Pop() : new Vector2[count]; + Subdivide(parent, leftChild, rightChild); + + // We re-use the buffer of the parent for one of the children, so that we save one allocation per iteration. + for (int i = 0; i < count; ++i) + parent[i] = leftChild[i]; + + toFlatten.Push(rightChild); + toFlatten.Push(parent); + } + + output.Add(controlPoints[count - 1]); + return output; + } + } +} \ No newline at end of file diff --git a/osu.Game.Mode.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Mode.Osu/Objects/Drawables/DrawableHitCircle.cs index 8c73955ca9..94e9a41c5a 100644 --- a/osu.Game.Mode.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Mode.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -2,6 +2,7 @@ //Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.ComponentModel; using osu.Framework.Graphics; using osu.Framework.Graphics.Transformations; using osu.Game.Modes.Objects.Drawables; @@ -10,7 +11,7 @@ using OpenTK; namespace osu.Game.Modes.Osu.Objects.Drawables { - public class DrawableHitCircle : DrawableHitObject + public class DrawableHitCircle : DrawableOsuHitObject { private OsuHitObject osuObject; @@ -23,7 +24,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables private GlowPiece glow; private HitExplosion explosion; - public DrawableHitCircle(HitCircle h) : base(h) + public DrawableHitCircle(OsuHitObject h) : base(h) { osuObject = h; @@ -39,7 +40,12 @@ namespace osu.Game.Modes.Osu.Objects.Drawables circle = new CirclePiece { Colour = osuObject.Colour, - Hit = Hit, + Hit = () => + { + ((PositionalJudgementInfo)Judgement).PositionOffset = Vector2.Zero; //todo: set to correct value + UpdateJudgement(true); + return true; + }, }, number = new NumberPiece(), ring = new RingPiece(), @@ -53,29 +59,54 @@ namespace osu.Game.Modes.Osu.Objects.Drawables Colour = osuObject.Colour, } }; + + //may not be so correct + Size = circle.DrawSize; } protected override void LoadComplete() { base.LoadComplete(); - //may not be so correct - Size = circle.DrawSize; - //force application of the state that was set before we loaded. UpdateState(State); } - protected override void UpdateState(ArmedState state) + double hit50 = 150; + double hit100 = 80; + double hit300 = 30; + + protected override void CheckJudgement(bool userTriggered) { - if (!IsLoaded) return; + if (!userTriggered) + { + if (Judgement.TimeOffset > hit50) + Judgement.Result = HitResult.Miss; + return; + } - Flush(true); //move to DrawableHitObject - ApproachCircle.Flush(true); + double hitOffset = Math.Abs(Judgement.TimeOffset); - double t = HitTime ?? osuObject.StartTime; + if (hitOffset < hit50) + { + Judgement.Result = HitResult.Hit; - Alpha = 0; + OsuJudgementInfo osuInfo = Judgement as OsuJudgementInfo; + + if (hitOffset < hit300) + osuInfo.Score = OsuScoreResult.Hit300; + else if (hitOffset < hit100) + osuInfo.Score = OsuScoreResult.Hit100; + else if (hitOffset < hit50) + osuInfo.Score = OsuScoreResult.Hit50; + } + else + Judgement.Result = HitResult.Miss; + } + + protected override void UpdateInitialState() + { + base.UpdateInitialState(); //sane defaults ring.Alpha = circle.Alpha = number.Alpha = glow.Alpha = 1; @@ -83,34 +114,48 @@ namespace osu.Game.Modes.Osu.Objects.Drawables ApproachCircle.Scale = new Vector2(2); explode.Alpha = 0; Scale = new Vector2(0.5f); //this will probably need to be moved to DrawableHitObject at some point. + } - const float preempt = 600; + protected override void UpdatePreemptState() + { + base.UpdatePreemptState(); - const float fadein = 400; + ApproachCircle.FadeIn(Math.Min(TIME_FADEIN * 2, TIME_PREEMPT)); + ApproachCircle.ScaleTo(0.6f, TIME_PREEMPT); + } - Delay(t - Time.Current - preempt, true); + protected override void UpdateState(ArmedState state) + { + if (!IsLoaded) return; - FadeIn(fadein); - - ApproachCircle.FadeIn(Math.Min(fadein * 2, preempt)); - ApproachCircle.ScaleTo(0.6f, preempt); - - Delay(preempt, true); + base.UpdateState(state); ApproachCircle.FadeOut(); - glow.FadeOut(400); switch (state) { - case ArmedState.Disarmed: - Delay(osuObject.Duration + 200); - FadeOut(200); + case ArmedState.Idle: + Delay(osuObject.Duration + TIME_PREEMPT); + FadeOut(TIME_FADEOUT); explosion?.Expire(); explosion = null; break; - case ArmedState.Armed: + case ArmedState.Miss: + ring.FadeOut(); + circle.FadeOut(); + number.FadeOut(); + glow.FadeOut(); + + explosion?.Expire(); + explosion = null; + + Schedule(() => Add(explosion = new HitExplosion((OsuJudgementInfo)Judgement))); + + FadeOut(800); + break; + case ArmedState.Hit: const double flash_in = 30; flash.FadeTo(0.8f, flash_in); @@ -119,7 +164,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables explode.FadeIn(flash_in); - Schedule(() => Add(explosion = new HitExplosion(Judgement.Hit300))); + Schedule(() => Add(explosion = new HitExplosion((OsuJudgementInfo)Judgement))); Delay(flash_in, true); diff --git a/osu.Game.Mode.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Mode.Osu/Objects/Drawables/DrawableOsuHitObject.cs new file mode 100644 index 0000000000..80e1f2bb7f --- /dev/null +++ b/osu.Game.Mode.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using osu.Game.Modes.Objects; +using osu.Game.Modes.Objects.Drawables; + +namespace osu.Game.Modes.Osu.Objects.Drawables +{ + public class DrawableOsuHitObject : DrawableHitObject + { + protected const float TIME_PREEMPT = 600; + protected const float TIME_FADEIN = 400; + protected const float TIME_FADEOUT = 500; + + public DrawableOsuHitObject(OsuHitObject hitObject) + : base(hitObject) + { + } + + public override JudgementInfo CreateJudgementInfo() => new OsuJudgementInfo(); + + protected override void UpdateState(ArmedState state) + { + if (!IsLoaded) return; + + Flush(true); + + UpdateInitialState(); + + Delay(HitObject.StartTime - Time.Current - TIME_PREEMPT + Judgement.TimeOffset, true); + + UpdatePreemptState(); + + Delay(TIME_PREEMPT, true); + } + + protected virtual void UpdatePreemptState() + { + FadeIn(TIME_FADEIN); + } + + protected virtual void UpdateInitialState() + { + Alpha = 0; + } + } + + public class OsuJudgementInfo : PositionalJudgementInfo + { + public OsuScoreResult Score; + public ComboResult Combo; + } + + public enum ComboResult + { + [Description(@"")] + None, + [Description(@"Good")] + Good, + [Description(@"Amazing")] + Perfect + } + + public enum OsuScoreResult + { + [Description(@"Miss")] + Miss, + [Description(@"50")] + Hit50, + [Description(@"100")] + Hit100, + [Description(@"300")] + Hit300, + } +} diff --git a/osu.Game.Mode.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Mode.Osu/Objects/Drawables/DrawableSlider.cs index 693f05cd1f..8882049cbb 100644 --- a/osu.Game.Mode.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Mode.Osu/Objects/Drawables/DrawableSlider.cs @@ -1,27 +1,52 @@ -using osu.Framework.Graphics; +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Modes.Objects.Drawables; using osu.Game.Modes.Osu.Objects.Drawables.Pieces; using OpenTK; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Transformations; +using OpenTK.Graphics; +using osu.Framework.Input; +using OpenTK.Graphics.ES30; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Textures; namespace osu.Game.Modes.Osu.Objects.Drawables { - class DrawableSlider : DrawableHitObject + class DrawableSlider : DrawableOsuHitObject { - public DrawableSlider(Slider h) : base(h) - { - Origin = Anchor.Centre; - RelativePositionAxes = Axes.Both; - Position = new Vector2(h.Position.X / 512, h.Position.Y / 384); + private Slider slider; - for (float i = 0; i <= 1; i += 0.1f) + private DrawableHitCircle startCircle; + private Container ball; + private Body body; + + public DrawableSlider(Slider s) : base(s) + { + slider = s; + + Origin = Anchor.TopLeft; + Position = Vector2.Zero; + RelativeSizeAxes = Axes.Both; + + Children = new Drawable[] { - Add(new CirclePiece + body = new Body(s) { - Colour = h.Colour, - Hit = Hit, - Position = h.Curve.PositionAt(i) - h.Position //non-relative? - }); - } + Position = s.Position, + }, + ball = new Ball(), + startCircle = new DrawableHitCircle(new HitCircle + { + StartTime = s.StartTime, + Position = s.Position, + Colour = s.Colour, + }) + { + Depth = -1 //override time-based depth. + }, + }; } protected override void LoadComplete() @@ -32,19 +57,234 @@ namespace osu.Game.Modes.Osu.Objects.Drawables UpdateState(State); } + protected override void Update() + { + base.Update(); + + ball.Alpha = Time.Current >= slider.StartTime && Time.Current <= slider.EndTime ? 1 : 0; + + double t = (Time.Current - slider.StartTime) / slider.Duration; + if (slider.RepeatCount > 1) + { + int currentRepeat = (int)(t * slider.RepeatCount); + t = (t * slider.RepeatCount) % 1; + if (currentRepeat % 2 == 1) + t = 1 - t; + } + + ball.Position = slider.Curve.PositionAt(t); + } + + protected override void CheckJudgement(bool userTriggered) + { + var j = Judgement as OsuJudgementInfo; + var sc = startCircle.Judgement as OsuJudgementInfo; + + if (!userTriggered && Time.Current >= HitObject.EndTime) + { + j.Score = sc.Score; + j.Result = sc.Result; + } + } + protected override void UpdateState(ArmedState state) { - if (!IsLoaded) return; + base.UpdateState(state); - Flush(true); //move to DrawableHitObject + Delay(HitObject.Duration); + FadeOut(300); + } - Alpha = 0; + private class Ball : Container + { + private Box follow; - Delay(HitObject.StartTime - 200 - Time.Current, true); + public Ball() + { + Masking = true; + AutoSizeAxes = Axes.Both; + BlendingMode = BlendingMode.Additive; + Origin = Anchor.Centre; - FadeIn(200); - Delay(200 + HitObject.Duration); - FadeOut(200); + Children = new Drawable[] + { + follow = new Box + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Colour = Color4.Orange, + Width = 64, + Height = 64, + }, + new Container + { + Masking = true, + AutoSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Colour = Color4.Cyan, + CornerRadius = 32, + Children = new[] + { + new Box + { + + Width = 64, + Height = 64, + }, + } + } + + }; + } + + private InputState lastState; + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + lastState = state; + return base.OnMouseDown(state, args); + } + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + { + lastState = state; + return base.OnMouseUp(state, args); + } + + protected override bool OnMouseMove(InputState state) + { + lastState = state; + return base.OnMouseMove(state); + } + + bool tracking; + protected bool Tracking + { + get { return tracking; } + set + { + if (value == tracking) return; + + tracking = value; + + follow.ScaleTo(tracking ? 2.4f : 1, 140, EasingTypes.Out); + follow.FadeTo(tracking ? 0.8f : 0, 140, EasingTypes.Out); + } + } + + protected override void Update() + { + base.Update(); + + CornerRadius = DrawWidth / 2; + Tracking = lastState != null && Contains(lastState.Mouse.NativeState.Position) && lastState.Mouse.HasMainButtonPressed; + } + } + + private class Body : Container + { + private Path path; + private BufferedContainer container; + + private double? drawnProgress; + + private Slider slider; + public Body(Slider s) + { + slider = s; + + Children = new Drawable[] + { + container = new BufferedContainer + { + CacheDrawnFrameBuffer = true, + Children = new Drawable[] + { + path = new Path + { + Colour = s.Colour, + BlendingMode = BlendingMode.None, + }, + } + } + }; + + container.Attach(RenderbufferInternalFormat.DepthComponent16); + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + // Surprisingly, this looks somewhat okay and works well as a test for self-overlaps. + // TODO: Don't do this. + path.Texture = textures.Get(@"Menu/logo"); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + path.PathWidth = 32; + } + + protected override void Update() + { + base.Update(); + + if (updateSnaking()) + { + // Autosizing does not give us the desired behaviour here. + // We want the container to have the same size as the slider, + // and to be positioned such that the slider head is at (0,0). + container.Size = path.Size; + container.Position = -path.HeadPosition; + + container.ForceRedraw(); + } + } + + private bool updateSnaking() + { + double progress = MathHelper.Clamp((Time.Current - slider.StartTime + TIME_PREEMPT) / TIME_FADEIN, 0, 1); + + if (progress == drawnProgress) return false; + + bool madeChanges = false; + if (progress == 0) + { + //if we have gone backwards, just clear the path for now. + drawnProgress = 0; + path.ClearVertices(); + madeChanges = true; + } + + Vector2 startPosition = slider.Curve.PositionAt(0); + + if (drawnProgress == null) + { + drawnProgress = 0; + path.AddVertex(slider.Curve.PositionAt(drawnProgress.Value) - startPosition); + madeChanges = true; + } + + double segmentSize = 1 / (slider.Curve.Length / 5); + + while (drawnProgress + segmentSize < progress) + { + drawnProgress += segmentSize; + path.AddVertex(slider.Curve.PositionAt(drawnProgress.Value) - startPosition); + madeChanges = true; + } + + if (progress == 1 && drawnProgress != progress) + { + drawnProgress = progress; + path.AddVertex(slider.Curve.PositionAt(drawnProgress.Value) - startPosition); + madeChanges = true; + } + + return madeChanges; + } } } } diff --git a/osu.Game.Mode.Osu/Objects/Drawables/HitExplosion.cs b/osu.Game.Mode.Osu/Objects/Drawables/HitExplosion.cs index a3067a1eab..a0ab68fa99 100644 --- a/osu.Game.Mode.Osu/Objects/Drawables/HitExplosion.cs +++ b/osu.Game.Mode.Osu/Objects/Drawables/HitExplosion.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Transformations; +using osu.Game.Modes.Objects.Drawables; using OpenTK; namespace osu.Game.Modes.Osu.Objects.Drawables @@ -12,7 +13,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables private SpriteText line1; private SpriteText line2; - public HitExplosion(Judgement judgement, ComboJudgement comboJudgement = ComboJudgement.None) + public HitExplosion(OsuJudgementInfo judgement) { AutoSizeAxes = Axes.Both; Anchor = Anchor.Centre; @@ -27,13 +28,13 @@ namespace osu.Game.Modes.Osu.Objects.Drawables { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Text = judgement.GetDescription(), + Text = judgement.Score.GetDescription(), Font = @"Venera", TextSize = 20, }, line2 = new SpriteText { - Text = comboJudgement.GetDescription(), + Text = judgement.Combo.GetDescription(), Font = @"Venera", TextSize = 14, } diff --git a/osu.Game.Mode.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Mode.Osu/Objects/Drawables/Pieces/CirclePiece.cs index c326c46553..fd55a8315a 100644 --- a/osu.Game.Mode.Osu/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Mode.Osu/Objects/Drawables/Pieces/CirclePiece.cs @@ -49,8 +49,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { - Hit?.Invoke(); - return true; + return Hit?.Invoke() ?? false; } } } \ No newline at end of file diff --git a/osu.Game.Mode.Osu/Objects/Drawables/Pieces/Triangles.cs b/osu.Game.Mode.Osu/Objects/Drawables/Pieces/Triangles.cs index 94aa185772..3ef13792de 100644 --- a/osu.Game.Mode.Osu/Objects/Drawables/Pieces/Triangles.cs +++ b/osu.Game.Mode.Osu/Objects/Drawables/Pieces/Triangles.cs @@ -8,29 +8,24 @@ using OpenTK; namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces { - public class Triangles : Container + public class Triangles : Container { - private Texture triangle; - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - triangle = textures.Get(@"Play/osu/triangle@2x"); - } - protected override void LoadComplete() { base.LoadComplete(); + + const float size = 100; for (int i = 0; i < 10; i++) { - Add(new Sprite + Add(new Triangle { - Texture = triangle, Origin = Anchor.Centre, RelativePositionAxes = Axes.Both, Position = new Vector2(RNG.NextSingle(), RNG.NextSingle()), Scale = new Vector2(RNG.NextSingle() * 0.4f + 0.2f), - Alpha = RNG.NextSingle() * 0.3f + // Scaling height by 0.866 results in equiangular triangles (== 60° and equal side length) + Size = new Vector2(size, 0.866f * size), + Alpha = RNG.NextSingle() * 0.3f, }); } } diff --git a/osu.Game.Mode.Osu/Objects/Slider.cs b/osu.Game.Mode.Osu/Objects/Slider.cs index b52a7c7623..a0cdbeae7c 100644 --- a/osu.Game.Mode.Osu/Objects/Slider.cs +++ b/osu.Game.Mode.Osu/Objects/Slider.cs @@ -1,198 +1,26 @@ //Copyright (c) 2007-2016 ppy Pty Ltd . //Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.Collections.Generic; -using OpenTK; +using osu.Game.Database; +using osu.Game.Beatmaps; +using System; namespace osu.Game.Modes.Osu.Objects { public class Slider : OsuHitObject { - public override double EndTime => StartTime + (RepeatCount + 1) * Curve.Length; + public override double EndTime => StartTime + RepeatCount * Curve.Length / Velocity; + + public double Velocity; + + public override void SetDefaultsFromBeatmap(Beatmap beatmap) + { + Velocity = 100 / beatmap.BeatLengthAt(StartTime, true) * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier; + } public int RepeatCount; public SliderCurve Curve; - - } - - public class SliderCurve - { - public double Length; - - public List Path; - - public CurveTypes CurveType; - - private List calculatedPath; - - public void Calculate() - { - switch (CurveType) - { - case CurveTypes.Linear: - calculatedPath = Path; - break; - default: - var bezier = new BezierApproximator(Path); - calculatedPath = bezier.CreateBezier(); - break; - } - } - - public Vector2 PositionAt(double progress) - { - int index = (int)(progress * (calculatedPath.Count - 1)); - - Vector2 pos = calculatedPath[index]; - if (index != progress) - pos += (calculatedPath[index + 1] - pos) * (float)(progress - index); - - return pos; - } - } - - public class BezierApproximator - { - private int count; - private List controlPoints; - private Vector2[] subdivisionBuffer1; - private Vector2[] subdivisionBuffer2; - - private const float TOLERANCE = 0.5f; - private const float TOLERANCE_SQ = TOLERANCE * TOLERANCE; - - public BezierApproximator(List controlPoints) - { - this.controlPoints = controlPoints; - count = controlPoints.Count; - - subdivisionBuffer1 = new Vector2[count]; - subdivisionBuffer2 = new Vector2[count * 2 - 1]; - } - - /// - /// Make sure the 2nd order derivative (approximated using finite elements) is within tolerable bounds. - /// NOTE: The 2nd order derivative of a 2d curve represents its curvature, so intuitively this function - /// checks (as the name suggests) whether our approximation is _locally_ "flat". More curvy parts - /// need to have a denser approximation to be more "flat". - /// - /// The control points to check for flatness. - /// Whether the control points are flat enough. - private static bool IsFlatEnough(Vector2[] controlPoints) - { - for (int i = 1; i < controlPoints.Length - 1; i++) - if ((controlPoints[i - 1] - 2 * controlPoints[i] + controlPoints[i + 1]).LengthSquared > TOLERANCE_SQ) - return false; - - return true; - } - - /// - /// Subdivides n control points representing a bezier curve into 2 sets of n control points, each - /// describing a bezier curve equivalent to a half of the original curve. Effectively this splits - /// the original curve into 2 curves which result in the original curve when pieced back together. - /// - /// The control points to split. - /// Output: The control points corresponding to the left half of the curve. - /// Output: The control points corresponding to the right half of the curve. - private void Subdivide(Vector2[] controlPoints, Vector2[] l, Vector2[] r) - { - Vector2[] midpoints = subdivisionBuffer1; - - for (int i = 0; i < count; ++i) - midpoints[i] = controlPoints[i]; - - for (int i = 0; i < count; i++) - { - l[i] = midpoints[0]; - r[count - i - 1] = midpoints[count - i - 1]; - - for (int j = 0; j < count - i - 1; j++) - midpoints[j] = (midpoints[j] + midpoints[j + 1]) / 2; - } - } - - /// - /// This uses De Casteljau's algorithm to obtain an optimal - /// piecewise-linear approximation of the bezier curve with the same amount of points as there are control points. - /// - /// The control points describing the bezier curve to be approximated. - /// The points representing the resulting piecewise-linear approximation. - private void Approximate(Vector2[] controlPoints, List output) - { - Vector2[] l = subdivisionBuffer2; - Vector2[] r = subdivisionBuffer1; - - Subdivide(controlPoints, l, r); - - for (int i = 0; i < count - 1; ++i) - l[count + i] = r[i + 1]; - - output.Add(controlPoints[0]); - for (int i = 1; i < count - 1; ++i) - { - int index = 2 * i; - Vector2 p = 0.25f * (l[index - 1] + 2 * l[index] + l[index + 1]); - output.Add(p); - } - } - - /// - /// Creates a piecewise-linear approximation of a bezier curve, by adaptively repeatedly subdividing - /// the control points until their approximation error vanishes below a given threshold. - /// - /// The control points describing the curve. - /// A list of vectors representing the piecewise-linear approximation. - public List CreateBezier() - { - List output = new List(); - - if (count == 0) - return output; - - Stack toFlatten = new Stack(); - Stack freeBuffers = new Stack(); - - // "toFlatten" contains all the curves which are not yet approximated well enough. - // We use a stack to emulate recursion without the risk of running into a stack overflow. - // (More specifically, we iteratively and adaptively refine our curve with a - // Depth-first search - // over the tree resulting from the subdivisions we make.) - toFlatten.Push(controlPoints.ToArray()); - - Vector2[] leftChild = subdivisionBuffer2; - - while (toFlatten.Count > 0) - { - Vector2[] parent = toFlatten.Pop(); - if (IsFlatEnough(parent)) - { - // If the control points we currently operate on are sufficiently "flat", we use - // an extension to De Casteljau's algorithm to obtain a piecewise-linear approximation - // of the bezier curve represented by our control points, consisting of the same amount - // of points as there are control points. - Approximate(parent, output); - freeBuffers.Push(parent); - continue; - } - - // If we do not yet have a sufficiently "flat" (in other words, detailed) approximation we keep - // subdividing the curve we are currently operating on. - Vector2[] rightChild = freeBuffers.Count > 0 ? freeBuffers.Pop() : new Vector2[count]; - Subdivide(parent, leftChild, rightChild); - - // We re-use the buffer of the parent for one of the children, so that we save one allocation per iteration. - for (int i = 0; i < count; ++i) - parent[i] = leftChild[i]; - - toFlatten.Push(rightChild); - toFlatten.Push(parent); - } - - output.Add(controlPoints[count - 1]); - return output; - } } public enum CurveTypes @@ -201,5 +29,5 @@ namespace osu.Game.Modes.Osu.Objects Bezier, Linear, PerfectCurve - }; + } } diff --git a/osu.Game.Mode.Osu/Objects/SliderCurve.cs b/osu.Game.Mode.Osu/Objects/SliderCurve.cs new file mode 100644 index 0000000000..4e691196f5 --- /dev/null +++ b/osu.Game.Mode.Osu/Objects/SliderCurve.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; +using OpenTK; + +namespace osu.Game.Modes.Osu.Objects +{ + public class SliderCurve + { + public double Length; + + public List Path; + + public CurveTypes CurveType; + + private List calculatedPath; + + private List calculateSubpath(List subpath) + { + switch (CurveType) + { + case CurveTypes.Linear: + return subpath; + default: + return new BezierApproximator(subpath).CreateBezier(); + } + } + + public void Calculate() + { + calculatedPath = new List(); + + // Sliders may consist of various subpaths separated by two consecutive vertices + // with the same position. The following loop parses these subpaths and computes + // their shape independently, consecutively appending them to calculatedPath. + List subpath = new List(); + for (int i = 0; i < Path.Count; ++i) + { + subpath.Add(Path[i]); + if (i == Path.Count - 1 || Path[i] == Path[i + 1]) + { + // If we already constructed a subpath previously, then the new subpath + // will have as starting position the end position of the previous subpath. + // Hence we can and should remove the previous endpoint to avoid a segment + // with 0 length. + if (calculatedPath.Count > 0) + calculatedPath.RemoveAt(calculatedPath.Count - 1); + + calculatedPath.AddRange(calculateSubpath(subpath)); + subpath.Clear(); + } + } + } + + public Vector2 PositionAt(double progress) + { + progress = MathHelper.Clamp(progress, 0, 1); + + double index = progress * (calculatedPath.Count - 1); + int flooredIndex = (int)index; + + Vector2 pos = calculatedPath[flooredIndex]; + if (index != flooredIndex) + pos += (calculatedPath[flooredIndex + 1] - pos) * (float)(index - flooredIndex); + + return pos; + } + } +} \ No newline at end of file diff --git a/osu.Game.Mode.Osu/OsuRuleset.cs b/osu.Game.Mode.Osu/OsuRuleset.cs index ac43e5501c..15799b134a 100644 --- a/osu.Game.Mode.Osu/OsuRuleset.cs +++ b/osu.Game.Mode.Osu/OsuRuleset.cs @@ -17,6 +17,8 @@ namespace osu.Game.Modes.Osu public override HitObjectParser CreateHitObjectParser() => new OsuHitObjectParser(); + public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(); + protected override PlayMode PlayMode => PlayMode.Osu; } } diff --git a/osu.Game.Mode.Osu/OsuScore.cs b/osu.Game.Mode.Osu/OsuScore.cs new file mode 100644 index 0000000000..5a70cea434 --- /dev/null +++ b/osu.Game.Mode.Osu/OsuScore.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace osu.Game.Modes.Osu +{ + class OsuScore : Score + { + } +} diff --git a/osu.Game.Mode.Osu/OsuScoreProcessor.cs b/osu.Game.Mode.Osu/OsuScoreProcessor.cs new file mode 100644 index 0000000000..a5ebda6834 --- /dev/null +++ b/osu.Game.Mode.Osu/OsuScoreProcessor.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using osu.Game.Modes.Objects.Drawables; +using osu.Game.Modes.Osu.Objects.Drawables; + +namespace osu.Game.Modes.Osu +{ + class OsuScoreProcessor : ScoreProcessor + { + protected override void UpdateCalculations(JudgementInfo judgement) + { + if (judgement != null) + { + switch (judgement.Result) + { + case HitResult.Hit: + Combo.Value++; + break; + case HitResult.Miss: + Combo.Value = 0; + break; + } + } + + int score = 0; + int maxScore = 0; + + foreach (OsuJudgementInfo j in Judgements) + { + switch (j.Score) + { + case OsuScoreResult.Miss: + maxScore += 300; + break; + case OsuScoreResult.Hit50: + score += 50; + maxScore += 300; + break; + case OsuScoreResult.Hit100: + score += 100; + maxScore += 300; + break; + case OsuScoreResult.Hit300: + score += 300; + maxScore += 300; + break; + } + } + + TotalScore.Value = score; + Accuracy.Value = (double)score / maxScore; + } + } +} diff --git a/osu.Game.Mode.Osu/UI/OsuPlayfield.cs b/osu.Game.Mode.Osu/UI/OsuPlayfield.cs index 94638fd79c..1e69cd78a3 100644 --- a/osu.Game.Mode.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Mode.Osu/UI/OsuPlayfield.cs @@ -37,15 +37,6 @@ namespace osu.Game.Modes.Osu.UI AddInternal(new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = Color4.Black, - Depth = float.MinValue, - Alpha = 0.5f, - }, approachCircles = new Container { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game.Mode.Osu/osu.Game.Modes.Osu.csproj b/osu.Game.Mode.Osu/osu.Game.Modes.Osu.csproj index e7efa413c4..ba6e714e0a 100644 --- a/osu.Game.Mode.Osu/osu.Game.Modes.Osu.csproj +++ b/osu.Game.Mode.Osu/osu.Game.Modes.Osu.csproj @@ -41,6 +41,8 @@ + + @@ -52,6 +54,9 @@ + + + diff --git a/osu.Game.Modes.Catch/CatchRuleset.cs b/osu.Game.Modes.Catch/CatchRuleset.cs index 0195859cb7..eac762b4a0 100644 --- a/osu.Game.Modes.Catch/CatchRuleset.cs +++ b/osu.Game.Modes.Catch/CatchRuleset.cs @@ -18,6 +18,8 @@ namespace osu.Game.Modes.Catch protected override PlayMode PlayMode => PlayMode.Catch; + public override ScoreProcessor CreateScoreProcessor() => null; + public override HitObjectParser CreateHitObjectParser() => new OsuHitObjectParser(); } } diff --git a/osu.Game.Modes.Mania/ManiaRuleset.cs b/osu.Game.Modes.Mania/ManiaRuleset.cs index cb122084df..e91b2ed02e 100644 --- a/osu.Game.Modes.Mania/ManiaRuleset.cs +++ b/osu.Game.Modes.Mania/ManiaRuleset.cs @@ -19,6 +19,8 @@ namespace osu.Game.Modes.Mania protected override PlayMode PlayMode => PlayMode.Mania; + public override ScoreProcessor CreateScoreProcessor() => null; + public override HitObjectParser CreateHitObjectParser() => new OsuHitObjectParser(); } } diff --git a/osu.Game.Modes.Taiko/TaikoRuleset.cs b/osu.Game.Modes.Taiko/TaikoRuleset.cs index fa9d0862c7..e706387aaa 100644 --- a/osu.Game.Modes.Taiko/TaikoRuleset.cs +++ b/osu.Game.Modes.Taiko/TaikoRuleset.cs @@ -1,6 +1,7 @@ //Copyright (c) 2007-2016 ppy Pty Ltd . //Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Collections.Generic; using osu.Game.Modes.Objects; using osu.Game.Modes.Osu.Objects; @@ -18,6 +19,8 @@ namespace osu.Game.Modes.Taiko protected override PlayMode PlayMode => PlayMode.Taiko; + public override ScoreProcessor CreateScoreProcessor() => null; + public override HitObjectParser CreateHitObjectParser() => new OsuHitObjectParser(); } } diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 4d6c6a8056..3f8ae0e33a 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -16,5 +16,27 @@ namespace osu.Game.Beatmaps public List HitObjects { get; set; } public List ControlPoints { get; set; } public List ComboColors { get; set; } + + public double BeatLengthAt(double time, bool applyMultipliers = false) + { + int point = 0; + int samplePoint = 0; + + for (int i = 0; i < ControlPoints.Count; i++) + if (ControlPoints[i].Time <= time) + { + if (ControlPoints[i].TimingChange) + point = i; + else + samplePoint = i; + } + + double mult = 1; + + if (applyMultipliers && samplePoint > point && ControlPoints[samplePoint].BeatLength < 0) + mult = ControlPoints[samplePoint].VelocityAdjustment; + + return ControlPoints[point].BeatLength * mult; + } } } diff --git a/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs new file mode 100644 index 0000000000..e90829873f --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs @@ -0,0 +1,24 @@ +//Copyright (c) 2007-2016 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; + +namespace osu.Game.Beatmaps.Drawables +{ + class BeatmapBackgroundSprite : Sprite + { + private readonly WorkingBeatmap working; + + public BeatmapBackgroundSprite(WorkingBeatmap working) + { + this.working = working; + } + + [BackgroundDependencyLoader] + private void load(OsuGameBase game) + { + Texture = working.Background; + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs index eed7e9369e..3f5fe58ab5 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs @@ -35,14 +35,8 @@ namespace osu.Game.Beatmaps.Drawables switch (state) { case BeatmapGroupState.Expanded: - //if (!difficulties.Children.All(d => IsLoaded)) - // Task.WhenAll(difficulties.Children.Select(d => d.Preload(Game))).ContinueWith(t => difficulties.Show()); - //else foreach (BeatmapPanel panel in BeatmapPanels) - { - panel.Hidden = false; panel.FadeIn(250); - } Header.State = PanelSelectedState.Selected; if (SelectedPanel != null) @@ -54,11 +48,7 @@ namespace osu.Game.Beatmaps.Drawables SelectedPanel.State = PanelSelectedState.NotSelected; foreach (BeatmapPanel panel in BeatmapPanels) - { - panel.Hidden = true; panel.FadeOut(250); - } - break; } } @@ -76,6 +66,7 @@ namespace osu.Game.Beatmaps.Drawables BeatmapPanels = beatmap.BeatmapSetInfo.Beatmaps.Select(b => new BeatmapPanel(b) { + Alpha = 0, GainedSelection = panelGainedSelection, RelativeSizeAxes = Axes.X, }).ToList(); diff --git a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs b/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs index 6de25a853b..72d1e2f05d 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs @@ -2,13 +2,17 @@ //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.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.MathUtils; using osu.Game.Database; using osu.Game.Graphics; +using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.UserInterface; using OpenTK; using OpenTK.Graphics; @@ -22,6 +26,8 @@ namespace osu.Game.Beatmaps.Drawables public Action GainedSelection; + Color4 deselectedColour = new Color4(20, 43, 51, 255); + protected override void Selected() { base.Selected(); @@ -36,7 +42,7 @@ namespace osu.Game.Beatmaps.Drawables { base.Deselected(); - background.Colour = new Color4(20, 43, 51, 255); + background.Colour = deselectedColour; } public BeatmapPanel(BeatmapInfo beatmap) @@ -44,12 +50,22 @@ namespace osu.Game.Beatmaps.Drawables Beatmap = beatmap; Height *= 0.60f; - Children = new Framework.Graphics.Drawable[] + Children = new Drawable[] { background = new Box { RelativeSizeAxes = Axes.Both, }, + new Triangles + { + // The border is drawn in the shader of the children. Being additive, triangles would over-emphasize + // the border wherever they cross it, and thus they get their own masking container without a border. + Masking = true, + CornerRadius = Content.CornerRadius, + RelativeSizeAxes = Axes.Both, + BlendingMode = BlendingMode.Additive, + Colour = deselectedColour, + }, new FlowContainer { Padding = new MarginPadding(5), @@ -57,7 +73,7 @@ namespace osu.Game.Beatmaps.Drawables AutoSizeAxes = Axes.Both, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Children = new Framework.Graphics.Drawable[] + Children = new Drawable[] { new DifficultyIcon(FontAwesome.fa_dot_circle_o, new Color4(159, 198, 0, 255)) { @@ -71,7 +87,7 @@ namespace osu.Game.Beatmaps.Drawables Spacing = new Vector2(0, 5), Direction = FlowDirection.VerticalOnly, AutoSizeAxes = Axes.Both, - Children = new Framework.Graphics.Drawable[] + Children = new Drawable[] { new FlowContainer { diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs index 709885d132..7234e2ad8b 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs @@ -27,7 +27,6 @@ namespace osu.Game.Beatmaps.Drawables public BeatmapSetHeader(WorkingBeatmap beatmap) { this.beatmap = beatmap; - Hidden = false; Children = new Drawable[] { @@ -72,6 +71,12 @@ namespace osu.Game.Beatmaps.Drawables }; } + protected override void LoadComplete() + { + base.LoadComplete(); + FadeInFromZero(250); + } + protected override void Selected() { base.Selected(); @@ -114,7 +119,7 @@ namespace osu.Game.Beatmaps.Drawables { new FlowContainer { - Depth = 1, + Depth = -1, Direction = FlowDirection.HorizontalOnly, RelativeSizeAxes = Axes.Both, // This makes the gradient not be perfectly horizontal, but diagonal at a ~40° angle @@ -156,38 +161,17 @@ namespace osu.Game.Beatmaps.Drawables [BackgroundDependencyLoader] private void load(OsuGameBase game) { - new BeatmapBackground(working) + new BeatmapBackgroundSprite(working) { Anchor = Anchor.Centre, Origin = Anchor.Centre, + FillMode = FillMode.Fill, }.Preload(game, (bg) => { Add(bg); ForceRedraw(); }); } - - class BeatmapBackground : Sprite - { - private readonly WorkingBeatmap working; - - public BeatmapBackground(WorkingBeatmap working) - { - this.working = working; - } - - [BackgroundDependencyLoader] - private void load(OsuGameBase game) - { - Texture = working.Background; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - Scale = new Vector2(1366 / (Texture?.Width ?? 1) * 0.6f); - } - } } } } \ No newline at end of file diff --git a/osu.Game/Beatmaps/Drawables/Panel.cs b/osu.Game/Beatmaps/Drawables/Panel.cs index cef25d24a5..f97a5260d8 100644 --- a/osu.Game/Beatmaps/Drawables/Panel.cs +++ b/osu.Game/Beatmaps/Drawables/Panel.cs @@ -4,6 +4,7 @@ using osu.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Transformations; using osu.Framework.Input; using OpenTK; using OpenTK.Graphics; @@ -14,7 +15,12 @@ namespace osu.Game.Beatmaps.Drawables { public const float MAX_HEIGHT = 80; - public bool Hidden = true; + public override bool RemoveWhenNotAlive => false; + + public bool IsOnScreen; + + public override bool IsAlive => IsOnScreen && base.IsAlive; + private Container nestedContainer; protected override Container Content => nestedContainer; @@ -42,7 +48,6 @@ namespace osu.Game.Beatmaps.Drawables { base.LoadComplete(); applyState(); - FadeInFromZero(250); } private void applyState() diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs index 5468e616f0..b1eaeb467d 100644 --- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs @@ -187,7 +187,25 @@ namespace osu.Game.Beatmaps.Formats private void handleTimingPoints(Beatmap beatmap, string val) { - // TODO + ControlPoint cp = null; + + string[] split = val.Split(','); + + if (split.Length > 2) + { + int kiai_flags = split.Length > 7 ? Convert.ToInt32(split[7], NumberFormatInfo.InvariantInfo) : 0; + double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo); + cp = new ControlPoint + { + Time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo), + BeatLength = beatLength > 0 ? beatLength : 0, + VelocityAdjustment = beatLength < 0 ? -beatLength / 100.0 : 1, + TimingChange = split.Length <= 6 || split[6][0] == '1', + }; + } + + if (cp != null) + beatmap.ControlPoints.Add(cp); } private void handleColours(Beatmap beatmap, string key, string val) @@ -275,8 +293,12 @@ namespace osu.Game.Beatmaps.Formats break; case Section.HitObjects: var obj = parser?.Parse(val); + if (obj != null) + { + obj.SetDefaultsFromBeatmap(beatmap); beatmap.HitObjects.Add(obj); + } break; } } diff --git a/osu.Game/Beatmaps/Timing/ControlPoint.cs b/osu.Game/Beatmaps/Timing/ControlPoint.cs index 89eac572ec..6e83760c8d 100644 --- a/osu.Game/Beatmaps/Timing/ControlPoint.cs +++ b/osu.Game/Beatmaps/Timing/ControlPoint.cs @@ -12,5 +12,14 @@ namespace osu.Game.Beatmaps.Timing public class ControlPoint { public double Time; + public double BeatLength; + public double VelocityAdjustment; + public bool TimingChange; + } + + internal enum TimeSignatures + { + SimpleQuadruple = 4, + SimpleTriple = 3 } } diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 86e26048ab..8657f74d87 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -156,7 +156,7 @@ namespace osu.Game.Configuration Set(OsuConfig.AudioDevice, string.Empty); //Set(OsuConfig.ReleaseStream, ReleaseStream.Lazer, true); Set(OsuConfig.UpdateFailCount, 0); - //Set(OsuConfig.SavePassword, Password != null); + Set(OsuConfig.SavePassword, false); Set(OsuConfig.SaveUsername, true); //Set(OsuConfig.TreeSortMode, TreeGroupMode.Show_All); //Set(OsuConfig.TreeSortMode2, TreeSortMode.Title); diff --git a/osu.Game/Graphics/Backgrounds/Background.cs b/osu.Game/Graphics/Backgrounds/Background.cs index 368bb7d723..ebb9348af7 100644 --- a/osu.Game/Graphics/Backgrounds/Background.cs +++ b/osu.Game/Graphics/Backgrounds/Background.cs @@ -22,7 +22,7 @@ namespace osu.Game.Graphics.Backgrounds { this.textureName = textureName; RelativeSizeAxes = Axes.Both; - Depth = float.MinValue; + Depth = float.MaxValue; Add(Sprite = new Sprite { diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs new file mode 100644 index 0000000000..34e65f0b2a --- /dev/null +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -0,0 +1,79 @@ +//Copyright (c) 2007-2016 ppy Pty Ltd . +//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.MathUtils; +using OpenTK; + +namespace osu.Game.Graphics.Backgrounds +{ + public class Triangles : Container + { + public Triangles() + { + Alpha = 0.3f; + } + + private float triangleScale = 1; + + public float TriangleScale + { + get { return triangleScale; } + set + { + triangleScale = value; + + Children.ForEach(t => t.ScaleTo(triangleScale)); + } + } + + private int aimTriangleCount => (int)((DrawWidth * DrawHeight) / 800 / triangleScale); + + protected override void Update() + { + base.Update(); + + foreach (Drawable d in Children) + { + d.Position -= new Vector2(0, (float)(d.Scale.X * (50 / DrawHeight) * (Time.Elapsed / 880)) / triangleScale); + if (d.DrawPosition.Y + d.DrawSize.Y * d.Scale.Y < 0) + d.Expire(); + } + + bool useRandomX = Children.Count() < aimTriangleCount / 2; + while (Children.Count() < aimTriangleCount) + addTriangle(useRandomX); + + } + + protected virtual Triangle CreateTriangle() + { + var scale = triangleScale * RNG.NextSingle() * 0.4f + 0.2f; + const float size = 100; + + return new Triangle + { + Origin = Anchor.TopCentre, + RelativePositionAxes = Axes.Both, + Scale = new Vector2(scale), + // Scaling height by 0.866 results in equiangular triangles (== 60° and equal side length) + Size = new Vector2(size, 0.866f * size), + Alpha = RNG.NextSingle(), + Depth = scale, + }; + } + + private void addTriangle(bool randomX) + { + var sprite = CreateTriangle(); + sprite.Position = new Vector2(RNG.NextSingle(), randomX ? RNG.NextSingle() : 1); + Add(sprite); + } + } +} diff --git a/osu.Game/Graphics/Containers/ParallaxContainer.cs b/osu.Game/Graphics/Containers/ParallaxContainer.cs index cf61529cd2..5cc8a4097b 100644 --- a/osu.Game/Graphics/Containers/ParallaxContainer.cs +++ b/osu.Game/Graphics/Containers/ParallaxContainer.cs @@ -4,6 +4,7 @@ using osu.Framework.Input; using OpenTK; using osu.Framework; using osu.Framework.Allocation; +using osu.Framework.Graphics.Transformations; namespace osu.Game.Graphics.Containers { @@ -35,11 +36,16 @@ namespace osu.Game.Graphics.Containers this.input = input; } + bool firstUpdate = true; + protected override void Update() { base.Update(); - content.Position = (ToLocalSpace(input.CurrentState.Mouse.NativeState.Position) - DrawSize / 2) * ParallaxAmount; + + content.MoveTo((ToLocalSpace(input.CurrentState.Mouse.NativeState.Position) - DrawSize / 2) * ParallaxAmount, firstUpdate ? 0 : 1000, EasingTypes.OutQuint); content.Scale = new Vector2(1 + ParallaxAmount); + + firstUpdate = false; } } } diff --git a/osu.Game/Graphics/Cursor/CursorTrail.cs b/osu.Game/Graphics/Cursor/CursorTrail.cs new file mode 100644 index 0000000000..f2060bf8a3 --- /dev/null +++ b/osu.Game/Graphics/Cursor/CursorTrail.cs @@ -0,0 +1,228 @@ +//Copyright (c) 2007-2016 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.Shaders; +using osu.Framework.Graphics.Textures; +using osu.Framework.Input; +using OpenTK; +using System; +using osu.Framework.Graphics.OpenGL; +using osu.Framework.Graphics.OpenGL.Buffers; +using OpenTK.Graphics.ES30; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Colour; + +namespace osu.Game.Graphics.Cursor +{ + + class CursorTrail : Drawable + { + public override bool Contains(Vector2 screenSpacePos) => true; + public override bool HandleInput => true; + + private int currentIndex; + + private Shader shader; + private Texture texture; + + private Vector2 size => texture.Size * Scale; + + private double timeOffset; + + private float time; + + private TrailDrawNodeSharedData trailDrawNodeSharedData = new TrailDrawNodeSharedData(); + private const int MAX_SPRITES = 2048; + + private TrailPart[] parts = new TrailPart[MAX_SPRITES]; + + private Vector2? lastPosition; + + protected override DrawNode CreateDrawNode() => new TrailDrawNode(); + + protected override void ApplyDrawNode(DrawNode node) + { + base.ApplyDrawNode(node); + + TrailDrawNode tNode = node as TrailDrawNode; + tNode.Shader = shader; + tNode.Texture = texture; + tNode.Size = size; + tNode.Time = time; + tNode.Shared = trailDrawNodeSharedData; + + for (int i = 0; i < parts.Length; ++i) + if (parts[i].InvalidationID > tNode.Parts[i].InvalidationID) + tNode.Parts[i] = parts[i]; + } + + public CursorTrail() + { + RelativeSizeAxes = Axes.Both; + + for (int i = 0; i < MAX_SPRITES; i++) + { + parts[i].InvalidationID = 0; + parts[i].WasUpdated = true; + } + } + + [BackgroundDependencyLoader] + private void load(ShaderManager shaders, TextureStore textures) + { + shader = shaders?.Load(@"CursorTrail", FragmentShaderDescriptor.Texture); + texture = textures.Get(@"Cursor/cursortrail"); + } + + protected override void Update() + { + base.Update(); + + Invalidate(Invalidation.DrawNode, shallPropagate: false); + + int fadeClockResetThreshold = 1000000; + + time = (float)(Time.Current - timeOffset) / 500f; + if (time > fadeClockResetThreshold) + ResetTime(); + } + + private void ResetTime() + { + for (int i = 0; i < parts.Length; ++i) + { + parts[i].Time -= time; + ++parts[i].InvalidationID; + } + + time = 0; + timeOffset = Time.Current; + } + + protected override bool OnMouseMove(InputState state) + { + if (lastPosition == null) + { + lastPosition = state.Mouse.NativeState.Position; + 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) + { + lastPosition = pos1 + direction * d; + addPosition(lastPosition.Value); + } + + return base.OnMouseMove(state); + } + + private void addPosition(Vector2 pos) + { + parts[currentIndex].Position = pos; + parts[currentIndex].Time = time; + ++parts[currentIndex].InvalidationID; + + currentIndex = (currentIndex + 1) % MAX_SPRITES; + } + + struct TrailPart + { + public Vector2 Position; + public float Time; + public long InvalidationID; + public bool WasUpdated; + } + + class TrailDrawNodeSharedData + { + public VertexBuffer VertexBuffer; + } + + class TrailDrawNode : DrawNode + { + public Shader Shader; + public Texture Texture; + + public float Time; + public TrailDrawNodeSharedData Shared; + + public TrailPart[] Parts = new TrailPart[MAX_SPRITES]; + public Vector2 Size; + + public TrailDrawNode() + { + for (int i = 0; i < MAX_SPRITES; i++) + { + Parts[i].InvalidationID = 0; + Parts[i].WasUpdated = false; + } + } + + public override void Draw(Action vertexAction) + { + if (Shared.VertexBuffer == null) + Shared.VertexBuffer = new QuadVertexBuffer(MAX_SPRITES, BufferUsageHint.DynamicDraw); + + Shader.GetUniform("g_FadeClock").Value = Time; + + int updateStart = -1, updateEnd = 0; + for (int i = 0; i < Parts.Length; ++i) + { + if (Parts[i].WasUpdated) + { + if (updateStart == -1) + updateStart = i; + updateEnd = i + 1; + + int start = i * 4; + 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; + + Texture.DrawQuad( + new Quad(pos.X - Size.X / 2, pos.Y - Size.Y / 2, Size.X, Size.Y), + colour, + null, + v => Shared.VertexBuffer.Vertices[end++] = v); + + Parts[i].WasUpdated = false; + } + else if (updateStart != -1) + { + Shared.VertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4); + updateStart = -1; + } + } + + // Update all remaining vertices that have been changed. + if (updateStart != -1) + Shared.VertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4); + + base.Draw(vertexAction); + + Shader.Bind(); + + Texture.TextureGL.Bind(); + Shared.VertexBuffer.Draw(); + + Shader.Unbind(); + } + } + } +} \ No newline at end of file diff --git a/osu.Game/Graphics/Cursor/OsuCursorContainer.cs b/osu.Game/Graphics/Cursor/OsuCursorContainer.cs index 081919feaf..0b36e006fd 100644 --- a/osu.Game/Graphics/Cursor/OsuCursorContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuCursorContainer.cs @@ -1,7 +1,7 @@ //Copyright (c) 2007-2016 ppy Pty Ltd . //Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework; +using OpenTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -17,9 +17,14 @@ namespace osu.Game.Graphics.Cursor { protected override Drawable CreateCursor() => new OsuCursor(); + public OsuCursorContainer() + { + Add(new CursorTrail { Depth = 1 }); + } + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { - ActiveCursor.Scale = new OpenTK.Vector2(1); + ActiveCursor.Scale = new Vector2(1); ActiveCursor.ScaleTo(1.2f, 100, EasingTypes.OutQuad); return base.OnMouseDown(state, args); } @@ -52,5 +57,4 @@ namespace osu.Game.Graphics.Cursor } } } - } diff --git a/osu.Game/Graphics/UserInterface/BackButton.cs b/osu.Game/Graphics/UserInterface/BackButton.cs new file mode 100644 index 0000000000..7ccc5c061b --- /dev/null +++ b/osu.Game/Graphics/UserInterface/BackButton.cs @@ -0,0 +1,160 @@ +// Copyright (c) 2007-2016 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Transformations; +using osu.Framework.Input; + +namespace osu.Game.Graphics.UserInterface +{ + // Basic back button as it was on stable (kinda). No skinning possible for now + public class BackButton : ClickableContainer + { + private TextAwesome icon; + + private Container leftContainer; + private Container rightContainer; + + private Box leftBox; + private Box rightBox; + + private const double transform_time = 300.0; + private const int pulse_length = 250; + + private const float shear = 0.1f; + + private static readonly Vector2 size_extended = new Vector2(140, 50); + private static readonly Vector2 size_retracted = new Vector2(100, 50); + + public BackButton() + { + Size = size_retracted; + + Children = new Drawable[] + { + leftContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Width = 0.4f, + Children = new Drawable[] + { + leftBox = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = new Color4(195, 40, 140, 255), + Shear = new Vector2(shear, 0), + }, + icon = new TextAwesome + { + Anchor = Anchor.Centre, + TextSize = 25, + Icon = FontAwesome.fa_osu_left_o + }, + } + }, + rightContainer = new Container + { + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + RelativeSizeAxes = Axes.Both, + Width = 0.6f, + Children = new Drawable[] + { + rightBox = new Box + { + Colour = new Color4(238, 51, 153, 255), + Origin = Anchor.TopLeft, + Anchor = Anchor.TopLeft, + RelativeSizeAxes = Axes.Both, + Shear = new Vector2(shear, 0), + EdgeSmoothness = new Vector2(1.5f, 0), + }, + new SpriteText + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Text = @"Back", + } + } + } + }; + } + + public override bool Contains(Vector2 screenSpacePos) + { + return leftBox.Contains(screenSpacePos) || rightBox.Contains(screenSpacePos); + } + + protected override bool OnHover(InputState state) + { + icon.ClearTransformations(); + + ResizeTo(size_extended, transform_time, EasingTypes.OutElastic); + + int duration = 0; //(int)(Game.Audio.BeatLength / 2); + if (duration == 0) duration = pulse_length; + + double offset = 0; //(1 - Game.Audio.SyncBeatProgress) * duration; + double startTime = Time.Current + offset; + + // basic pulse + icon.Transforms.Add(new TransformScale + { + StartValue = new Vector2(1.1f), + EndValue = Vector2.One, + StartTime = startTime, + EndTime = startTime + duration, + Easing = EasingTypes.Out, + LoopCount = -1, + LoopDelay = duration + }); + + return true; + } + + protected override void OnHoverLost(InputState state) + { + icon.ClearTransformations(); + + ResizeTo(size_retracted, transform_time, EasingTypes.OutElastic); + + int duration = 0; //(int)(Game.Audio.BeatLength); + if (duration == 0) duration = pulse_length * 2; + + double offset = 0; //(1 - Game.Audio.SyncBeatProgress) * duration; + double startTime = Time.Current + offset; + + // slow pulse + icon.Transforms.Add(new TransformScale + { + StartValue = new Vector2(1.1f), + EndValue = Vector2.One, + StartTime = startTime, + EndTime = startTime + duration, + Easing = EasingTypes.Out, + LoopCount = -1, + LoopDelay = duration + }); + } + + protected override bool OnClick(InputState state) + { + var flash = new Box + { + RelativeSizeAxes = Axes.Both, + Shear = new Vector2(shear, 0), + Colour = new Color4(255, 255, 255, 128), + }; + Add(flash); + + flash.FadeOutFromOne(200); + flash.Expire(); + + return base.OnClick(state); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs index 364ef1c7aa..13accc4914 100644 --- a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs +++ b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs @@ -6,17 +6,14 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Threading; using OpenTK; -using osu.Framework.Allocation; using osu.Framework.Graphics.Primitives; +using osu.Framework.Audio; +using osu.Framework.Allocation; namespace osu.Game.Graphics.UserInterface.Volume { internal class VolumeControl : OverlayContainer { - public BindableDouble VolumeGlobal { get; set; } - public BindableDouble VolumeSample { get; set; } - public BindableDouble VolumeTrack { get; set; } - private VolumeMeter volumeMeterMaster; private void volumeChanged(object sender, EventArgs e) @@ -54,21 +51,18 @@ namespace osu.Game.Graphics.UserInterface.Volume { base.LoadComplete(); - VolumeGlobal.ValueChanged += volumeChanged; - VolumeSample.ValueChanged += volumeChanged; - VolumeTrack.ValueChanged += volumeChanged; - - volumeMeterMaster.Bindable = VolumeGlobal; - volumeMeterEffect.Bindable = VolumeSample; - volumeMeterMusic.Bindable = VolumeTrack; + volumeMeterMaster.Bindable.ValueChanged += volumeChanged; + volumeMeterEffect.Bindable.ValueChanged += volumeChanged; + volumeMeterMusic.Bindable.ValueChanged += volumeChanged; } protected override void Dispose(bool isDisposing) { - VolumeGlobal.ValueChanged -= volumeChanged; - VolumeSample.ValueChanged -= volumeChanged; - VolumeTrack.ValueChanged -= volumeChanged; base.Dispose(isDisposing); + + volumeMeterMaster.Bindable.ValueChanged -= volumeChanged; + volumeMeterEffect.Bindable.ValueChanged -= volumeChanged; + volumeMeterMusic.Bindable.ValueChanged -= volumeChanged; } public void Adjust(InputState state) @@ -82,6 +76,14 @@ namespace osu.Game.Graphics.UserInterface.Volume volumeMeterMaster.TriggerWheel(state); } + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + volumeMeterMaster.Bindable.Weld(audio.Volume); + volumeMeterEffect.Bindable.Weld(audio.VolumeSample); + volumeMeterMusic.Bindable.Weld(audio.VolumeTrack); + } + ScheduledDelegate popOutDelegate; private VolumeMeter volumeMeterEffect; diff --git a/osu.Game/Graphics/UserInterface/Volume/VolumeMeter.cs b/osu.Game/Graphics/UserInterface/Volume/VolumeMeter.cs index f13019e72c..4416a1da80 100644 --- a/osu.Game/Graphics/UserInterface/Volume/VolumeMeter.cs +++ b/osu.Game/Graphics/UserInterface/Volume/VolumeMeter.cs @@ -13,7 +13,7 @@ namespace osu.Game.Graphics.UserInterface.Volume internal class VolumeMeter : Container { private Box meterFill; - public BindableDouble Bindable; + public BindableDouble Bindable { get; private set; } = new BindableDouble(); public VolumeMeter(string meterName) { @@ -41,6 +41,7 @@ namespace osu.Game.Graphics.UserInterface.Volume meterFill = new Box { Colour = Color4.White, + Scale = new Vector2(1, 0), RelativeSizeAxes = Axes.Both, Origin = Anchor.BottomCentre, Anchor = Anchor.BottomCentre @@ -54,6 +55,8 @@ namespace osu.Game.Graphics.UserInterface.Volume Origin = Anchor.TopCentre } }; + + Bindable.ValueChanged += delegate { updateFill(); }; } protected override void LoadComplete() @@ -68,7 +71,6 @@ namespace osu.Game.Graphics.UserInterface.Volume private set { Bindable.Value = value; - updateFill(); } } diff --git a/osu.Game/Modes/HitJudgementResolver.cs b/osu.Game/Modes/HitJudgementResolver.cs deleted file mode 100644 index 5f2afec369..0000000000 --- a/osu.Game/Modes/HitJudgementResolver.cs +++ /dev/null @@ -1,51 +0,0 @@ -//Copyright (c) 2007-2016 ppy Pty Ltd . -//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using osu.Game.Modes.Objects; -using OpenTK; - -namespace osu.Game.Modes -{ - public class HitJudgementResolver - { - public JudgementResult CheckJudgement(HitObject h) => new JudgementResult { Combo = ComboJudgement.None, Judgement = Judgement.Hit300 }; - } - - public struct JudgementResult - { - public ComboJudgement Combo; - public Judgement Judgement; - public float TimeOffset; - public Vector2 PositionOffset; - } - - public enum ComboJudgement - { - [Description(@"")] - None, - [Description(@"Good")] - Good, - [Description(@"Amazing")] - Perfect - } - - public enum Judgement - { - [Description(@"Miss")] - Miss, - [Description(@"50")] - Hit50, - [Description(@"100")] - Hit100, - [Description(@"300")] - Hit300, - [Description(@"500")] - Hit500 - } -} diff --git a/osu.Game/Modes/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Modes/Objects/Drawables/DrawableHitObject.cs index 387d5d061a..8e41fb6521 100644 --- a/osu.Game/Modes/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Modes/Objects/Drawables/DrawableHitObject.cs @@ -2,29 +2,31 @@ //Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.ComponentModel; +using System.Diagnostics; using osu.Framework; using osu.Framework.Graphics.Containers; +using OpenTK; +using Container = osu.Framework.Graphics.Containers.Container; namespace osu.Game.Modes.Objects.Drawables { public abstract class DrawableHitObject : Container, IStateful { - //todo: move to a more central implementation. this logic should not be at a drawable level. - public Action OnHit; - public Action OnMiss; - - public Func AllowHit; + public event Action OnJudgement; public Container ChildObjects; - public JudgementResult Result; + public JudgementInfo Judgement; + + public abstract JudgementInfo CreateJudgementInfo(); public HitObject HitObject; public DrawableHitObject(HitObject hitObject) { HitObject = hitObject; - Depth = -(float)hitObject.StartTime; + Depth = (float)hitObject.StartTime; } private ArmedState state; @@ -41,36 +43,55 @@ namespace osu.Game.Modes.Objects.Drawables } } - protected double? HitTime; - - protected virtual bool Hit() + protected override void LoadComplete() { - if (State != ArmedState.Disarmed) + base.LoadComplete(); + + Judgement = CreateJudgementInfo(); + } + + /// + /// Process a hit of this hitobject. Carries out judgement. + /// + /// Preliminary judgement information provided by the hit source. + /// Whether a hit was processed. + protected bool UpdateJudgement(bool userTriggered) + { + if (Judgement.Result != null) return false; - if (AllowHit?.Invoke(this) == false) + Judgement.TimeOffset = Time.Current - HitObject.EndTime; + + CheckJudgement(userTriggered); + + if (Judgement.Result == null) return false; - HitTime = Time.Current; + switch (Judgement.Result) + { + default: + State = ArmedState.Hit; + break; + case HitResult.Miss: + State = ArmedState.Miss; + break; + } + + OnJudgement?.Invoke(this, Judgement); - State = ArmedState.Armed; return true; } - private bool counted; + protected virtual void CheckJudgement(bool userTriggered) + { + //todo: consider making abstract. + } protected override void Update() { base.Update(); - if (Time.Current >= HitObject.EndTime && !counted) - { - counted = true; - if (state == ArmedState.Armed) - OnHit?.Invoke(this); - else - OnMiss?.Invoke(this); - } + UpdateJudgement(false); } protected abstract void UpdateState(ArmedState state); @@ -78,7 +99,28 @@ namespace osu.Game.Modes.Objects.Drawables public enum ArmedState { - Disarmed, - Armed + Idle, + Hit, + Miss + } + + public class PositionalJudgementInfo : JudgementInfo + { + public Vector2 PositionOffset; + } + + public class JudgementInfo + { + public ulong? ComboAtHit; + public HitResult? Result; + public double TimeOffset; + } + + public enum HitResult + { + [Description(@"Miss")] + Miss, + [Description(@"Hit")] + Hit, } } diff --git a/osu.Game/Modes/Objects/HitObject.cs b/osu.Game/Modes/Objects/HitObject.cs index abb88726b6..71839e7036 100644 --- a/osu.Game/Modes/Objects/HitObject.cs +++ b/osu.Game/Modes/Objects/HitObject.cs @@ -1,6 +1,7 @@ //Copyright (c) 2007-2016 ppy Pty Ltd . //Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Game.Beatmaps; using osu.Game.Beatmaps.Samples; using OpenTK.Graphics; @@ -21,5 +22,7 @@ namespace osu.Game.Modes.Objects public double Duration => EndTime - StartTime; public HitSampleInfo Sample; + + public virtual void SetDefaultsFromBeatmap(Beatmap beatmap) { } } } diff --git a/osu.Game/Modes/Ruleset.cs b/osu.Game/Modes/Ruleset.cs index 6576159f62..b0304f6576 100644 --- a/osu.Game/Modes/Ruleset.cs +++ b/osu.Game/Modes/Ruleset.cs @@ -18,12 +18,12 @@ namespace osu.Game.Modes public abstract ScoreOverlay CreateScoreOverlay(); + public abstract ScoreProcessor CreateScoreProcessor(); + public abstract HitRenderer CreateHitRendererWith(List objects); public abstract HitObjectParser CreateHitObjectParser(); - public virtual HitJudgementResolver CreateHitJudgement() => new HitJudgementResolver(); - public static void Register(Ruleset ruleset) => availableRulesets.TryAdd(ruleset.PlayMode, ruleset.GetType()); protected abstract PlayMode PlayMode { get; } diff --git a/osu.Game/Modes/Score.cs b/osu.Game/Modes/Score.cs new file mode 100644 index 0000000000..ee90a9a0f9 --- /dev/null +++ b/osu.Game/Modes/Score.cs @@ -0,0 +1,19 @@ +//Copyright (c) 2007-2016 ppy Pty Ltd . +//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace osu.Game.Modes +{ + public class Score + { + public double TotalScore { get; set; } + public double Accuracy { get; set; } + public double Combo { get; set; } + public double MaxCombo { get; set; } + } +} diff --git a/osu.Game/Modes/ScoreProcesssor.cs b/osu.Game/Modes/ScoreProcesssor.cs new file mode 100644 index 0000000000..a34915eaef --- /dev/null +++ b/osu.Game/Modes/ScoreProcesssor.cs @@ -0,0 +1,54 @@ +//Copyright (c) 2007-2016 ppy Pty Ltd . +//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using osu.Framework.Configuration; +using osu.Game.Modes.Objects.Drawables; + +namespace osu.Game.Modes +{ + public abstract class ScoreProcessor + { + public virtual Score GetScore() => new Score() + { + TotalScore = TotalScore, + Combo = Combo, + MaxCombo = HighestCombo, + Accuracy = Accuracy + }; + + public readonly BindableDouble TotalScore = new BindableDouble { MinValue = 0 }; + + public readonly BindableDouble Accuracy = new BindableDouble { MinValue = 0, MaxValue = 1 }; + + public readonly BindableInt Combo = new BindableInt(); + + public readonly BindableInt HighestCombo = new BindableInt(); + + public readonly List Judgements = new List(); + + public ScoreProcessor() + { + Combo.ValueChanged += delegate { HighestCombo.Value = Math.Max(HighestCombo.Value, Combo.Value); }; + } + + public void AddJudgement(JudgementInfo judgement) + { + Judgements.Add(judgement); + + UpdateCalculations(judgement); + + judgement.ComboAtHit = (ulong)Combo.Value; + } + + /// + /// Update any values that potentially need post-processing on a judgement change. + /// + /// A new JudgementInfo that triggered this calculation. May be null. + protected abstract void UpdateCalculations(JudgementInfo newJudgement); + } +} diff --git a/osu.Game/Modes/UI/ComboCounter.cs b/osu.Game/Modes/UI/ComboCounter.cs index 0d34d5dd9a..b150111387 100644 --- a/osu.Game/Modes/UI/ComboCounter.cs +++ b/osu.Game/Modes/UI/ComboCounter.cs @@ -262,5 +262,13 @@ namespace osu.Game.Modes.UI (d as ComboCounter).DisplayedCount = CurrentValue; } } + + public void Set(ulong value) + { + if (value == 0) + Roll(); + else + Count = value; + } } } diff --git a/osu.Game/Modes/UI/HitRenderer.cs b/osu.Game/Modes/UI/HitRenderer.cs index f160356cb4..bd191a47a3 100644 --- a/osu.Game/Modes/UI/HitRenderer.cs +++ b/osu.Game/Modes/UI/HitRenderer.cs @@ -14,11 +14,21 @@ namespace osu.Game.Modes.UI { public abstract class HitRenderer : Container { - public Action OnHit; - public Action OnMiss; + public event Action OnJudgement; + + public event Action OnAllJudged; + + protected void TriggerOnJudgement(JudgementInfo j) + { + OnJudgement?.Invoke(j); + if (AllObjectsJudged) + OnAllJudged?.Invoke(); + } protected Playfield Playfield; + public bool AllObjectsJudged => Playfield.HitObjects.Children.First()?.Judgement.Result != null; //reverse depth sort means First() instead of Last(). + public IEnumerable DrawableObjects => Playfield.HitObjects.Children; } @@ -68,22 +78,13 @@ namespace osu.Game.Modes.UI if (drawableObject == null) continue; - drawableObject.OnHit = onHit; - drawableObject.OnMiss = onMiss; + drawableObject.OnJudgement += onJudgement; Playfield.Add(drawableObject); } } - private void onMiss(DrawableHitObject obj) - { - OnMiss?.Invoke(obj.HitObject); - } - - private void onHit(DrawableHitObject obj) - { - OnHit?.Invoke(obj.HitObject); - } + private void onJudgement(DrawableHitObject o, JudgementInfo j) => TriggerOnJudgement(j); protected abstract DrawableHitObject GetVisualRepresentation(T h); } diff --git a/osu.Game/Modes/UI/Playfield.cs b/osu.Game/Modes/UI/Playfield.cs index 8bfd82e407..ff8a03db2a 100644 --- a/osu.Game/Modes/UI/Playfield.cs +++ b/osu.Game/Modes/UI/Playfield.cs @@ -14,6 +14,8 @@ namespace osu.Game.Modes.UI public virtual void Add(DrawableHitObject h) => HitObjects.Add(h); + public override bool Contains(Vector2 screenSpacePos) => true; + public Playfield() { AddInternal(HitObjects = new HitObjectContainer @@ -25,6 +27,8 @@ namespace osu.Game.Modes.UI public class HitObjectContainer : Container { protected override Vector2 DrawScale => new Vector2(DrawSize.X / 512); + + public override bool Contains(Vector2 screenSpacePos) => true; } } } diff --git a/osu.Game/Modes/UI/ScoreOverlay.cs b/osu.Game/Modes/UI/ScoreOverlay.cs index 2270533d47..c7441483f8 100644 --- a/osu.Game/Modes/UI/ScoreOverlay.cs +++ b/osu.Game/Modes/UI/ScoreOverlay.cs @@ -15,6 +15,7 @@ namespace osu.Game.Modes.UI public ComboCounter ComboCounter; public ScoreCounter ScoreCounter; public PercentageCounter AccuracyCounter; + public Score Score { get; set; } protected abstract KeyCounterCollection CreateKeyCounter(); protected abstract ComboCounter CreateComboCounter(); @@ -45,5 +46,13 @@ namespace osu.Game.Modes.UI AccuracyCounter = CreateAccuracyCounter(), }; } + + public void BindProcessor(ScoreProcessor processor) + { + //bind processor bindables to combocounter, score display etc. + processor.TotalScore.ValueChanged += delegate { ScoreCounter?.Set((ulong)processor.TotalScore.Value); }; + processor.Accuracy.ValueChanged += delegate { AccuracyCounter?.Set((float)processor.Accuracy.Value); }; + processor.Combo.ValueChanged += delegate { ComboCounter?.Set((ulong)processor.Combo.Value); }; + } } } diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index a7b2239355..68557f63f2 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -3,9 +3,12 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; using System.Net; using System.Threading; using osu.Framework; +using osu.Framework.Configuration; using osu.Framework.Logging; using osu.Framework.Threading; using osu.Game.Online.API.Requests; @@ -17,8 +20,8 @@ namespace osu.Game.Online.API private OAuth authentication; public string Endpoint = @"https://new.ppy.sh"; - const string ClientId = @"daNBnfdv7SppRVc61z0XuOI13y6Hroiz"; - const string ClientSecret = @"d6fgZuZeQ0eSXkEj5igdqQX6ztdtS6Ow"; + const string ClientId = @"5"; + const string ClientSecret = @"FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk"; ConcurrentQueue queue = new ConcurrentQueue(); @@ -26,15 +29,11 @@ namespace osu.Game.Online.API public string Username; - private SecurePassword password; + //private SecurePassword password; - public string Password - { - set - { - password = string.IsNullOrEmpty(value) ? null : new SecurePassword(value); - } - } + public string Password; + + public Bindable LocalUser = new Bindable(); public string Token { @@ -50,7 +49,7 @@ namespace osu.Game.Online.API } } - protected bool HasLogin => Token != null || (!string.IsNullOrEmpty(Username) && password != null); + protected bool HasLogin => Token != null || (!string.IsNullOrEmpty(Username) && !string.IsNullOrEmpty(Password)); private Thread thread; @@ -65,6 +64,25 @@ namespace osu.Game.Online.API thread.Start(); } + private List components = new List(); + + public void Register(IOnlineComponent component) + { + Scheduler.Add(delegate + { + components.Add(component); + component.APIStateChanged(this, state); + }); + } + + public void Unregister(IOnlineComponent component) + { + Scheduler.Add(delegate + { + components.Remove(component); + }); + } + public string AccessToken => authentication.RequestAccessToken(); /// @@ -102,7 +120,7 @@ namespace osu.Game.Online.API if (State < APIState.Connecting) State = APIState.Connecting; - if (!authentication.HasValidAccessToken && !authentication.AuthenticateWithLogin(Username, password.Get(Representation.Raw))) + if (!authentication.HasValidAccessToken && !authentication.AuthenticateWithLogin(Username, Password)) { //todo: this fails even on network-related issues. we should probably handle those differently. //NotificationManager.ShowMessage("Login failed!"); @@ -111,6 +129,17 @@ namespace osu.Game.Online.API continue; } + + var userReq = new GetUserRequest(); + userReq.Success += (u) => { + LocalUser.Value = u; + }; + if (!handleRequest(userReq)) + { + State = APIState.Failing; + continue; + } + //we're connected! State = APIState.Online; failureCount = 0; @@ -142,7 +171,17 @@ namespace osu.Game.Online.API private void ClearCredentials() { Username = null; - password = null; + Password = null; + } + + public void Login(string username, string password) + { + Debug.Assert(State == APIState.Offline); + + Username = username; + Password = password; + + State = APIState.Connecting; } /// @@ -154,6 +193,7 @@ namespace osu.Game.Online.API { try { + Logger.Log($@"Performing request {req}", LoggingTarget.Network); req.Perform(this); State = APIState.Online; @@ -221,6 +261,7 @@ namespace osu.Game.Online.API log.Add($@"We just went {newState}!"); Scheduler.Add(delegate { + components.ForEach(c => c.APIStateChanged(this, newState)); OnStateChange?.Invoke(oldState, newState); }); } @@ -237,29 +278,6 @@ namespace osu.Game.Online.API public delegate void StateChangeDelegate(APIState oldState, APIState newState); - public enum APIState - { - /// - /// We cannot login (not enough credentials). - /// - Offline, - - /// - /// We are having connectivity issues. - /// - Failing, - - /// - /// We are in the process of (re-)connecting. - /// - Connecting, - - /// - /// We are online. - /// - Online - } - private void flushQueue(bool failOldRequests = true) { var oldQueue = queue; @@ -277,6 +295,7 @@ namespace osu.Game.Online.API public void Logout() { + ClearCredentials(); authentication.Clear(); State = APIState.Offline; } @@ -286,4 +305,27 @@ namespace osu.Game.Online.API Scheduler.Update(); } } + + public enum APIState + { + /// + /// We cannot login (not enough credentials). + /// + Offline, + + /// + /// We are having connectivity issues. + /// + Failing, + + /// + /// We are in the process of (re-)connecting. + /// + Connecting, + + /// + /// We are online. + /// + Online + } } diff --git a/osu.Game/Online/API/IOnlineComponent.cs b/osu.Game/Online/API/IOnlineComponent.cs new file mode 100644 index 0000000000..0208384921 --- /dev/null +++ b/osu.Game/Online/API/IOnlineComponent.cs @@ -0,0 +1,16 @@ +//Copyright (c) 2007-2016 ppy Pty Ltd . +//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace osu.Game.Online.API +{ + public interface IOnlineComponent + { + void APIStateChanged(APIAccess api, APIState state); + } +} diff --git a/osu.Game/Online/API/OAuth.cs b/osu.Game/Online/API/OAuth.cs index 3358fac0e8..0aa1ed84e1 100644 --- a/osu.Game/Online/API/OAuth.cs +++ b/osu.Game/Online/API/OAuth.cs @@ -30,7 +30,7 @@ namespace osu.Game.Online.API { var req = new AccessTokenRequestPassword(username, password) { - Url = $@"{endpoint}/oauth/access_token", + Url = $@"{endpoint}/oauth/token", Method = HttpMethod.POST, ClientId = clientId, ClientSecret = clientSecret @@ -55,7 +55,7 @@ namespace osu.Game.Online.API { var req = new AccessTokenRequestRefresh(refresh) { - Url = $@"{endpoint}/oauth/access_token", + Url = $@"{endpoint}/oauth/token", Method = HttpMethod.POST, ClientId = clientId, ClientSecret = clientSecret diff --git a/osu.Game/Online/API/Requests/GetUserRequest.cs b/osu.Game/Online/API/Requests/GetUserRequest.cs new file mode 100644 index 0000000000..1f6da1e1de --- /dev/null +++ b/osu.Game/Online/API/Requests/GetUserRequest.cs @@ -0,0 +1,18 @@ +//Copyright (c) 2007-2016 ppy Pty Ltd . +//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + + +namespace osu.Game.Online.API.Requests +{ + public class GetUserRequest : APIRequest + { + private int? userId; + + public GetUserRequest(int? userId = null) + { + this.userId = userId; + } + + protected override string Target => userId.HasValue ? $@"users/{userId}" : @"me"; + } +} diff --git a/osu.Game/Online/Chat/Display/ChannelDisplay.cs b/osu.Game/Online/Chat/Drawables/ChannelDisplay.cs similarity index 92% rename from osu.Game/Online/Chat/Display/ChannelDisplay.cs rename to osu.Game/Online/Chat/Drawables/ChannelDisplay.cs index 93e235f3f3..041163b19a 100644 --- a/osu.Game/Online/Chat/Display/ChannelDisplay.cs +++ b/osu.Game/Online/Chat/Drawables/ChannelDisplay.cs @@ -4,15 +4,13 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Transformations; using OpenTK; -using osu.Framework; -using osu.Framework.Allocation; -namespace osu.Game.Online.Chat.Display +namespace osu.Game.Online.Chat.Drawables { public class ChannelDisplay : Container { diff --git a/osu.Game/Online/Chat/Display/ChatLine.cs b/osu.Game/Online/Chat/Drawables/ChatLine.cs similarity index 95% rename from osu.Game/Online/Chat/Display/ChatLine.cs rename to osu.Game/Online/Chat/Drawables/ChatLine.cs index b582bc5a6d..454f7beed7 100644 --- a/osu.Game/Online/Chat/Display/ChatLine.cs +++ b/osu.Game/Online/Chat/Drawables/ChatLine.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics.Sprites; using OpenTK; using OpenTK.Graphics; -namespace osu.Game.Online.Chat.Display +namespace osu.Game.Online.Chat.Drawables { public class ChatLine : Container { diff --git a/osu.Game/Online/User.cs b/osu.Game/Online/User.cs index 27de5d8dc2..134167e868 100644 --- a/osu.Game/Online/User.cs +++ b/osu.Game/Online/User.cs @@ -10,6 +10,9 @@ namespace osu.Game.Online [JsonProperty(@"username")] public string Name; + [JsonProperty(@"id")] + public int Id; + [JsonProperty(@"colour")] public string Colour; } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 4f40899500..c6e07c3a79 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -19,7 +19,9 @@ using osu.Framework.Logging; using osu.Game.Graphics.UserInterface.Volume; using osu.Game.Database; using osu.Framework.Allocation; +using osu.Framework.Graphics.Transformations; using osu.Game.Modes; +using osu.Game.Overlays.Toolbar; using osu.Game.Screens; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; @@ -30,7 +32,7 @@ namespace osu.Game { public Toolbar Toolbar; - private ChatConsole chat; + private ChatOverlay chat; private MusicController musicController; @@ -45,7 +47,7 @@ namespace osu.Game string[] args; - public OptionsOverlay Options; + private OptionsOverlay options; public OsuGame(string[] args = null) { @@ -59,7 +61,7 @@ namespace osu.Game host.Size = new Vector2(Config.Get(OsuConfig.Width), Config.Get(OsuConfig.Height)); } - public void ToggleOptions() => Options.ToggleVisibility(); + public void ToggleOptions() => options.ToggleVisibility(); [BackgroundDependencyLoader] private void load() @@ -97,12 +99,7 @@ namespace osu.Game { RelativeSizeAxes = Axes.Both, }, - volume = new VolumeControl - { - VolumeGlobal = Audio.Volume, - VolumeSample = Audio.VolumeSample, - VolumeTrack = Audio.VolumeTrack - }, + volume = new VolumeControl(), overlayContent = new Container{ RelativeSizeAxes = Axes.Both }, new GlobalHotkeys //exists because UserInputManager is at a level below us. { @@ -120,16 +117,18 @@ namespace osu.Game }); //overlay elements - (chat = new ChatConsole(API) { Depth = 0 }).Preload(this, overlayContent.Add); - (Options = new OptionsOverlay { Depth = 1 }).Preload(this, overlayContent.Add); - (musicController = new MusicController() { Depth = 3 }).Preload(this, overlayContent.Add); + (chat = new ChatOverlay { Depth = 0 }).Preload(this, overlayContent.Add); + (options = new OptionsOverlay { Depth = -1 }).Preload(this, overlayContent.Add); + (musicController = new MusicController() { Depth = -3 }).Preload(this, overlayContent.Add); + + Dependencies.Cache(options); + Dependencies.Cache(musicController); + (Toolbar = new Toolbar { - Depth = 2, + Depth = -2, OnHome = delegate { mainMenu?.MakeCurrent(); }, - OnSettings = Options.ToggleVisibility, OnPlayModeChange = delegate (PlayMode m) { PlayMode.Value = m; }, - OnMusicController = musicController.ToggleVisibility }).Preload(this, t => { PlayMode.ValueChanged += delegate { Toolbar.SetGameMode(PlayMode.Value); }; @@ -137,6 +136,19 @@ namespace osu.Game overlayContent.Add(Toolbar); }); + options.StateChanged += delegate + { + switch (options.State) + { + case Visibility.Hidden: + intro.MoveToX(0, OptionsOverlay.TRANSITION_LENGTH, EasingTypes.OutQuint); + break; + case Visibility.Visible: + intro.MoveToX(OptionsOverlay.SIDEBAR_WIDTH / 2, OptionsOverlay.TRANSITION_LENGTH, EasingTypes.OutQuint); + break; + } + }; + Cursor.Alpha = 0; } @@ -154,7 +166,7 @@ namespace osu.Game switch (args.Key) { case Key.O: - Options.ToggleVisibility(); + options.ToggleVisibility(); return true; } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 7b05093dde..5b35b1e672 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -16,12 +16,14 @@ using osu.Game.Database; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Processing; using osu.Game.IPC; +using osu.Game.Online; using osu.Game.Online.API; using osu.Game.Overlays; +using osu.Game.Online.API.Requests; namespace osu.Game { - public class OsuGameBase : BaseGame + public class OsuGameBase : BaseGame, IOnlineComponent { internal OsuConfigManager Config; @@ -41,11 +43,11 @@ namespace osu.Game private void load() { Dependencies.Cache(this); - Dependencies.Cache(new OsuConfigManager(Host.Storage)); + Dependencies.Cache(Config); Dependencies.Cache(new BeatmapDatabase(Host.Storage, Host)); - + //this completely overrides the framework default. will need to change once we make a proper FontStore. - Dependencies.Cache(Fonts = new FontStore { ScaleAdjust = 0.01f }); + Dependencies.Cache(Fonts = new FontStore { ScaleAdjust = 0.01f }, true); Fonts.AddStore(new GlyphStore(Resources, @"Fonts/FontAwesome")); Fonts.AddStore(new GlyphStore(Resources, @"Fonts/osuFont")); @@ -73,6 +75,19 @@ namespace osu.Game Password = Config.Get(OsuConfig.Password), Token = Config.Get(OsuConfig.Token) }); + + API.Register(this); + } + + public void APIStateChanged(APIAccess api, APIState state) + { + switch (state) + { + case APIState.Online: + Config.Set(OsuConfig.Username, Config.Get(OsuConfig.SaveUsername) ? API.Username : string.Empty); + Config.Set(OsuConfig.Password, Config.Get(OsuConfig.SavePassword) ? API.Password : string.Empty); + break; + } } protected override void LoadComplete() @@ -83,7 +98,7 @@ namespace osu.Game { Children = new[] { - Cursor = new OsuCursorContainer { Depth = float.MaxValue } + Cursor = new OsuCursorContainer { Depth = float.MinValue } } }); } diff --git a/osu.Game/Overlays/ChatConsole.cs b/osu.Game/Overlays/ChatOverlay.cs similarity index 82% rename from osu.Game/Overlays/ChatConsole.cs rename to osu.Game/Overlays/ChatOverlay.cs index ee06e29877..22f7a3a78f 100644 --- a/osu.Game/Overlays/ChatConsole.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -1,12 +1,12 @@ //Copyright (c) 2007-2016 ppy Pty Ltd . //Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using OpenTK; using OpenTK.Graphics; -using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -16,11 +16,11 @@ using osu.Framework.Threading; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.Chat; -using osu.Game.Online.Chat.Display; +using osu.Game.Online.Chat.Drawables; namespace osu.Game.Overlays { - public class ChatConsole : OverlayContainer + public class ChatOverlay : OverlayContainer, IOnlineComponent { private ChannelDisplay channelDisplay; @@ -32,10 +32,8 @@ namespace osu.Game.Overlays private APIAccess api; - public ChatConsole(APIAccess api) + public ChatOverlay() { - this.api = api; - RelativeSizeAxes = Axes.X; Size = new Vector2(1, 300); Anchor = Anchor.BottomLeft; @@ -45,7 +43,7 @@ namespace osu.Game.Overlays { new Box { - Depth = float.MinValue, + Depth = float.MaxValue, RelativeSizeAxes = Axes.Both, Colour = new Color4(0.1f, 0.1f, 0.1f, 0.4f), }, @@ -57,50 +55,16 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load() + private void load(APIAccess api) { - initializeChannels(); + this.api = api; + api.Register(this); } private long? lastMessageId; private List careChannels; - private void initializeChannels() - { - careChannels = new List(); - - //if (api.State != APIAccess.APIState.Online) - // return; - - SpriteText loading; - Add(loading = new SpriteText - { - Text = @"Loading available channels...", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - TextSize = 40, - }); - - messageRequest?.Cancel(); - - ListChannelsRequest req = new ListChannelsRequest(); - req.Success += delegate (List channels) - { - Scheduler.Add(delegate - { - loading.FadeOut(100); - addChannel(channels.Find(c => c.Name == @"#osu")); - }); - - //addChannel(channels.Find(c => c.Name == @"#lobby")); - //addChannel(channels.Find(c => c.Name == @"#english")); - - messageRequest = Scheduler.AddDelayed(() => FetchNewMessages(api), 1000, true); - }; - api.Queue(req); - } - private void addChannel(Channel channel) { Add(channelDisplay = new ChannelDisplay(channel)); @@ -145,8 +109,58 @@ namespace osu.Game.Overlays protected override void PopOut() { - MoveToY(DrawSize.Y, transition_length, EasingTypes.InQuint); - FadeOut(transition_length, EasingTypes.InQuint); + MoveToY(DrawSize.Y, transition_length, EasingTypes.InSine); + FadeOut(transition_length, EasingTypes.InSine); + } + + public void APIStateChanged(APIAccess api, APIState state) + { + switch (state) + { + case APIState.Online: + initializeChannels(); + break; + default: + messageRequest?.Cancel(); + break; + } + } + + private void initializeChannels() + { + Clear(); + + careChannels = new List(); + + //if (api.State != APIAccess.APIState.Online) + // return; + + SpriteText loading; + Add(loading = new SpriteText + { + Text = @"Loading available channels...", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + TextSize = 40, + }); + + messageRequest?.Cancel(); + + ListChannelsRequest req = new ListChannelsRequest(); + req.Success += delegate (List channels) + { + Scheduler.Add(delegate + { + loading.FadeOut(100); + addChannel(channels.Find(c => c.Name == @"#osu")); + }); + + //addChannel(channels.Find(c => c.Name == @"#lobby")); + //addChannel(channels.Find(c => c.Name == @"#english")); + + messageRequest = Scheduler.AddDelayed(() => FetchNewMessages(api), 1000, true); + }; + api.Queue(req); } } } diff --git a/osu.Game/Overlays/DragBar.cs b/osu.Game/Overlays/DragBar.cs index ee54d21d74..32a1ed4933 100644 --- a/osu.Game/Overlays/DragBar.cs +++ b/osu.Game/Overlays/DragBar.cs @@ -16,6 +16,18 @@ namespace osu.Game.Overlays public Action SeekRequested; private bool isDragging; + private bool enabled; + public bool IsEnabled + { + get { return enabled; } + set + { + enabled = value; + if (!enabled) + fill.Width = 0; + } + } + public DragBar() { RelativeSizeAxes = Axes.X; @@ -34,13 +46,14 @@ namespace osu.Game.Overlays public void UpdatePosition(float position) { - if (isDragging) return; + if (isDragging || !IsEnabled) return; fill.Width = position; } private void seek(InputState state) { + if (!IsEnabled) return; float seekLocation = state.Mouse.Position.X / DrawWidth; SeekRequested?.Invoke(seekLocation); fill.Width = seekLocation; diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 27080c8db4..c69624bfda 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -240,6 +240,7 @@ namespace osu.Game.Overlays private void workingChanged(object sender = null, EventArgs e = null) { + progress.IsEnabled = (beatmapSource.Value != null); if (beatmapSource.Value == current) return; bool audioEquals = current?.BeatmapInfo.AudioEquals(beatmapSource.Value.BeatmapInfo) ?? false; current = beatmapSource.Value; @@ -386,7 +387,7 @@ namespace osu.Game.Overlays this.beatmap = beatmap; CacheDrawnFrameBuffer = true; RelativeSizeAxes = Axes.Both; - Depth = float.MinValue; + Depth = float.MaxValue; Children = new Drawable[] { diff --git a/osu.Game/Overlays/Options/General/LoginOptions.cs b/osu.Game/Overlays/Options/General/LoginOptions.cs index 2dc8bb5976..94eb6b1c01 100644 --- a/osu.Game/Overlays/Options/General/LoginOptions.cs +++ b/osu.Game/Overlays/Options/General/LoginOptions.cs @@ -1,4 +1,5 @@ -using OpenTK; +using System; +using OpenTK; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -10,38 +11,72 @@ using osu.Game.Online.API; namespace osu.Game.Overlays.Options.General { - public class LoginOptions : OptionsSubsection + public class LoginOptions : OptionsSubsection, IOnlineComponent { private Container loginForm; - protected override string Header => "Sign In"; - public LoginOptions() - { - Children = new[] - { - loginForm = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new[] { new LoadingAnimation() } - } - }; - } + private Action performLogout; + protected override string Header => "Sign In"; [BackgroundDependencyLoader(permitNulls: true)] private void load(APIAccess api) + { + api?.Register(this); + } + + public void APIStateChanged(APIAccess api, APIState state) { - if (api == null) - return; - loginForm.Children = new Drawable[] + switch (state) { - new LoginForm(api) - }; + case APIState.Offline: + Children = new Drawable[] + { + new LoginForm() + }; + break; + case APIState.Failing: + Children = new Drawable[] + { + new SpriteText + { + Text = "Connection failing :(", + }, + }; + break; + case APIState.Connecting: + Children = new Drawable[] + { + new SpriteText + { + Text = "Connecting...", + }, + }; + break; + case APIState.Online: + Children = new Drawable[] + { + new SpriteText + { + Text = $"Connected as {api.Username}!", + }, + new OsuButton + { + RelativeSizeAxes = Axes.X, + Text = "Sign out", + Action = api.Logout + } + }; + break; + } } class LoginForm : FlowContainer { - public LoginForm(APIAccess api) + private TextBox username; + private TextBox password; + private APIAccess api; + + public LoginForm() { Direction = FlowDirection.VerticalOnly; AutoSizeAxes = Axes.Y; @@ -51,16 +86,29 @@ namespace osu.Game.Overlays.Options.General Children = new Drawable[] { new SpriteText { Text = "Username" }, - new TextBox { Height = 20, RelativeSizeAxes = Axes.X, Text = api?.Username ?? string.Empty }, + username = new TextBox { Height = 20, RelativeSizeAxes = Axes.X, Text = api?.Username ?? string.Empty }, new SpriteText { Text = "Password" }, - new TextBox { Height = 20, RelativeSizeAxes = Axes.X }, + password = new PasswordTextBox { Height = 20, RelativeSizeAxes = Axes.X }, new OsuButton { RelativeSizeAxes = Axes.X, Text = "Log in", + Action = performLogin } }; } + + private void performLogin() + { + if (!string.IsNullOrEmpty(username.Text) && !string.IsNullOrEmpty(password.Text)) + api.Login(username.Text, password.Text); + } + + [BackgroundDependencyLoader(permitNulls: true)] + private void load(APIAccess api) + { + this.api = api; + } } } } diff --git a/osu.Game/Overlays/OptionsOverlay.cs b/osu.Game/Overlays/OptionsOverlay.cs index e3039ad781..33677799f1 100644 --- a/osu.Game/Overlays/OptionsOverlay.cs +++ b/osu.Game/Overlays/OptionsOverlay.cs @@ -30,8 +30,12 @@ namespace osu.Game.Overlays { internal const float CONTENT_MARGINS = 10; + public const float TRANSITION_LENGTH = 600; + + public const float SIDEBAR_WIDTH = OptionsSidebar.default_width; + private const float width = 400; - private const float sidebar_width = OptionsSidebar.default_width; + private const float sidebar_padding = 10; private ScrollContainer scrollContainer; @@ -71,7 +75,7 @@ namespace osu.Game.Overlays ScrollDraggerVisible = false, RelativeSizeAxes = Axes.Y, Width = width, - Margin = new MarginPadding { Left = sidebar_width }, + Margin = new MarginPadding { Left = SIDEBAR_WIDTH }, Children = new[] { new FlowContainer @@ -108,7 +112,7 @@ namespace osu.Game.Overlays }, sidebar = new OptionsSidebar { - Width = sidebar_width, + Width = SIDEBAR_WIDTH, Children = sidebarButtons = sections.Select(section => new SidebarButton { @@ -175,16 +179,16 @@ namespace osu.Game.Overlays protected override void PopIn() { - scrollContainer.MoveToX(0, 600, EasingTypes.OutQuint); - sidebar.MoveToX(0, 800, EasingTypes.OutQuint); - FadeTo(1, 300); + scrollContainer.MoveToX(0, TRANSITION_LENGTH, EasingTypes.OutQuint); + sidebar.MoveToX(0, TRANSITION_LENGTH, EasingTypes.OutQuint); + FadeTo(1, TRANSITION_LENGTH / 2); } protected override void PopOut() { - scrollContainer.MoveToX(-width, 600, EasingTypes.OutQuint); - sidebar.MoveToX(-sidebar_width, 600, EasingTypes.OutQuint); - FadeTo(0, 300); + scrollContainer.MoveToX(-width, TRANSITION_LENGTH, EasingTypes.OutQuint); + sidebar.MoveToX(-SIDEBAR_WIDTH, TRANSITION_LENGTH, EasingTypes.OutQuint); + FadeTo(0, TRANSITION_LENGTH / 2); } } } diff --git a/osu.Game/Overlays/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs similarity index 66% rename from osu.Game/Overlays/Toolbar.cs rename to osu.Game/Overlays/Toolbar/Toolbar.cs index 190615de29..df6168b6c5 100644 --- a/osu.Game/Overlays/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -2,37 +2,37 @@ //Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Transformations; +using osu.Framework.Input; using osu.Game.Configuration; using osu.Game.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Allocation; -using osu.Framework.Graphics.Colour; using osu.Game.Modes; -using osu.Game.Screens.Play; -using osu.Framework.Input; +using osu.Game.Online.API; +using OpenTK; +using OpenTK.Graphics; -namespace osu.Game.Overlays +namespace osu.Game.Overlays.Toolbar { public class Toolbar : OverlayContainer { private const float height = 50; - public Action OnSettings; public Action OnHome; public Action OnPlayModeChange; - public Action OnMusicController; private ToolbarModeSelector modeSelector; - private ToolbarButton userButton; + private Box solidBackground; private Box gradientBackground; - private const int transition_time = 200; + private const int transition_time = 250; + + private const float alpha_hovering = 0.8f; + private const float alpha_normal = 0.6f; + protected override void PopIn() { @@ -48,23 +48,26 @@ namespace osu.Game.Overlays protected override bool OnHover(InputState state) { - gradientBackground.FadeIn(200); + solidBackground.FadeTo(alpha_hovering, transition_time, EasingTypes.OutQuint); + gradientBackground.FadeIn(transition_time, EasingTypes.OutQuint); return true; } protected override void OnHoverLost(InputState state) { - gradientBackground.FadeOut(200); + solidBackground.FadeTo(alpha_normal, transition_time, EasingTypes.OutQuint); + gradientBackground.FadeOut(transition_time, EasingTypes.OutQuint); } public Toolbar() { Children = new Drawable[] { - new Box + solidBackground = new Box { RelativeSizeAxes = Axes.Both, - Colour = new Color4(0.1f, 0.1f, 0.1f, 0.6f) + Colour = new Color4(0.1f, 0.1f, 0.1f, 1), + Alpha = alpha_normal, }, gradientBackground = new Box { @@ -81,18 +84,9 @@ namespace osu.Game.Overlays AutoSizeAxes = Axes.X, Children = new Drawable[] { - new ToolbarButton + new ToolbarSettingsButton(), + new ToolbarHomeButton() { - Icon = FontAwesome.fa_gear, - TooltipMain = "Settings", - TooltipSub = "Change your settings", - Action = () => OnSettings?.Invoke() - }, - new ToolbarButton - { - Icon = FontAwesome.fa_home, - TooltipMain = "Home", - TooltipSub = "Return to the main menu", Action = () => OnHome?.Invoke() }, modeSelector = new ToolbarModeSelector @@ -110,19 +104,12 @@ namespace osu.Game.Overlays AutoSizeAxes = Axes.X, Children = new [] { - new ToolbarButton - { - Icon = FontAwesome.fa_music, - Action = () => OnMusicController?.Invoke() - }, + new ToolbarMusicButton(), new ToolbarButton { Icon = FontAwesome.fa_search }, - userButton = new ToolbarButton - { - Icon = FontAwesome.fa_user, - }, + new ToolbarUserButton(), new ToolbarButton { Icon = FontAwesome.fa_bars @@ -135,12 +122,6 @@ namespace osu.Game.Overlays Size = new Vector2(1, height); } - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - userButton.Text = config.Get(OsuConfig.Username); - } - public void SetGameMode(PlayMode mode) => modeSelector.SetGameMode(mode); } } diff --git a/osu.Game/Overlays/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs similarity index 75% rename from osu.Game/Overlays/ToolbarButton.cs rename to osu.Game/Overlays/Toolbar/ToolbarButton.cs index 1d8bf51795..70462fa5fe 100644 --- a/osu.Game/Overlays/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -4,20 +4,19 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Transformations; using osu.Framework.Input; using osu.Game.Graphics; +using osu.Game.Graphics.Backgrounds; using OpenTK; using OpenTK.Graphics; -using osu.Framework; -using osu.Framework.Graphics.Primitives; -namespace osu.Game.Overlays +namespace osu.Game.Overlays.Toolbar { public class ToolbarButton : Container { - public const float WIDTH = 60; - public FontAwesome Icon { get { return DrawableIcon.Icon; } @@ -58,6 +57,7 @@ namespace osu.Game.Overlays private FlowContainer tooltipContainer; private SpriteText tooltip1; private SpriteText tooltip2; + protected FlowContainer Flow; public ToolbarButton() { @@ -66,16 +66,17 @@ namespace osu.Game.Overlays HoverBackground = new Box { RelativeSizeAxes = Axes.Both, + Colour = new Color4(80, 80, 80, 180), BlendingMode = BlendingMode.Additive, - Colour = new Color4(60, 60, 60, 255), Alpha = 0, }, - new FlowContainer + Flow = new FlowContainer { Direction = FlowDirection.HorizontalOnly, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Padding = new MarginPadding { Left = 5, Right = 5 }, + Padding = new MarginPadding { Left = 15, Right = 15 }, + Spacing = new Vector2(5), RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Children = new Drawable[] @@ -87,7 +88,6 @@ namespace osu.Game.Overlays }, DrawableText = new SpriteText { - Margin = new MarginPadding { Left = 5 }, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, @@ -118,7 +118,6 @@ namespace osu.Game.Overlays }; RelativeSizeAxes = Axes.Y; - Size = new Vector2(WIDTH, 1); } protected override void Update() @@ -126,7 +125,7 @@ namespace osu.Game.Overlays base.Update(); //todo: find a way to avoid using this (autosize needs to be able to ignore certain drawables.. in this case the tooltip) - Size = new Vector2(WIDTH + (DrawableText.IsVisible ? DrawableText.DrawSize.X : 0), 1); + Size = new Vector2(Flow.DrawSize.X, 1); } protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; @@ -134,21 +133,44 @@ namespace osu.Game.Overlays protected override bool OnClick(InputState state) { Action?.Invoke(); - HoverBackground.FlashColour(Color4.White, 400); + HoverBackground.FlashColour(new Color4(255, 255, 255, 100), 500, EasingTypes.OutQuint); return true; } protected override bool OnHover(InputState state) { - HoverBackground.FadeTo(0.4f, 200); + HoverBackground.FadeIn(200); tooltipContainer.FadeIn(100); return false; } protected override void OnHoverLost(InputState state) { - HoverBackground.FadeTo(0, 200); + HoverBackground.FadeOut(200); tooltipContainer.FadeOut(100); } } + + public class OpaqueBackground : Container + { + public OpaqueBackground() + { + RelativeSizeAxes = Axes.Both; + Masking = true; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = new Color4(30, 30, 30, 255) + }, + new Triangles + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.05f, + }, + }; + } + } } \ No newline at end of file diff --git a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs new file mode 100644 index 0000000000..259abc0739 --- /dev/null +++ b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs @@ -0,0 +1,19 @@ +//Copyright (c) 2007-2016 ppy Pty Ltd . +//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Screens.Menu; + +namespace osu.Game.Overlays.Toolbar +{ + class ToolbarHomeButton : ToolbarButton + { + public ToolbarHomeButton() + { + Icon = FontAwesome.fa_home; + TooltipMain = "Home"; + TooltipSub = "Return to the main menu"; + } + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/ToolbarModeButton.cs b/osu.Game/Overlays/Toolbar/ToolbarModeButton.cs similarity index 62% rename from osu.Game/Overlays/ToolbarModeButton.cs rename to osu.Game/Overlays/Toolbar/ToolbarModeButton.cs index c10b8514db..60ce228164 100644 --- a/osu.Game/Overlays/ToolbarModeButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarModeButton.cs @@ -2,14 +2,12 @@ //Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Extensions; +using osu.Framework.Graphics.Containers; using osu.Game.Graphics; -using OpenTK.Graphics; -using osu.Framework; -using osu.Framework.Allocation; using osu.Game.Modes; -using osu.Game.Screens.Play; +using OpenTK.Graphics; -namespace osu.Game.Overlays +namespace osu.Game.Overlays.Toolbar { public class ToolbarModeButton : ToolbarButton { @@ -30,7 +28,23 @@ namespace osu.Game.Overlays { set { - //Background.Colour = value ? new Color4(100, 100, 100, 255) : new Color4(20, 20, 20, 255); + if (value) + { + DrawableIcon.Colour = Color4.White; + DrawableIcon.Masking = true; + DrawableIcon.EdgeEffect = new EdgeEffect + { + Type = EdgeEffectType.Glow, + Colour = new Color4(255, 194, 224, 100), + Radius = 15, + Roundness = 15, + }; + } + else + { + DrawableIcon.Masking = false; + DrawableIcon.Colour = new Color4(255, 194, 224, 255); + } } } diff --git a/osu.Game/Overlays/ToolbarModeSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs similarity index 67% rename from osu.Game/Overlays/ToolbarModeSelector.cs rename to osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs index 9b6d8a7fe1..bc60123c02 100644 --- a/osu.Game/Overlays/ToolbarModeSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs @@ -3,26 +3,25 @@ using System; using System.Linq; +using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Transformations; +using osu.Game.Graphics.Backgrounds; +using osu.Game.Modes; using OpenTK; using OpenTK.Graphics; -using osu.Framework; -using osu.Framework.Caching; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Allocation; -using osu.Game.Modes; -using osu.Game.Screens.Play; -namespace osu.Game.Overlays +namespace osu.Game.Overlays.Toolbar { class ToolbarModeSelector : Container { const float padding = 10; private FlowContainer modeButtons; - private Box modeButtonLine; + private Drawable modeButtonLine; private ToolbarModeButton activeButton; public Action OnPlayModeChange; @@ -33,11 +32,7 @@ namespace osu.Game.Overlays Children = new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = new Color4(20, 20, 20, 255) - }, + new OpaqueBackground(), modeButtons = new FlowContainer { RelativeSizeAxes = Axes.Y, @@ -45,14 +40,29 @@ namespace osu.Game.Overlays Direction = FlowDirection.HorizontalOnly, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, + Padding = new MarginPadding { Left = 10, Right = 10 }, }, - modeButtonLine = new Box + modeButtonLine = new Container { RelativeSizeAxes = Axes.X, Size = new Vector2(0.3f, 3), Anchor = Anchor.BottomLeft, - Origin = Anchor.TopCentre, - Colour = Color4.White + Origin = Anchor.TopLeft, + Masking = true, + EdgeEffect = new EdgeEffect + { + Type = EdgeEffectType.Glow, + Colour = new Color4(255, 194, 224, 100), + Radius = 15, + Roundness = 15, + }, + Children = new [] + { + new Box + { + RelativeSizeAxes = Axes.Both, + } + } } }; @@ -72,9 +82,13 @@ namespace osu.Game.Overlays } }); } + } - // We need to set the size within LoadComplete, because - Size = new Vector2(amountButtons * ToolbarButton.WIDTH + padding * 2, 1); + protected override void Update() + { + base.Update(); + + Size = new Vector2(modeButtons.DrawSize.X, 1); } public void SetGameMode(PlayMode mode) @@ -97,7 +111,7 @@ namespace osu.Game.Overlays base.UpdateLayout(); if (!activeMode.EnsureValid()) - activeMode.Refresh(() => modeButtonLine.MoveToX(activeButton.DrawPosition.X + activeButton.DrawSize.X / 2 + padding, 200, EasingTypes.OutQuint)); + activeMode.Refresh(() => modeButtonLine.MoveToX(activeButton.DrawPosition.X, 200, EasingTypes.OutQuint)); } } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs new file mode 100644 index 0000000000..67ec2aed26 --- /dev/null +++ b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs @@ -0,0 +1,23 @@ +//Copyright (c) 2007-2016 ppy Pty Ltd . +//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; + +namespace osu.Game.Overlays.Toolbar +{ + class ToolbarMusicButton : ToolbarOverlayToggleButton + { + public ToolbarMusicButton() + { + Icon = FontAwesome.fa_music; + } + + [BackgroundDependencyLoader] + private void load(MusicController music) + { + StateContainer = music; + Action = music.ToggleVisibility; + } + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs new file mode 100644 index 0000000000..b72a358456 --- /dev/null +++ b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs @@ -0,0 +1,71 @@ +//Copyright (c) 2007-2016 ppy Pty Ltd . +//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using osu.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Configuration; +using osu.Game.Online.API; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Overlays.Toolbar +{ + class ToolbarOverlayToggleButton : ToolbarButton + { + private Box StateBackground; + + private OverlayContainer stateContainer; + + public OverlayContainer StateContainer + { + get { return stateContainer; } + set + { + stateContainer = value; + stateContainer.StateChanged += stateChanged; + } + } + + public ToolbarOverlayToggleButton() + { + Add(StateBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = new Color4(150, 150, 150, 180), + BlendingMode = BlendingMode.Additive, + Depth = 2, + Alpha = 0, + }); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + if (stateContainer != null) + stateContainer.StateChanged -= stateChanged; + } + + private void stateChanged(OverlayContainer c, Visibility state) + { + switch (state) + { + case Visibility.Hidden: + StateBackground.FadeOut(200); + break; + case Visibility.Visible: + StateBackground.FadeIn(200); + break; + } + } + } +} diff --git a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs new file mode 100644 index 0000000000..4bc3964716 --- /dev/null +++ b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs @@ -0,0 +1,25 @@ +//Copyright (c) 2007-2016 ppy Pty Ltd . +//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; + +namespace osu.Game.Overlays.Toolbar +{ + class ToolbarSettingsButton : ToolbarOverlayToggleButton + { + public ToolbarSettingsButton() + { + Icon = FontAwesome.fa_gear; + TooltipMain = "Settings"; + TooltipSub = "Change your settings"; + } + + [BackgroundDependencyLoader] + private void load(OptionsOverlay options) + { + StateContainer = options; + Action = options.ToggleVisibility; + } + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs new file mode 100644 index 0000000000..3a91a007ff --- /dev/null +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -0,0 +1,140 @@ +//Copyright (c) 2007-2016 ppy Pty Ltd . +//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Configuration; +using osu.Game.Online.API; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Overlays.Toolbar +{ + class ToolbarUserButton : ToolbarButton, IOnlineComponent + { + private Avatar avatar; + + public ToolbarUserButton() + { + DrawableText.Font = @"Exo2.0-MediumItalic"; + + Add(new OpaqueBackground { Depth = 1 }); + + Flow.Add(avatar = new Avatar()); + } + + [BackgroundDependencyLoader] + private void load(APIAccess api, OsuConfigManager config) + { + api.Register(this); + } + + public void APIStateChanged(APIAccess api, APIState state) + { + switch (state) + { + default: + Text = @"Guest"; + avatar.UserId = 1; + break; + case APIState.Online: + Text = api.Username; + avatar.UserId = api.LocalUser.Value.Id; + break; + } + } + + public class Avatar : Container + { + public Drawable Sprite; + + private int userId; + private OsuGame game; + private Texture guestTexture; + + public Avatar() + { + Size = new Vector2(32); + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + + CornerRadius = Size.X / 8; + + EdgeEffect = new EdgeEffect + { + Type = EdgeEffectType.Shadow, + Radius = 4, + Colour = new Color4(0, 0, 0, 0.1f), + }; + + Masking = true; + } + + [BackgroundDependencyLoader] + private void load(OsuGame game, TextureStore textures) + { + this.game = game; + + guestTexture = textures.Get(@"Online/avatar-guest@2x"); + } + + public int UserId + { + get { return userId; } + set + { + if (userId == value) + return; + + userId = value; + + Sprite newSprite; + if (userId > 1) + newSprite = new OnlineSprite($@"https://a.ppy.sh/{userId}"); + else + newSprite = new Sprite { Texture = guestTexture }; + + newSprite.FillMode = FillMode.Fit; + + newSprite.Preload(game, s => + { + Sprite?.FadeOut(); + Sprite?.Expire(); + Sprite = s; + + Add(s); + + s.FadeInFromZero(200); + }); + } + } + + public class OnlineSprite : Sprite + { + private readonly string url; + private readonly int userId; + + public OnlineSprite(string url) + { + Debug.Assert(url != null); + this.url = url; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + Texture = textures.Get(url); + } + } + } + } +} diff --git a/osu.Game/Screens/Backgrounds/BackgroundModeBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundModeBeatmap.cs index 1a4ce2eec2..1c25fcfb0e 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundModeBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundModeBeatmap.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Backgrounds float newDepth = 0; if (background != null) { - newDepth = background.Depth - 1; + newDepth = background.Depth + 1; background.Flush(); background.FadeOut(250); background.Expire(); diff --git a/osu.Game/Screens/GameModeWhiteBox.cs b/osu.Game/Screens/GameModeWhiteBox.cs index ac6cc5ece3..b05ac614c6 100644 --- a/osu.Game/Screens/GameModeWhiteBox.cs +++ b/osu.Game/Screens/GameModeWhiteBox.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Transformations; using osu.Framework.Graphics.UserInterface; using osu.Game.Screens.Backgrounds; +using osu.Game.Graphics.UserInterface; using OpenTK; using OpenTK.Graphics; @@ -17,7 +18,7 @@ namespace osu.Game.Screens { public class GameModeWhiteBox : OsuGameMode { - private Button popButton; + private BackButton popButton; const int transition_time = 1000; @@ -113,14 +114,10 @@ namespace osu.Game.Screens }, } }, - popButton = new Button + popButton = new BackButton { - Text = @"Back", - RelativeSizeAxes = Axes.X, - Size = new Vector2(0.1f, 40), Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Colour = new Color4(235, 51, 153, 255), Alpha = 0, Action = delegate { Exit(); diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs index fa5dbb3b99..f0de6e7f74 100644 --- a/osu.Game/Screens/Menu/Button.cs +++ b/osu.Game/Screens/Menu/Button.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Menu public class Button : Container, IStateful { private Container iconText; - private Box box; + private Container box; private Color4 colour; private TextAwesome icon; private string internalName; @@ -51,15 +51,31 @@ namespace osu.Game.Screens.Menu Children = new Drawable[] { - box = new Box + box = new Container { + Masking = true, + EdgeEffect = new EdgeEffect + { + Type = EdgeEffectType.Shadow, + Colour = new Color4(0, 0, 0, 0.2f), + Roundness = 5, + Radius = 8, + }, Anchor = Anchor.Centre, Origin = Anchor.Centre, Colour = colour, Scale = new Vector2(0, 1), Size = boxSize, Shear = new Vector2(ButtonSystem.wedge_width / boxSize.Y, 0), - EdgeSmoothness = new Vector2(2, 0), + + Children = new Drawable[] + { + new Box + { + EdgeSmoothness = new Vector2(2, 0), + RelativeSizeAxes = Axes.Both, + }, + } }, iconText = new Container { @@ -71,6 +87,7 @@ namespace osu.Game.Screens.Menu { icon = new TextAwesome { + Shadow = true, Anchor = Anchor.Centre, TextSize = 30, Position = new Vector2(0, 0), @@ -78,6 +95,7 @@ namespace osu.Game.Screens.Menu }, new SpriteText { + Shadow = true, Direction = FlowDirection.HorizontalOnly, Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Menu/FlowContainerWithOrigin.cs b/osu.Game/Screens/Menu/FlowContainerWithOrigin.cs index fc72365812..cb6bc26b8a 100644 --- a/osu.Game/Screens/Menu/FlowContainerWithOrigin.cs +++ b/osu.Game/Screens/Menu/FlowContainerWithOrigin.cs @@ -1,6 +1,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using OpenTK; +using System.Collections.Generic; +using System.Linq; namespace osu.Game.Screens.Menu { @@ -15,6 +17,10 @@ namespace osu.Game.Screens.Menu /// public Drawable CentreTarget; + protected override IComparer DepthComparer => new ReverseCreationOrderDepthComparer(); + + protected override IEnumerable SortedChildren => base.SortedChildren.Reverse(); + public override Anchor Origin => Anchor.Custom; public override Vector2 OriginPosition diff --git a/osu.Game/Screens/Menu/Intro.cs b/osu.Game/Screens/Menu/Intro.cs index 4474ee8024..ed7c0fa7e9 100644 --- a/osu.Game/Screens/Menu/Intro.cs +++ b/osu.Game/Screens/Menu/Intro.cs @@ -8,6 +8,7 @@ using osu.Framework.Audio.Track; using osu.Framework.GameModes; using osu.Framework.Graphics; using osu.Framework.Graphics.Transformations; +using osu.Game.Graphics.Containers; using osu.Game.Screens.Backgrounds; using OpenTK.Graphics; @@ -34,13 +35,21 @@ namespace osu.Game.Screens.Menu { Children = new Drawable[] { - logo = new OsuLogo() + new ParallaxContainer { - Alpha = 0, - BlendingMode = BlendingMode.Additive, - Interactive = false, - Colour = Color4.DarkGray, - Ripple = false + ParallaxAmount = 0.01f, + Children = new Drawable[] + { + logo = new OsuLogo + { + Alpha = 0, + Triangles = false, + BlendingMode = BlendingMode.Additive, + Interactive = false, + Colour = Color4.DarkGray, + Ripple = false + } + } } }; } diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 6047339b25..323b23b920 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -2,14 +2,19 @@ //Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Transformations; using osu.Framework.Input; +using osu.Framework.MathUtils; +using osu.Game.Graphics.Backgrounds; using OpenTK; +using OpenTK.Graphics; namespace osu.Game.Screens.Menu { @@ -21,16 +26,27 @@ namespace osu.Game.Screens.Menu private Sprite logo; private CircularContainer logoContainer; private Container logoBounceContainer; + private Container logoHoverContainer; private MenuVisualisation vis; + private Container colourAndTriangles; + public Action Action; - public float SizeForFlow => logo == null ? 0 : logo.DrawSize.X * logo.Scale.X * logoBounceContainer.Scale.X * 0.8f; + public float SizeForFlow => logo == null ? 0 : logo.DrawSize.X * logo.Scale.X * logoBounceContainer.Scale.X * logoHoverContainer.Scale.X * 0.78f; private Sprite ripple; private Container rippleContainer; + public bool Triangles + { + set + { + colourAndTriangles.Alpha = value ? 1 : 0; + } + } + public override bool Contains(Vector2 screenSpacePos) { return logoContainer.Contains(screenSpacePos); @@ -61,41 +77,77 @@ namespace osu.Game.Screens.Menu AutoSizeAxes = Axes.Both, Children = new Drawable[] { - logoContainer = new CircularContainer + logoHoverContainer = new Container { AutoSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Children = new[] - { - logo = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - }, - }, - rippleContainer = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, Children = new Drawable[] { - ripple = new Sprite() + new BufferedContainer + { + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + logoContainer = new CircularContainer + { + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(0.8f), + Children = new Drawable[] + { + colourAndTriangles = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = new Color4(233, 103, 161, 255), + }, + new OsuLogoTriangles + { + RelativeSizeAxes = Axes.Both, + }, + } + }, + + }, + }, + logo = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.5f), + }, + } + }, + rippleContainer = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, + Children = new Drawable[] + { + ripple = new Sprite() + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + BlendingMode = BlendingMode.Additive, + Scale = new Vector2(0.5f), + Alpha = 0.15f + } + } + }, + vis = new MenuVisualisation + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = logo.Size, BlendingMode = BlendingMode.Additive, - Alpha = 0.05f + Alpha = 0.2f, } } - }, - vis = new MenuVisualisation - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = logo.Size, - BlendingMode = BlendingMode.Additive, - Alpha = 0.2f, } } } @@ -105,15 +157,15 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load(TextureStore textures) { - logo.Texture = textures.Get(@"Menu/logo"); - ripple.Texture = textures.Get(@"Menu/logo"); + logo.Texture = textures.Get(@"Menu/logo@2x"); + ripple.Texture = textures.Get(@"Menu/logo@2x"); } protected override void LoadComplete() { base.LoadComplete(); - ripple.ScaleTo(1.1f, 500); + ripple.ScaleTo(ripple.Scale * 1.1f, 500); ripple.FadeOut(500); ripple.Loop(300); } @@ -122,14 +174,14 @@ namespace osu.Game.Screens.Menu { if (!Interactive) return false; - logoBounceContainer.ScaleTo(1.1f, 1000, EasingTypes.Out); + logoBounceContainer.ScaleTo(0.9f, 1000, EasingTypes.Out); return true; } protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) { - logoBounceContainer.ScaleTo(1.2f, 500, EasingTypes.OutElastic); + logoBounceContainer.ScaleTo(1f, 500, EasingTypes.OutElastic); return true; } @@ -144,13 +196,39 @@ namespace osu.Game.Screens.Menu protected override bool OnHover(InputState state) { if (!Interactive) return false; - logoBounceContainer.ScaleTo(1.2f, 500, EasingTypes.OutElastic); + logoHoverContainer.ScaleTo(1.2f, 500, EasingTypes.OutElastic); return true; } protected override void OnHoverLost(InputState state) { - logoBounceContainer.ScaleTo(1, 500, EasingTypes.OutElastic); + logoHoverContainer.ScaleTo(1, 500, EasingTypes.OutElastic); + } + + class OsuLogoTriangles : Triangles + { + public OsuLogoTriangles() + { + TriangleScale = 4; + Alpha = 1; + } + + protected override Triangle CreateTriangle() + { + var triangle = base.CreateTriangle(); + triangle.Alpha = 1; + triangle.Colour = getTriangleShade(); + return triangle; + } + + private Color4 getTriangleShade() + { + float val = RNG.NextSingle(); + return Interpolation.ValueAt(val, + new Color4(222, 91, 149, 255), + new Color4(255, 125, 183, 255), + 0, 1); + } } } } \ No newline at end of file diff --git a/osu.Game/Screens/OsuGameMode.cs b/osu.Game/Screens/OsuGameMode.cs index 51b8c7d7cf..b5e45f2097 100644 --- a/osu.Game/Screens/OsuGameMode.cs +++ b/osu.Game/Screens/OsuGameMode.cs @@ -111,7 +111,7 @@ namespace osu.Game.Screens { AddInternal(new ParallaxContainer { - Depth = float.MinValue, + Depth = float.MaxValue, Children = new[] { Background = bg diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 14df8d37b4..2f35f1b5d5 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1,6 +1,7 @@ //Copyright (c) 2007-2016 ppy Pty Ltd . //Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; @@ -18,6 +19,8 @@ using OpenTK.Input; using MouseState = osu.Framework.Input.MouseState; using OpenTK; using osu.Framework.GameModes; +using osu.Game.Modes.UI; +using osu.Game.Screens.Ranking; namespace osu.Game.Screens.Play { @@ -37,6 +40,9 @@ namespace osu.Game.Screens.Play private Ruleset ruleset; + private ScoreProcessor scoreProcessor; + private HitRenderer hitRenderer; + [BackgroundDependencyLoader] private void load(AudioManager audio, BeatmapDatabase beatmaps, OsuGameBase game) { @@ -83,13 +89,15 @@ namespace osu.Game.Screens.Play ruleset = Ruleset.GetRuleset(usablePlayMode); var scoreOverlay = ruleset.CreateScoreOverlay(); - var hitRenderer = ruleset.CreateHitRendererWith(beatmap.HitObjects); + scoreOverlay.BindProcessor(scoreProcessor = ruleset.CreateScoreProcessor()); - hitRenderer.OnHit += delegate (HitObject h) { scoreOverlay.OnHit(h); }; - hitRenderer.OnMiss += delegate (HitObject h) { scoreOverlay.OnMiss(h); }; + hitRenderer = ruleset.CreateHitRendererWith(beatmap.HitObjects); + + hitRenderer.OnJudgement += scoreProcessor.AddJudgement; + hitRenderer.OnAllJudged += hitRenderer_OnAllJudged; if (Autoplay) - hitRenderer.Schedule(() => hitRenderer.DrawableObjects.ForEach(h => h.State = ArmedState.Armed)); + hitRenderer.Schedule(() => hitRenderer.DrawableObjects.ForEach(h => h.State = ArmedState.Hit)); Children = new Drawable[] { @@ -105,6 +113,18 @@ namespace osu.Game.Screens.Play }; } + private void hitRenderer_OnAllJudged() + { + Delay(1000); + Schedule(delegate + { + Push(new Results + { + Score = scoreProcessor.GetScore() + }); + }); + } + protected override void OnEntering(GameMode last) { base.OnEntering(last); @@ -125,26 +145,28 @@ namespace osu.Game.Screens.Play { } + bool leftViaKeyboard; + bool rightViaKeyboard; + protected override void TransformState(InputState state) { base.TransformState(state); MouseState mouse = (MouseState)state.Mouse; - foreach (Key k in state.Keyboard.Keys) + if (state.Keyboard != null) { - switch (k) - { - case Key.Z: - mouse.ButtonStates.Find(s => s.Button == MouseButton.Left).State = true; - break; - case Key.X: - mouse.ButtonStates.Find(s => s.Button == MouseButton.Right).State = true; - break; - } + leftViaKeyboard = state.Keyboard.Keys.Contains(Key.Z); + rightViaKeyboard = state.Keyboard.Keys.Contains(Key.X); + } + + if (state.Mouse != null) + { + if (leftViaKeyboard) mouse.ButtonStates.Find(s => s.Button == MouseButton.Left).State = true; + if (rightViaKeyboard) mouse.ButtonStates.Find(s => s.Button == MouseButton.Right).State = true; } } } } -} +} \ No newline at end of file diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index b1d55ab54a..a4219daf54 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -2,19 +2,29 @@ //Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.GameModes; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Transformations; +using osu.Game.Modes; using osu.Game.Screens.Backgrounds; +using OpenTK; using OpenTK.Graphics; namespace osu.Game.Screens.Ranking { - class Results : GameModeWhiteBox + class Results : OsuGameMode { - protected override BackgroundMode CreateBackground() => new BackgroundModeCustom(@"Backgrounds/bg4"); + protected override BackgroundMode CreateBackground() => new BackgroundModeBeatmap(Beatmap); + + private static readonly Vector2 BACKGROUND_BLUR = new Vector2(20); + + ScoreDisplay scoreDisplay; protected override void OnEntering(GameMode last) { base.OnEntering(last); - Background.Schedule(() => Background.FadeColour(Color4.DarkGray, 500)); + Background.Schedule(() => (Background as BackgroundModeBeatmap)?.BlurTo(BACKGROUND_BLUR, 1000)); } protected override bool OnExiting(GameMode next) @@ -22,5 +32,63 @@ namespace osu.Game.Screens.Ranking Background.Schedule(() => Background.FadeColour(Color4.White, 500)); return base.OnExiting(next); } + + public Score Score + { + set + { + scoreDisplay?.FadeOut(500); + scoreDisplay?.Expire(); + + scoreDisplay = new ScoreDisplay(value) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + + Add(scoreDisplay); + + scoreDisplay.FadeIn(500); + scoreDisplay.ScaleTo(0.1f); + scoreDisplay.ScaleTo(1, 1000, EasingTypes.OutElastic); + scoreDisplay.RotateTo(360 * 5, 1000, EasingTypes.OutElastic); + + } + } + } + + class ScoreDisplay : Container + { + public ScoreDisplay(Score s) + { + AutoSizeAxes = Axes.Both; + + Children = new Drawable[] + { + new FlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FlowDirection.VerticalOnly, + Children = new Drawable[] + { + new SpriteText + { + TextSize = 40, + Text = $@"Accuracy: {s.Accuracy:#0.00%}", + }, + new SpriteText + { + TextSize = 40, + Text = $@"Score: {s.TotalScore}", + }, + new SpriteText + { + TextSize = 40, + Text = $@"MaxCombo: {s.MaxCombo}", + } + } + } + }; + } } } diff --git a/osu.Game/Screens/Select/BeatmapInfoOverlay.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs similarity index 76% rename from osu.Game/Screens/Select/BeatmapInfoOverlay.cs rename to osu.Game/Screens/Select/BeatmapInfoWedge.cs index 1e12f03749..203d290fc3 100644 --- a/osu.Game/Screens/Select/BeatmapInfoOverlay.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -1,4 +1,6 @@ using System; +using osu.Framework; +using osu.Framework.Allocation; using OpenTK; using OpenTK.Graphics; using osu.Framework.Graphics; @@ -8,31 +10,52 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Framework.Graphics.Colour; +using osu.Game.Beatmaps.Drawables; namespace osu.Game.Screens.Select { - class BeatmapInfoOverlay : Container + class BeatmapInfoWedge : Container { + private static readonly Vector2 wedged_container_shear = new Vector2(0.15f, 0); + private Container beatmapInfoContainer; + private BaseGame game; + + public BeatmapInfoWedge() + { + Shear = wedged_container_shear; + Masking = true; + BorderColour = new Color4(221, 255, 255, 255); + BorderThickness = 2.5f; + EdgeEffect = new EdgeEffect + { + Type = EdgeEffectType.Glow, + Colour = new Color4(130, 204, 255, 150), + Radius = 20, + Roundness = 15, + }; + } + + [BackgroundDependencyLoader] + private void load(BaseGame game) + { + this.game = game; + } + public void UpdateBeatmap(WorkingBeatmap beatmap) { if (beatmap == null) return; - float newDepth = 0; - if (beatmapInfoContainer != null) - { - newDepth = beatmapInfoContainer.Depth - 1; - beatmapInfoContainer.FadeOut(250); - beatmapInfoContainer.Expire(); - } + var lastContainer = beatmapInfoContainer; - FadeIn(250); + float newDepth = lastContainer?.Depth + 1 ?? 0; BeatmapSetInfo beatmapSetInfo = beatmap.BeatmapSetInfo; BeatmapInfo beatmapInfo = beatmap.BeatmapInfo; - Add(beatmapInfoContainer = new BufferedContainer + + (beatmapInfoContainer = new BufferedContainer { Depth = newDepth, PixelSnapping = true, @@ -51,18 +74,17 @@ namespace osu.Game.Screens.Select }, // We use a container, such that we can set the colour gradient to go across the // vertices of the masked container instead of the vertices of the (larger) sprite. - beatmap.Background == null ? new Container() : new Container + new Container { RelativeSizeAxes = Axes.Both, ColourInfo = ColourInfo.GradientVertical(Color4.White, new Color4(1f, 1f, 1f, 0.3f)), Children = new [] { // Zoomed-in and cropped beatmap background - new Sprite + new BeatmapBackgroundSprite(beatmap) { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Texture = beatmap.Background, FillMode = FillMode.Fill, }, }, @@ -117,6 +139,14 @@ namespace osu.Game.Screens.Select } } } + }).Preload(game, delegate(Drawable d) + { + FadeIn(250); + + lastContainer?.FadeOut(250); + lastContainer?.Expire(); + + Add(d); }); } } diff --git a/osu.Game/Screens/Select/CarouselContainer.cs b/osu.Game/Screens/Select/CarouselContainer.cs index 08220ea69d..98bcf093af 100644 --- a/osu.Game/Screens/Select/CarouselContainer.cs +++ b/osu.Game/Screens/Select/CarouselContainer.cs @@ -10,39 +10,85 @@ using osu.Game.Database; using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Lists; using osu.Game.Beatmaps.Drawables; - +using osu.Framework.Timing; + namespace osu.Game.Screens.Select { class CarouselContainer : ScrollContainer { private Container scrollableContent; private List groups = new List(); - private List panels = new List(); public BeatmapGroup SelectedGroup { get; private set; } public BeatmapPanel SelectedPanel { get; private set; } - - private List yPositions = new List(); - - public CarouselContainer() - { - DistanceDecayJump = 0.01; - Add(scrollableContent = new Container + private List yPositions = new List(); + private CarouselLifetimeList Lifetime; + + public CarouselContainer() + { + DistanceDecayJump = 0.01; + + Add(scrollableContent = new Container(Lifetime = new CarouselLifetimeList(DepthComparer)) + { + RelativeSizeAxes = Axes.X, + }); + } + + internal class CarouselLifetimeList : LifetimeList + { + public CarouselLifetimeList(IComparer comparer) + : base(comparer) + { + } + + public int StartIndex; + public int EndIndex; + + public override bool Update(FrameTimeInfo time) { - RelativeSizeAxes = Axes.X, - }); - } + bool anyAliveChanged = false; + //check existing items to make sure they haven't died. + foreach (var item in AliveItems.ToArray()) + { + item.UpdateTime(time); + if (!item.IsAlive) + { + //todo: make this more efficient + int i = IndexOf(item); + anyAliveChanged |= CheckItem(item, ref i); + } + } + + //handle custom range + for (int i = StartIndex; i < EndIndex; i++) + { + var item = this[i]; + item.UpdateTime(time); + anyAliveChanged |= CheckItem(item, ref i); + } + + return anyAliveChanged; + } + } + public void AddGroup(BeatmapGroup group) { group.State = BeatmapGroupState.Collapsed; - groups.Add(group); - panels.Add(group.Header); + + group.Header.Depth = -scrollableContent.Children.Count(); + scrollableContent.Add(group.Header); + foreach (BeatmapPanel panel in group.BeatmapPanels) - panels.Add(panel); + { + panel.Depth = -scrollableContent.Children.Count(); + scrollableContent.Add(panel); + } computeYPositions(); } @@ -74,12 +120,19 @@ namespace osu.Game.Screens.Select if (group.State == BeatmapGroupState.Expanded) { group.Header.MoveToX(-100, 500, EasingTypes.OutExpo); + var headerY = group.Header.Position.Y; foreach (BeatmapPanel panel in group.BeatmapPanels) { if (panel == SelectedPanel) selectedY = currentY + panel.DrawHeight / 2 - DrawHeight / 2; + panel.MoveToX(-50, 500, EasingTypes.OutExpo); + + //on first display we want to begin hidden under our group's header. + if (panel.Alpha == 0) + panel.MoveToY(headerY); + movePanel(panel, true, ref currentY); } } @@ -88,7 +141,10 @@ namespace osu.Game.Screens.Select group.Header.MoveToX(0, 500, EasingTypes.OutExpo); foreach (BeatmapPanel panel in group.BeatmapPanels) + { + panel.MoveToX(0, 500, EasingTypes.OutExpo); movePanel(panel, false, ref currentY); + } } } @@ -114,11 +170,7 @@ namespace osu.Game.Screens.Select public void SelectGroup(BeatmapGroup group, BeatmapPanel panel) { if (SelectedGroup != null && SelectedGroup != group) - { SelectedGroup.State = BeatmapGroupState.Collapsed; - foreach (BeatmapPanel p in group.BeatmapPanels) - p.MoveToY(group.Header.Position.Y); - } SelectedGroup = group; panel.State = PanelSelectedState.Selected; @@ -128,7 +180,7 @@ namespace osu.Game.Screens.Select ScrollTo(selectedY); } - private static float offsetX(Panel panel, float dist, float halfHeight) + private static float offsetX(float dist, float halfHeight) { // The radius of the circle the carousel moves on. const float CIRCLE_RADIUS = 4; @@ -138,28 +190,16 @@ namespace osu.Game.Screens.Select return 125 + x; } - private void addPanel(int index) - { - Panel panel = panels[index]; - if (panel.Hidden) - return; - - if (!scrollableContent.Contains(panel)) - { - panel.Depth = index + (panel is BeatmapSetHeader ? panels.Count : 0); - scrollableContent.Add(panel); - } - } - protected override void Update() { base.Update(); float drawHeight = DrawHeight; - scrollableContent.RemoveAll(delegate (Panel p) + + Lifetime.AliveItems.ForEach(delegate (Panel p) { float panelPosY = p.Position.Y; - return panelPosY < Current - p.DrawHeight || panelPosY > Current + drawHeight || !IsVisible; + p.IsOnScreen = panelPosY >= Current - p.DrawHeight && panelPosY <= Current + drawHeight; }); int firstIndex = yPositions.BinarySearch(Current - Panel.MAX_HEIGHT); @@ -167,19 +207,24 @@ namespace osu.Game.Screens.Select int lastIndex = yPositions.BinarySearch(Current + drawHeight); if (lastIndex < 0) lastIndex = ~lastIndex; - for (int i = firstIndex; i < lastIndex; ++i) - addPanel(i); + Lifetime.StartIndex = firstIndex; + Lifetime.EndIndex = lastIndex; float halfHeight = drawHeight / 2; - foreach (Panel panel in scrollableContent.Children) + + for (int i = firstIndex; i < lastIndex; ++i) { + var panel = Lifetime[i]; + + panel.IsOnScreen = true; + float panelDrawY = panel.Position.Y - Current + panel.DrawHeight / 2; float dist = Math.Abs(1f - panelDrawY / halfHeight); // Setting the origin position serves as an additive position on top of potential // local transformation we may want to apply (e.g. when a panel gets selected, we // may want to smoothly transform it leftwards.) - panel.OriginPosition = new Vector2(-offsetX(panel, dist, halfHeight), 0); + panel.OriginPosition = new Vector2(-offsetX(dist, halfHeight), 0); // We are applying a multiplicative alpha (which is internally done by nesting an // additional container and setting that container's alpha) such that we can diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 604c4aae28..983d5008df 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -19,6 +19,7 @@ using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Modes; using osu.Game.Screens.Backgrounds; +using osu.Game.Graphics.UserInterface; using OpenTK; using OpenTK.Graphics; using osu.Game.Screens.Play; @@ -38,9 +39,8 @@ namespace osu.Game.Screens.Select private TrackManager trackManager; private static readonly Vector2 wedged_container_size = new Vector2(0.5f, 225); - private static readonly Vector2 wedged_container_shear = new Vector2(0.15f, 0); private static readonly Vector2 wedged_container_start_position = new Vector2(0, 50); - private BeatmapInfoOverlay wedgedBeatmapInfoOverlay; + private BeatmapInfoWedge beatmapInfoWedge; private static readonly Vector2 BACKGROUND_BLUR = new Vector2(20); private CancellationTokenSource initialAddSetsTask; @@ -73,11 +73,8 @@ namespace osu.Game.Screens.Select } } - /// Optionally provide a database to use instead of the OsuGame one. - public PlaySongSelect(BeatmapDatabase database = null) + public PlaySongSelect() { - this.database = database; - const float carouselWidth = 640; const float bottomToolHeight = 50; Children = new Drawable[] @@ -102,24 +99,13 @@ namespace osu.Game.Screens.Select Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, }, - wedgedBeatmapInfoOverlay = new BeatmapInfoOverlay + beatmapInfoWedge = new BeatmapInfoWedge { Alpha = 0, Position = wedged_container_start_position, Size = wedged_container_size, RelativeSizeAxes = Axes.X, - Shear = wedged_container_shear, Margin = new MarginPadding { Top = 20, Right = 20, }, - Masking = true, - BorderColour = new Color4(221, 255, 255, 255), - BorderThickness = 2.5f, - EdgeEffect = new EdgeEffect - { - Type = EdgeEffectType.Glow, - Colour = new Color4(130, 204, 255, 150), - Radius = 20, - Roundness = 15, - }, }, new Container { @@ -135,6 +121,13 @@ namespace osu.Game.Screens.Select Size = Vector2.One, Colour = new Color4(0, 0, 0, 0.5f), }, + new BackButton + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + //RelativeSizeAxes = Axes.Y, + Action = () => Exit() + }, new Button { Anchor = Anchor.CentreRight, @@ -239,7 +232,7 @@ namespace osu.Game.Screens.Select (Background as BackgroundModeBeatmap)?.BlurTo(BACKGROUND_BLUR, 1000); } - wedgedBeatmapInfoOverlay.UpdateBeatmap(beatmap); + beatmapInfoWedge.UpdateBeatmap(beatmap); } /// diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 9cb1535981..c4915b7567 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -63,8 +63,15 @@ - + + + + + + + + @@ -85,6 +92,11 @@ + + + + + @@ -146,20 +158,20 @@ - - + + - + - - - - + + + + - +