diff --git a/osu-framework b/osu-framework index 07e84f60b0..3760443ea9 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 07e84f60b0d2ee443f366cb2e34bf25b680983dd +Subproject commit 3760443ea9bf682a1bbf6cfa6aa00ea9541c12aa diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index 2efb0c0707..0e4935aa7a 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -5,9 +5,9 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using System.Collections.Generic; using System; +using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Osu.UI; namespace osu.Game.Rulesets.Catch.Beatmaps { @@ -17,14 +17,37 @@ namespace osu.Game.Rulesets.Catch.Beatmaps protected override IEnumerable ConvertHitObject(HitObject obj, Beatmap beatmap) { - if (!(obj is IHasXPosition)) + var curveData = obj as IHasCurve; + var positionData = obj as IHasPosition; + var comboData = obj as IHasCombo; + + if (positionData == null) yield break; + if (curveData != null) + { + yield return new JuiceStream + { + StartTime = obj.StartTime, + Samples = obj.Samples, + ControlPoints = curveData.ControlPoints, + CurveType = curveData.CurveType, + Distance = curveData.Distance, + RepeatSamples = curveData.RepeatSamples, + RepeatCount = curveData.RepeatCount, + X = positionData.X / CatchPlayfield.BASE_WIDTH, + NewCombo = comboData?.NewCombo ?? false + }; + + yield break; + } + yield return new Fruit { StartTime = obj.StartTime, - NewCombo = (obj as IHasCombo)?.NewCombo ?? false, - X = ((IHasXPosition)obj).X / OsuPlayfield.BASE_SIZE.X + Samples = obj.Samples, + NewCombo = comboData?.NewCombo ?? false, + X = positionData.X / CatchPlayfield.BASE_WIDTH }; } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs new file mode 100644 index 0000000000..e057bf3d8e --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs @@ -0,0 +1,60 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Catch.Objects.Drawable +{ + public abstract class DrawableCatchHitObject : DrawableCatchHitObject + where TObject : CatchBaseHit + { + public new TObject HitObject; + + protected DrawableCatchHitObject(TObject hitObject) + : base(hitObject) + { + HitObject = hitObject; + } + } + + public abstract class DrawableCatchHitObject : DrawableScrollingHitObject + { + protected DrawableCatchHitObject(CatchBaseHit hitObject) + : base(hitObject) + { + RelativePositionAxes = Axes.Both; + X = hitObject.X; + Y = (float)HitObject.StartTime; + } + + public Func CheckPosition; + + protected override void CheckForJudgements(bool userTriggered, double timeOffset) + { + if (timeOffset > 0) + AddJudgement(new Judgement { Result = CheckPosition?.Invoke(HitObject) ?? false ? HitResult.Perfect : HitResult.Miss }); + } + + private const float preempt = 1000; + + protected override void UpdateState(ArmedState state) + { + using (BeginAbsoluteSequence(HitObject.StartTime - preempt)) + { + // animation + this.FadeIn(200); + } + + switch (state) + { + case ArmedState.Miss: + using (BeginAbsoluteSequence(HitObject.StartTime, true)) + this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out); + break; + } + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs new file mode 100644 index 0000000000..2b2a8e7f8d --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs @@ -0,0 +1,34 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces; +using OpenTK; + +namespace osu.Game.Rulesets.Catch.Objects.Drawable +{ + public class DrawableDroplet : DrawableCatchHitObject + { + public DrawableDroplet(Droplet h) + : base(h) + { + Origin = Anchor.Centre; + + Size = new Vector2(Pulp.PULP_SIZE); + + AccentColour = h.ComboColour; + Masking = false; + } + + [BackgroundDependencyLoader] + private void load() + { + Child = new Pulp + { + AccentColour = AccentColour, + Scale = new Vector2(0.8f), + }; + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs index e0c9f0c028..4c28a9d021 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs @@ -1,72 +1,29 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.MathUtils; -using osu.Game.Graphics; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces; using OpenTK; -using OpenTK.Graphics; namespace osu.Game.Rulesets.Catch.Objects.Drawable { - public class DrawableFruit : DrawableScrollingHitObject + public class DrawableFruit : DrawableCatchHitObject { - private const float pulp_size = 20; - - private class Pulp : Circle, IHasAccentColour - { - public Pulp() - { - Size = new Vector2(pulp_size); - - Blending = BlendingMode.Additive; - Colour = Color4.White.Opacity(0.9f); - } - - private Color4 accentColour; - public Color4 AccentColour - { - get { return accentColour; } - set - { - accentColour = value; - - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Radius = 5, - Colour = accentColour.Lighten(100), - }; - } - } - } - - - public DrawableFruit(CatchBaseHit h) + public DrawableFruit(Fruit h) : base(h) { Origin = Anchor.Centre; - Size = new Vector2(pulp_size * 2.2f, pulp_size * 2.8f); - - RelativePositionAxes = Axes.Both; - X = h.X; + Size = new Vector2(Pulp.PULP_SIZE * 2.2f, Pulp.PULP_SIZE * 2.8f); AccentColour = HitObject.ComboColour; - Masking = false; Rotation = (float)(RNG.NextDouble() - 0.5f) * 40; } - public Func CheckPosition; - [BackgroundDependencyLoader] private void load() { @@ -114,30 +71,5 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable } }; } - - private const float preempt = 1000; - - protected override void CheckForJudgements(bool userTriggered, double timeOffset) - { - if (timeOffset > 0) - AddJudgement(new Judgement { Result = CheckPosition?.Invoke(HitObject) ?? false ? HitResult.Perfect : HitResult.Miss }); - } - - protected override void UpdateState(ArmedState state) - { - using (BeginAbsoluteSequence(HitObject.StartTime - preempt)) - { - // animation - this.FadeIn(200); - } - - switch (state) - { - case ArmedState.Miss: - using (BeginAbsoluteSequence(HitObject.StartTime, true)) - this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out); - break; - } - } } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs new file mode 100644 index 0000000000..afda91d0b4 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs @@ -0,0 +1,54 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using OpenTK; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Catch.Objects.Drawable +{ + public class DrawableJuiceStream : DrawableCatchHitObject + { + private readonly Container dropletContainer; + + public DrawableJuiceStream(JuiceStream s) : base(s) + { + RelativeSizeAxes = Axes.Both; + Height = (float)HitObject.Duration; + X = 0; + + Child = dropletContainer = new Container + { + RelativeSizeAxes = Axes.Both, + RelativeChildOffset = new Vector2(0, (float)HitObject.StartTime), + RelativeChildSize = new Vector2(1, (float)HitObject.Duration) + }; + + foreach (CatchBaseHit tick in s.Ticks) + { + TinyDroplet tiny = tick as TinyDroplet; + if (tiny != null) + { + AddNested(new DrawableDroplet(tiny) { Scale = new Vector2(0.5f) }); + continue; + } + + Droplet droplet = tick as Droplet; + if (droplet != null) + AddNested(new DrawableDroplet(droplet)); + + Fruit fruit = tick as Fruit; + if (fruit != null) + AddNested(new DrawableFruit(fruit)); + } + } + + protected override void AddNested(DrawableHitObject h) + { + ((DrawableCatchHitObject)h).CheckPosition = o => CheckPosition?.Invoke(o) ?? false; + dropletContainer.Add(h); + base.AddNested(h); + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs new file mode 100644 index 0000000000..00ddd365e3 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs @@ -0,0 +1,43 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces +{ + public class Pulp : Circle, IHasAccentColour + { + public const float PULP_SIZE = 20; + + public Pulp() + { + Size = new Vector2(PULP_SIZE); + + Blending = BlendingMode.Additive; + Colour = Color4.White.Opacity(0.9f); + } + + private Color4 accentColour; + public Color4 AccentColour + { + get { return accentColour; } + set + { + accentColour = value; + + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Radius = 5, + Colour = accentColour.Lighten(100), + }; + } + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs new file mode 100644 index 0000000000..6462f6f6a8 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -0,0 +1,169 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using OpenTK; +using osu.Framework.Lists; + +namespace osu.Game.Rulesets.Catch.Objects +{ + public class JuiceStream : CatchBaseHit, IHasCurve + { + /// + /// Positional distance that results in a duration of one second, before any speed adjustments. + /// + private const float base_scoring_distance = 100; + + public readonly SliderCurve Curve = new SliderCurve(); + + public int RepeatCount { get; set; } = 1; + + public double Velocity; + public double TickDistance; + + public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + base.ApplyDefaults(controlPointInfo, difficulty); + + TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); + DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); + + double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; + + Velocity = scoringDistance / timingPoint.BeatLength; + TickDistance = scoringDistance / difficulty.SliderTickRate; + } + + public IEnumerable Ticks + { + get + { + SortedList ticks = new SortedList((a, b) => a.StartTime.CompareTo(b.StartTime)); + + if (TickDistance == 0) + return ticks; + + var length = Curve.Distance; + var tickDistance = Math.Min(TickDistance, length); + var repeatDuration = length / Velocity; + + var minDistanceFromEnd = Velocity * 0.01; + + ticks.Add(new Fruit + { + Samples = Samples, + ComboColour = ComboColour, + StartTime = StartTime, + X = X + }); + + for (var repeat = 0; repeat < RepeatCount; repeat++) + { + var repeatStartTime = StartTime + repeat * repeatDuration; + var reversed = repeat % 2 == 1; + + for (var d = tickDistance; d <= length; d += tickDistance) + { + if (d > length - minDistanceFromEnd) + break; + + var timeProgress = d / length; + var distanceProgress = reversed ? 1 - timeProgress : timeProgress; + + var lastTickTime = repeatStartTime + timeProgress * repeatDuration; + ticks.Add(new Droplet + { + StartTime = lastTickTime, + ComboColour = ComboColour, + X = Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH, + Samples = new SampleInfoList(Samples.Select(s => new SampleInfo + { + Bank = s.Bank, + Name = @"slidertick", + Volume = s.Volume + })) + }); + } + + double tinyTickInterval = tickDistance / length * repeatDuration; + while (tinyTickInterval > 100) + tinyTickInterval /= 2; + + for (double t = 0; t < repeatDuration; t += tinyTickInterval) + { + double progress = reversed ? 1 - t / repeatDuration : t / repeatDuration; + + ticks.Add(new TinyDroplet + { + StartTime = repeatStartTime + t, + ComboColour = ComboColour, + X = Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH, + Samples = new SampleInfoList(Samples.Select(s => new SampleInfo + { + Bank = s.Bank, + Name = @"slidertick", + Volume = s.Volume + })) + }); + } + + ticks.Add(new Fruit + { + Samples = Samples, + ComboColour = ComboColour, + StartTime = repeatStartTime + repeatDuration, + X = Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH + }); + } + + return ticks; + } + } + + public double EndTime => StartTime + RepeatCount * Curve.Distance / Velocity; + + public float EndX => Curve.PositionAt(ProgressAt(1)).X / CatchPlayfield.BASE_WIDTH; + + public double Duration => EndTime - StartTime; + + public double Distance + { + get { return Curve.Distance; } + set { Curve.Distance = value; } + } + + public List ControlPoints + { + get { return Curve.ControlPoints; } + set { Curve.ControlPoints = value; } + } + + public List RepeatSamples { get; set; } = new List(); + + public CurveType CurveType + { + get { return Curve.CurveType; } + set { Curve.CurveType = value; } + } + + public Vector2 PositionAt(double progress) => Curve.PositionAt(ProgressAt(progress)); + + public double ProgressAt(double progress) + { + double p = progress * RepeatCount % 1; + if (RepeatAt(progress) % 2 == 1) + p = 1 - p; + return p; + } + + public int RepeatAt(double progress) => (int)(progress * RepeatCount); + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs b/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs new file mode 100644 index 0000000000..231f3d5361 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs @@ -0,0 +1,9 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Catch.Objects +{ + public class TinyDroplet : Droplet + { + } +} diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 16756e65f1..66a5636b74 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -21,6 +21,19 @@ namespace osu.Game.Rulesets.Catch.Scoring { foreach (var obj in beatmap.HitObjects) { + var stream = obj as JuiceStream; + + if (stream != null) + { + AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); + AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); + + foreach (var unused in stream.Ticks) + AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); + + continue; + } + var fruit = obj as Fruit; if (fruit != null) diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseCatchPlayer.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseCatchPlayer.cs index 5be07e94c0..25c095426f 100644 --- a/osu.Game.Rulesets.Catch/Tests/TestCaseCatchPlayer.cs +++ b/osu.Game.Rulesets.Catch/Tests/TestCaseCatchPlayer.cs @@ -2,8 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using NUnit.Framework; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Catch.Objects; namespace osu.Game.Rulesets.Catch.Tests { @@ -13,15 +11,5 @@ namespace osu.Game.Rulesets.Catch.Tests public TestCaseCatchPlayer() : base(typeof(CatchRuleset)) { } - - protected override Beatmap CreateBeatmap() - { - var beatmap = new Beatmap(); - - for (int i = 0; i < 256; i++) - beatmap.HitObjects.Add(new Fruit { X = 0.5f, StartTime = i * 100, NewCombo = i % 8 == 0 }); - - return beatmap; - } } } diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseCatchStacker.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseCatchStacker.cs new file mode 100644 index 0000000000..7c3bb2bfd8 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Tests/TestCaseCatchStacker.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Objects; + +namespace osu.Game.Rulesets.Catch.Tests +{ + [TestFixture] + public class TestCaseCatchStacker : Game.Tests.Visual.TestCasePlayer + { + public TestCaseCatchStacker() : base(typeof(CatchRuleset)) + { + } + + protected override Beatmap CreateBeatmap() + { + var beatmap = new Beatmap(); + + for (int i = 0; i < 256; i++) + beatmap.HitObjects.Add(new Fruit { X = 0.5f, StartTime = i * 100, NewCombo = i % 8 == 0 }); + + return beatmap; + } + } +} diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index c4033b562d..987eef5e45 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Catch.UI { public class CatchPlayfield : ScrollingPlayfield { + public static readonly float BASE_WIDTH = 512; + protected override Container Content => content; private readonly Container content; @@ -28,8 +30,6 @@ namespace osu.Game.Rulesets.Catch.UI Reversed.Value = true; - Size = new Vector2(1); - Anchor = Anchor.TopCentre; Origin = Anchor.TopCentre; @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Catch.UI base.Add(h); - var fruit = (DrawableFruit)h; + var fruit = (DrawableCatchHitObject)h; fruit.CheckPosition = CheckIfWeCanCatch; } @@ -83,7 +83,11 @@ namespace osu.Game.Rulesets.Catch.UI if (judgement.IsHit) { Vector2 screenPosition = judgedObject.ScreenSpaceDrawQuad.Centre; - Remove(judgedObject); + + // todo: don't do this + (judgedObject.Parent as Container)?.Remove(judgedObject); + (judgedObject.Parent as Container)?.Remove(judgedObject); + catcher.Add(judgedObject, screenPosition); } } diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs index 8a6ef71996..92912eb177 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs @@ -32,8 +32,13 @@ namespace osu.Game.Rulesets.Catch.UI protected override DrawableHitObject GetVisualRepresentation(CatchBaseHit h) { - if (h is Fruit) - return new DrawableFruit(h); + var fruit = h as Fruit; + if (fruit != null) + return new DrawableFruit(fruit); + + var stream = h as JuiceStream; + if (stream != null) + return new DrawableJuiceStream(stream); return null; } diff --git a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj index 718ae32a17..ba2c7a5f2e 100644 --- a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj +++ b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj @@ -50,14 +50,21 @@ + + + + + + + @@ -79,11 +86,6 @@ osu.Framework False - - {C92A607B-1FDD-4954-9F92-03FF547D9080} - osu.Game.Rulesets.Osu - False - {2a66dd92-adb1-4994-89e2-c94e04acda0d} osu.Game diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs index c079d3343b..ebe978f659 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces if (records.Count > 0) { var record = records.Peek(); - while (Time.Current - records.Peek().Time > spm_count_duration) + while (records.Count > 0 && Time.Current - records.Peek().Time > spm_count_duration) record = records.Dequeue(); SpinsPerMinute = (currentRotation - record.Rotation) / (Time.Current - record.Time) * 1000 * 60 / 360; } diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 5e4e122fb5..29c1e3a047 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -19,8 +19,10 @@ namespace osu.Game.Beatmaps //TODO: should be in database public int BeatmapVersion; + [JsonProperty("id")] public int? OnlineBeatmapID { get; set; } + [JsonProperty("beatmapset_id")] public int? OnlineBeatmapSetID { get; set; } [ForeignKey(typeof(BeatmapSetInfo))] diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index ce07b61ee4..a998b3eec3 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -573,6 +573,8 @@ namespace osu.Game.Beatmaps } catch { return new TrackVirtual(); } } + + protected override Waveform GetWaveform() => new Waveform(store.GetStream(getPathForFile(Metadata.AudioFile))); } /// diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 277846ee80..959e71d48d 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -43,6 +43,7 @@ namespace osu.Game.Beatmaps protected abstract Beatmap GetBeatmap(); protected abstract Texture GetBackground(); protected abstract Track GetTrack(); + protected virtual Waveform GetWaveform() => new Waveform(); private Beatmap beatmap; private readonly object beatmapLock = new object(); @@ -96,6 +97,17 @@ namespace osu.Game.Beatmaps } } + private Waveform waveform; + private readonly object waveformLock = new object(); + public Waveform Waveform + { + get + { + lock (waveformLock) + return waveform ?? (waveform = GetWaveform()); + } + } + public bool TrackLoaded => track != null; public void TransferTo(WorkingBeatmap other) @@ -114,6 +126,8 @@ namespace osu.Game.Beatmaps { background?.Dispose(); background = null; + + waveform?.Dispose(); } public void DisposeTrack() diff --git a/osu.Game/Graphics/Containers/OsuHoverContainer.cs b/osu.Game/Graphics/Containers/OsuHoverContainer.cs new file mode 100644 index 0000000000..3f82ad2179 --- /dev/null +++ b/osu.Game/Graphics/Containers/OsuHoverContainer.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Input; + +namespace osu.Game.Graphics.Containers +{ + public class OsuHoverContainer : OsuClickableContainer + { + private Color4 hoverColour; + + protected override bool OnHover(InputState state) + { + this.FadeColour(hoverColour, 500, Easing.OutQuint); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + this.FadeColour(Color4.White, 500, Easing.OutQuint); + base.OnHoverLost(state); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + hoverColour = colours.Yellow; + } + } +} diff --git a/osu.Game/Graphics/Processing/RatioAdjust.cs b/osu.Game/Graphics/Processing/RatioAdjust.cs deleted file mode 100644 index 640814d8e1..0000000000 --- a/osu.Game/Graphics/Processing/RatioAdjust.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Graphics.Containers; -using OpenTK; -using osu.Framework.Graphics; - -namespace osu.Game.Graphics.Processing -{ - internal class RatioAdjust : Container - { - public RatioAdjust() - { - RelativeSizeAxes = Axes.Both; - } - - protected override void Update() - { - base.Update(); - Vector2 parent = Parent.DrawSize; - - Scale = new Vector2(Math.Min(parent.Y / 768f, parent.X / 1024f)); - Size = new Vector2(1 / Scale.X); - } - } -} diff --git a/osu.Game/Graphics/UserInterface/IconButton.cs b/osu.Game/Graphics/UserInterface/IconButton.cs index d58f5797a3..afffd930ef 100644 --- a/osu.Game/Graphics/UserInterface/IconButton.cs +++ b/osu.Game/Graphics/UserInterface/IconButton.cs @@ -27,13 +27,28 @@ namespace osu.Game.Graphics.UserInterface set { flashColour = value; } } + private Color4? iconColour; /// /// The icon colour. This does not affect . /// public Color4 IconColour { - get { return icon.Colour; } - set { icon.Colour = value; } + get { return iconColour ?? Color4.White; } + set + { + iconColour = value; + icon.Colour = value; + } + } + + private Color4? iconHoverColour; + /// + /// The icon colour while the is hovered. + /// + public Color4 IconHoverColour + { + get { return iconHoverColour ?? IconColour; } + set { iconHoverColour = value; } } private Color4? hoverColour; @@ -133,12 +148,14 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnHover(InputState state) { hover.FadeIn(500, Easing.OutQuint); + icon.FadeColour(IconHoverColour, 500, Easing.OutQuint); return base.OnHover(state); } protected override void OnHoverLost(InputState state) { hover.FadeOut(500, Easing.OutQuint); + icon.FadeColour(IconColour, 500, Easing.OutQuint); base.OnHoverLost(state); } diff --git a/osu.Game/Online/API/Requests/GetBeatmapSetRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapSetRequest.cs new file mode 100644 index 0000000000..e0fdc9adf2 --- /dev/null +++ b/osu.Game/Online/API/Requests/GetBeatmapSetRequest.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Online.API.Requests +{ + public class GetBeatmapSetRequest : APIRequest + { + private readonly int beatmapSetId; + + public GetBeatmapSetRequest(int beatmapSetId) + { + this.beatmapSetId = beatmapSetId; + } + + protected override string Target => $@"beatmapsets/{beatmapSetId}"; + } +} diff --git a/osu.Game/Online/API/Requests/GetBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapSetsResponse.cs similarity index 68% rename from osu.Game/Online/API/Requests/GetBeatmapSetsRequest.cs rename to osu.Game/Online/API/Requests/GetBeatmapSetsResponse.cs index 470e13ea7b..085563845d 100644 --- a/osu.Game/Online/API/Requests/GetBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/GetBeatmapSetsResponse.cs @@ -5,34 +5,11 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; using osu.Game.Beatmaps; -using osu.Game.Overlays; -using osu.Game.Overlays.Direct; using osu.Game.Rulesets; using osu.Game.Users; namespace osu.Game.Online.API.Requests { - public class GetBeatmapSetsRequest : APIRequest> - { - private readonly string query; - private readonly RulesetInfo ruleset; - private readonly RankStatus rankStatus; - private readonly DirectSortCriteria sortCriteria; - private readonly SortDirection direction; - private string directionString => direction == SortDirection.Descending ? @"desc" : @"asc"; - - public GetBeatmapSetsRequest(string query, RulesetInfo ruleset, RankStatus rankStatus = RankStatus.Any, DirectSortCriteria sortCriteria = DirectSortCriteria.Ranked, SortDirection direction = SortDirection.Descending) - { - this.query = System.Uri.EscapeDataString(query); - this.ruleset = ruleset; - this.rankStatus = rankStatus; - this.sortCriteria = sortCriteria; - this.direction = direction; - } - - protected override string Target => $@"beatmapsets/search?q={query}&m={ruleset.ID ?? 0}&s={(int)rankStatus}&sort={sortCriteria.ToString().ToLower()}_{directionString}"; - } - public class GetBeatmapSetsResponse : BeatmapMetadata { [JsonProperty(@"covers")] @@ -51,7 +28,7 @@ namespace osu.Game.Online.API.Requests private int onlineId { get; set; } [JsonProperty(@"creator")] - private string creatorUsername; + private string creatorUsername { get; set; } [JsonProperty(@"user_id")] private long creatorId = 1; diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index 537fce2548..3777e10a31 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; using osu.Game.Beatmaps; +using osu.Game.Rulesets; using osu.Game.Users; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; @@ -67,6 +68,9 @@ namespace osu.Game.Online.API.Requests set { Replay = value; } } + [JsonProperty(@"mode_int")] + public int OnlineRulesetID { get; set; } + [JsonProperty(@"score_id")] private long onlineScoreID { @@ -79,6 +83,18 @@ namespace osu.Game.Online.API.Requests set { Date = value; } } + [JsonProperty(@"beatmap")] + private BeatmapInfo beatmap + { + set { Beatmap = value; } + } + + [JsonProperty(@"beatmapset")] + private BeatmapMetadata metadata + { + set { Beatmap.Metadata = value; } + } + [JsonProperty(@"statistics")] private Dictionary jsonStats { @@ -116,7 +132,12 @@ namespace osu.Game.Online.API.Requests public void ApplyBeatmap(BeatmapInfo beatmap) { Beatmap = beatmap; - Ruleset = beatmap.Ruleset; + ApplyRuleset(beatmap.Ruleset); + } + + public void ApplyRuleset(RulesetInfo ruleset) + { + Ruleset = ruleset; // Evaluate the mod string Mods = Ruleset.CreateInstance().GetAllMods().Where(mod => modStrings.Contains(mod.ShortenedName)).ToArray(); diff --git a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs new file mode 100644 index 0000000000..98db234196 --- /dev/null +++ b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs @@ -0,0 +1,30 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; + +namespace osu.Game.Online.API.Requests +{ + public class GetUserScoresRequest : APIRequest> + { + private readonly long userId; + private readonly ScoreType type; + private readonly int offset; + + public GetUserScoresRequest(long userId, ScoreType type, int offset = 0) + { + this.userId = userId; + this.type = type; + this.offset = offset; + } + + protected override string Target => $@"users/{userId}/scores/{type.ToString().ToLower()}?offset={offset}"; + } + + public enum ScoreType + { + Best, + Firsts, + Recent + } +} \ No newline at end of file diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs new file mode 100644 index 0000000000..56858b3d56 --- /dev/null +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Overlays; +using osu.Game.Overlays.Direct; +using osu.Game.Rulesets; + +namespace osu.Game.Online.API.Requests +{ + public class SearchBeatmapSetsRequest : APIRequest> + { + private readonly string query; + private readonly RulesetInfo ruleset; + private readonly RankStatus rankStatus; + private readonly DirectSortCriteria sortCriteria; + private readonly SortDirection direction; + private string directionString => direction == SortDirection.Descending ? @"desc" : @"asc"; + + public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset, RankStatus rankStatus = RankStatus.Any, DirectSortCriteria sortCriteria = DirectSortCriteria.Ranked, SortDirection direction = SortDirection.Descending) + { + this.query = System.Uri.EscapeDataString(query); + this.ruleset = ruleset; + this.rankStatus = rankStatus; + this.sortCriteria = sortCriteria; + this.direction = direction; + } + + protected override string Target => $@"beatmapsets/search?q={query}&m={ruleset.ID ?? 0}&s={(int)rankStatus}&sort={sortCriteria.ToString().ToLower()}_{directionString}"; + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 75a1d61371..d1baca68db 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -188,8 +188,8 @@ namespace osu.Game GetToolbarHeight = () => ToolbarOffset, Depth = -1 }, overlayContent.Add); - LoadComponentAsync(beatmapSetOverlay = new BeatmapSetOverlay { Depth = -2 }, mainContent.Add); - LoadComponentAsync(userProfile = new UserProfileOverlay { Depth = -3 }, mainContent.Add); + LoadComponentAsync(userProfile = new UserProfileOverlay { Depth = -2 }, mainContent.Add); + LoadComponentAsync(beatmapSetOverlay = new BeatmapSetOverlay { Depth = -3 }, mainContent.Add); LoadComponentAsync(musicController = new MusicController { Depth = -4, diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 8e7bfa8a76..93eb1d76df 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -15,7 +15,6 @@ using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Cursor; -using osu.Game.Graphics.Processing; using osu.Game.Online.API; using SQLite.Net; using osu.Framework.Graphics.Performance; @@ -186,7 +185,7 @@ namespace osu.Game GlobalKeyBindingInputManager globalBinding; - base.Content.Add(new RatioAdjust + base.Content.Add(new DrawSizePreservingFillContainer { Children = new Drawable[] { diff --git a/osu.Game/Overlays/BeatmapSet/PreviewButton.cs b/osu.Game/Overlays/BeatmapSet/PreviewButton.cs index bdb06106f0..f77a1f4a0a 100644 --- a/osu.Game/Overlays/BeatmapSet/PreviewButton.cs +++ b/osu.Game/Overlays/BeatmapSet/PreviewButton.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Allocation; -using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -12,9 +11,10 @@ using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; using OpenTK; using OpenTK.Graphics; +using osu.Game.Overlays.Direct; +using osu.Framework.Configuration; namespace osu.Game.Overlays.BeatmapSet { @@ -22,73 +22,16 @@ namespace osu.Game.Overlays.BeatmapSet { private const float transition_duration = 500; - private readonly Container audioWrapper; private readonly Box bg, progress; - private readonly SpriteIcon icon; - private readonly LoadingAnimation loadingAnimation; + private readonly PlayButton playButton; - private Track preview; + private Track preview => playButton.Preview; + private Bindable playing => playButton.Playing; - private bool loading - { - set - { - if (value) - { - loadingAnimation.Show(); - icon.FadeOut(transition_duration * 5, Easing.OutQuint); - } - else - { - loadingAnimation.Hide(); - icon.FadeIn(transition_duration, Easing.OutQuint); - } - } - } - - private BeatmapSetInfo beatmapSet; public BeatmapSetInfo BeatmapSet { - get { return beatmapSet; } - set - { - if (value == beatmapSet) return; - beatmapSet = value; - - Playing = false; - preview = null; - } - } - - private bool playing; - public bool Playing - { - get { return playing; } - set - { - if (value == playing) return; - playing = value; - - if (preview == null) - { - loading = true; - audioWrapper.Child = new AsyncLoadWrapper(new AudioLoadWrapper(BeatmapSet) - { - OnLoadComplete = d => - { - loading = false; - - preview = (d as AudioLoadWrapper)?.Preview; - Playing = Playing; - updatePlayingState(); - }, - }); - - return; - } - - updatePlayingState(); - } + get { return playButton.BeatmapSet; } + set { playButton.BeatmapSet = value; } } public PreviewButton() @@ -97,7 +40,6 @@ namespace osu.Game.Overlays.BeatmapSet Children = new Drawable[] { - audioWrapper = new Container(), bg = new Box { RelativeSizeAxes = Axes.Both, @@ -116,22 +58,16 @@ namespace osu.Game.Overlays.BeatmapSet Alpha = 0f, }, }, - icon = new SpriteIcon + playButton = new PlayButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.fa_play, Size = new Vector2(18), - Shadow = false, - }, - loadingAnimation = new LoadingAnimation - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, }, }; - Action = () => Playing = !Playing; + Action = () => playing.Value = !playing.Value; + playing.ValueChanged += newValue => progress.FadeTo(newValue ? 1 : 0, 100); } [BackgroundDependencyLoader] @@ -144,20 +80,15 @@ namespace osu.Game.Overlays.BeatmapSet { base.Update(); - if (Playing && preview != null) + if (playing.Value && preview != null) { progress.Width = (float)(preview.CurrentTime / preview.Length); - if (preview.HasCompleted) - { - Playing = false; - preview = null; - } } } protected override void Dispose(bool isDisposing) { - Playing = false; + playing.Value = false; base.Dispose(isDisposing); } @@ -172,47 +103,5 @@ namespace osu.Game.Overlays.BeatmapSet bg.FadeColour(Color4.Black.Opacity(0.25f), 100); base.OnHoverLost(state); } - - private void updatePlayingState() - { - if (preview == null) return; - - if (Playing) - { - icon.Icon = FontAwesome.fa_stop; - progress.FadeIn(100); - - preview.Seek(0); - preview.Start(); - } - else - { - icon.Icon = FontAwesome.fa_play; - progress.FadeOut(100); - preview.Stop(); - } - } - - private class AudioLoadWrapper : Drawable - { - private readonly string preview; - - public Track Preview; - - public AudioLoadWrapper(BeatmapSetInfo set) - { - preview = set.OnlineInfo.Preview; - } - - [BackgroundDependencyLoader] - private void load(AudioManager audio) - { - if (!string.IsNullOrEmpty(preview)) - { - Preview = audio.Track.Get(preview); - Preview.Volume.Value = 0.5; - } - } - } } } diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 7a4c6338a1..a60429f737 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Allocation; using OpenTK; using OpenTK.Graphics; using osu.Framework.Extensions.Color4Extensions; @@ -11,7 +12,10 @@ using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Overlays.BeatmapSet; +using osu.Game.Rulesets; namespace osu.Game.Overlays { @@ -23,6 +27,9 @@ namespace osu.Game.Overlays private readonly Header header; private readonly Info info; + private APIAccess api; + private RulesetStore rulesets; + // receive input outside our bounds so we can trigger a close event on ourselves. public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; @@ -75,6 +82,13 @@ namespace osu.Game.Overlays header.Picker.Beatmap.ValueChanged += b => info.Beatmap = b; } + [BackgroundDependencyLoader] + private void load(APIAccess api, RulesetStore rulesets) + { + this.api = api; + this.rulesets = rulesets; + } + protected override void PopIn() { base.PopIn(); @@ -93,6 +107,14 @@ namespace osu.Game.Overlays return true; } + public void ShowBeatmapSet(int beatmapSetId) + { + // todo: display the overlay while we are loading here. we need to support setting BeatmapSet to null for this to work. + var req = new GetBeatmapSetRequest(beatmapSetId); + req.Success += res => ShowBeatmapSet(res.ToBeatmapSet(rulesets)); + api.Queue(req); + } + public void ShowBeatmapSet(BeatmapSetInfo set) { header.BeatmapSet = info.BeatmapSet = set; diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs index 1675a2f663..7464ee7fb8 100644 --- a/osu.Game/Overlays/Direct/DirectGridPanel.cs +++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs @@ -21,6 +21,11 @@ namespace osu.Game.Overlays.Direct private const float vertical_padding = 5; private FillFlowContainer bottomPanel; + private PlayButton playButton; + private Box progressBar; + + protected override PlayButton PlayButton => playButton; + protected override Box PreviewBar => progressBar; public DirectGridPanel(BeatmapSetInfo beatmap) : base(beatmap) { @@ -87,6 +92,15 @@ namespace osu.Game.Overlays.Direct { RelativeSizeAxes = Axes.Both, }, + progressBar = new Box + { + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + BypassAutoSizeAxes = Axes.Both, + Size = new Vector2(0, 3), + Alpha = 0, + Colour = colours.Yellow, + }, new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -178,6 +192,12 @@ namespace osu.Game.Overlays.Direct new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), }, }, + playButton = new PlayButton(SetInfo) + { + Margin = new MarginPadding { Top = 5, Left = 10 }, + Size = new Vector2(30), + Alpha = 0, + }, }); } } diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs index 6702b7394c..5889a1bc12 100644 --- a/osu.Game/Overlays/Direct/DirectListPanel.cs +++ b/osu.Game/Overlays/Direct/DirectListPanel.cs @@ -28,8 +28,14 @@ namespace osu.Game.Overlays.Direct Height = height; } + private PlayButton playButton; + private Box progressBar; + + protected override PlayButton PlayButton => playButton; + protected override Box PreviewBar => progressBar; + [BackgroundDependencyLoader] - private void load(LocalisationEngine localisation) + private void load(LocalisationEngine localisation, OsuColour colours) { Content.CornerRadius = 5; @@ -48,29 +54,50 @@ namespace osu.Game.Overlays.Direct { new FillFlowContainer { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, + Direction = FillDirection.Horizontal, + LayoutEasing = Easing.OutQuint, + LayoutDuration = 120, + Spacing = new Vector2(10, 0), Children = new Drawable[] { - new OsuSpriteText + playButton = new PlayButton(SetInfo) { - Current = localisation.GetUnicodePreference(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title), - TextSize = 18, - Font = @"Exo2.0-BoldItalic", - }, - new OsuSpriteText - { - Current = localisation.GetUnicodePreference(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist), - Font = @"Exo2.0-BoldItalic", + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Size = new Vector2(height / 2), + FillMode = FillMode.Fit, + Alpha = 0, }, new FillFlowContainer { - AutoSizeAxes = Axes.X, - Height = 20, - Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding }, - Children = GetDifficultyIcons(), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new OsuSpriteText + { + Current = localisation.GetUnicodePreference(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title), + TextSize = 18, + Font = @"Exo2.0-BoldItalic", + }, + new OsuSpriteText + { + Current = localisation.GetUnicodePreference(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist), + Font = @"Exo2.0-BoldItalic", + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.X, + Height = 20, + Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding }, + Children = GetDifficultyIcons(), + }, + }, }, - }, + } }, new FillFlowContainer { @@ -126,6 +153,16 @@ namespace osu.Game.Overlays.Direct }, }, }, + progressBar = new Box + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + BypassAutoSizeAxes = Axes.Y, + Size = new Vector2(0, 3), + Alpha = 0, + Colour = colours.Yellow, + }, }); } } diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index 4f7f1bb39e..ef89c0022b 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -20,6 +20,8 @@ using osu.Game.Online.API; using osu.Framework.Logging; using osu.Game.Overlays.Notifications; using osu.Game.Online.API.Requests; +using osu.Framework.Configuration; +using osu.Framework.Audio.Track; namespace osu.Game.Overlays.Direct { @@ -39,6 +41,11 @@ namespace osu.Game.Overlays.Direct private NotificationOverlay notifications; private BeatmapSetOverlay beatmapSetOverlay; + public Track Preview => PlayButton.Preview; + public Bindable PreviewPlaying => PlayButton.Playing; + protected abstract PlayButton PlayButton { get; } + protected abstract Box PreviewBar { get; } + protected override Container Content => content; protected DirectPanel(BeatmapSetInfo setInfo) @@ -104,10 +111,21 @@ namespace osu.Game.Overlays.Direct attachDownload(downloadRequest); } + protected override void Update() + { + base.Update(); + + if (PreviewPlaying && Preview != null) + { + PreviewBar.Width = (float)(Preview.CurrentTime / Preview.Length); + } + } + protected override bool OnHover(InputState state) { content.TweenEdgeEffectTo(edgeEffectHovered, hover_transition_time, Easing.OutQuint); content.MoveToY(-4, hover_transition_time, Easing.OutQuint); + PlayButton.FadeIn(120, Easing.InOutQuint); return base.OnHover(state); } @@ -116,6 +134,8 @@ namespace osu.Game.Overlays.Direct { content.TweenEdgeEffectTo(edgeEffectNormal, hover_transition_time, Easing.OutQuint); content.MoveToY(0, hover_transition_time, Easing.OutQuint); + if (!PreviewPlaying) + PlayButton.FadeOut(120, Easing.InOutQuint); base.OnHoverLost(state); } @@ -123,6 +143,7 @@ namespace osu.Game.Overlays.Direct protected override bool OnClick(InputState state) { ShowInformation(); + PreviewPlaying.Value = false; return true; } @@ -183,6 +204,9 @@ namespace osu.Game.Overlays.Direct { base.LoadComplete(); this.FadeInFromZero(200, Easing.Out); + + PreviewPlaying.ValueChanged += newValue => PlayButton.FadeTo(newValue || IsHovered ? 1 : 0, 120, Easing.InOutQuint); + PreviewPlaying.ValueChanged += newValue => PreviewBar.FadeTo(newValue ? 1 : 0, 120, Easing.InOutQuint); } protected List GetDifficultyIcons() diff --git a/osu.Game/Overlays/Direct/PlayButton.cs b/osu.Game/Overlays/Direct/PlayButton.cs new file mode 100644 index 0000000000..32435a4873 --- /dev/null +++ b/osu.Game/Overlays/Direct/PlayButton.cs @@ -0,0 +1,181 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Track; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.Direct +{ + public class PlayButton : Container + { + public readonly Bindable Playing = new Bindable(); + public Track Preview { get; private set; } + + private BeatmapSetInfo beatmapSet; + public BeatmapSetInfo BeatmapSet + { + get { return beatmapSet; } + set + { + if (value == beatmapSet) return; + beatmapSet = value; + + Playing.Value = false; + trackLoader = null; + Preview = null; + } + } + + private Color4 hoverColour; + private readonly SpriteIcon icon; + private readonly LoadingAnimation loadingAnimation; + + private const float transition_duration = 500; + + private bool loading + { + set + { + if (value) + { + loadingAnimation.Show(); + icon.FadeOut(transition_duration * 5, Easing.OutQuint); + } + else + { + loadingAnimation.Hide(); + icon.FadeIn(transition_duration, Easing.OutQuint); + } + } + } + + public PlayButton(BeatmapSetInfo setInfo = null) + { + BeatmapSet = setInfo; + AddRange(new Drawable[] + { + icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fit, + RelativeSizeAxes = Axes.Both, + Icon = FontAwesome.fa_play, + }, + loadingAnimation = new LoadingAnimation(), + }); + + Playing.ValueChanged += playing => + { + icon.Icon = playing ? FontAwesome.fa_pause : FontAwesome.fa_play; + icon.FadeColour(playing || IsHovered ? hoverColour : Color4.White, 120, Easing.InOutQuint); + updatePreviewTrack(playing); + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colour) + { + hoverColour = colour.Yellow; + } + + protected override bool OnClick(InputState state) + { + Playing.Value = !Playing.Value; + return true; + } + + protected override bool OnHover(InputState state) + { + icon.FadeColour(hoverColour, 120, Easing.InOutQuint); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + if (!Playing.Value) + icon.FadeColour(Color4.White, 120, Easing.InOutQuint); + base.OnHoverLost(state); + } + + protected override void Update() + { + base.Update(); + + if (Preview?.HasCompleted ?? false) + { + Playing.Value = false; + Preview = null; + } + } + + private void updatePreviewTrack(bool playing) + { + if (playing) + { + if (Preview == null) + { + beginAudioLoad(); + return; + } + + Preview.Seek(0); + Preview.Start(); + } + else + { + Preview?.Stop(); + } + } + + private TrackLoader trackLoader; + + private void beginAudioLoad() + { + if (trackLoader != null) return; + + Add(new AsyncLoadWrapper(trackLoader = new TrackLoader($"https://b.ppy.sh/preview/{BeatmapSet.OnlineBeatmapSetID}.mp3") + { + OnLoadComplete = d => + { + // we may have been replaced by another loader + if (trackLoader != d) return; + + Preview = (d as TrackLoader)?.Preview; + Playing.TriggerChange(); + }, + })); + } + + private class TrackLoader : Drawable + { + private readonly string preview; + + public Track Preview; + + public TrackLoader(string preview) + { + this.preview = preview; + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + if (!string.IsNullOrEmpty(preview)) + { + Preview = audio.Track.Get(preview); + Preview.Volume.Value = 0.5; + } + } + } + } +} diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index 5b5003b30f..14110724ea 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -32,6 +32,7 @@ namespace osu.Game.Overlays private readonly FillFlowContainer resultCountsContainer; private readonly OsuSpriteText resultCountsText; private FillFlowContainer panels; + private DirectPanel playing; protected override Color4 BackgroundColour => OsuColour.FromHex(@"485e74"); protected override Color4 TrianglesColourLight => OsuColour.FromHex(@"465b71"); @@ -201,6 +202,12 @@ namespace osu.Game.Overlays panels.FadeOut(200); panels.Expire(); panels = null; + + if (playing != null) + { + playing.PreviewPlaying.Value = false; + playing = null; + } } if (BeatmapSets == null) return; @@ -227,10 +234,21 @@ namespace osu.Game.Overlays { if (panels != null) ScrollFlow.Remove(panels); ScrollFlow.Add(panels = newPanels); + + foreach (DirectPanel panel in p.Children) + panel.PreviewPlaying.ValueChanged += newValue => + { + if (newValue) + { + if (playing != null && playing != panel) + playing.PreviewPlaying.Value = false; + playing = panel; + } + }; }); } - private GetBeatmapSetsRequest getSetsRequest; + private SearchBeatmapSetsRequest getSetsRequest; private readonly Bindable currentQuery = new Bindable(); @@ -251,7 +269,7 @@ namespace osu.Game.Overlays if (Header.Tabs.Current.Value == DirectTab.Search && (Filter.Search.Text == string.Empty || currentQuery == string.Empty)) return; - getSetsRequest = new GetBeatmapSetsRequest(currentQuery.Value ?? string.Empty, + getSetsRequest = new SearchBeatmapSetsRequest(currentQuery.Value ?? string.Empty, ((FilterControl)Filter).Ruleset.Value, Filter.DisplayStyleControl.Dropdown.Current.Value, Filter.Tabs.Current.Value); //todo: sort direction (?) diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index 880b607e78..776f2ffadb 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -159,7 +159,7 @@ namespace osu.Game.Overlays { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Both, }); } diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index 3ac8af7b2b..ea51471199 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -90,6 +90,7 @@ namespace osu.Game.Overlays.MedalSplash }, description = new TextFlowContainer { + TextAnchor = Anchor.TopCentre, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X, @@ -115,15 +116,16 @@ namespace osu.Game.Overlays.MedalSplash medalSprite.Texture = textures.Get(medal.ImageUrl); medalGlow.Texture = textures.Get(@"MedalSplash/medal-glow"); description.Colour = colours.BlueLight; - - unlocked.Position = new Vector2(0f, medalContainer.Size.Y / 2 + 10); - infoFlow.Position = new Vector2(0f, unlocked.Position.Y + 90); } protected override void LoadComplete() { base.LoadComplete(); + updateState(); + + unlocked.Position = new Vector2(0f, medalContainer.DrawSize.Y / 2 + 10); + infoFlow.Position = new Vector2(0f, unlocked.Position.Y + 90); } public DisplayState State @@ -172,6 +174,7 @@ namespace osu.Game.Overlays.MedalSplash this.ScaleTo(scale_when_full, duration, Easing.OutExpo); this.MoveToY(MedalOverlay.DISC_SIZE / 2 - 60, duration, Easing.OutExpo); + unlocked.Show(); name.FadeInFromZero(duration + 100); description.FadeInFromZero(duration * 2); break; diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index d6a86fe714..973769a114 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -12,13 +12,14 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Framework.Input; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Users; using System.Diagnostics; using System.Globalization; +using System.Collections.Generic; +using osu.Framework.Graphics.Cursor; namespace osu.Game.Overlays.Profile { @@ -119,15 +120,11 @@ namespace osu.Game.Overlays.Profile } } }, - new LinkFlowContainer.LinkText + new LinkFlowContainer.ProfileLink(user) { - Text = user.Username, - Url = $@"https://osu.ppy.sh/users/{user.Id}", - TextSize = 30, - Font = @"Exo2.0-RegularItalic", Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Y = -48 + Y = -48, }, countryFlag = new DrawableFlag(user.Country?.FlagName) { @@ -509,34 +506,42 @@ namespace osu.Game.Overlays.Profile public class LinkText : OsuSpriteText { - public override bool HandleInput => Url != null; + private readonly OsuHoverContainer content; - public string Url; + public override bool HandleInput => content.Action != null; - private Color4 hoverColour; + protected override Container Content => content ?? (Container)this; - protected override bool OnHover(InputState state) + protected override IEnumerable FlowingChildren => Children; + + public string Url { - this.FadeColour(hoverColour, 500, Easing.OutQuint); - return base.OnHover(state); + set + { + if(value != null) + content.Action = () => Process.Start(value); + } } - protected override void OnHoverLost(InputState state) + public LinkText() { - this.FadeColour(Color4.White, 500, Easing.OutQuint); - base.OnHoverLost(state); + AddInternal(content = new OsuHoverContainer + { + AutoSizeAxes = Axes.Both, + }); } + } - protected override bool OnClick(InputState state) - { - Process.Start(Url); - return true; - } + public class ProfileLink : LinkText, IHasTooltip + { + public string TooltipText => "View Profile in Browser"; - [BackgroundDependencyLoader] - private void load(OsuColour colours) + public ProfileLink(User user) { - hoverColour = colours.Yellow; + Text = user.Username; + Url = $@"https://osu.ppy.sh/users/{user.Id}"; + Font = @"Exo2.0-RegularItalic"; + TextSize = 30; } } } diff --git a/osu.Game/Overlays/Profile/ProfileSection.cs b/osu.Game/Overlays/Profile/ProfileSection.cs index 2b5084e321..df7c0e117f 100644 --- a/osu.Game/Overlays/Profile/ProfileSection.cs +++ b/osu.Game/Overlays/Profile/ProfileSection.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Users; using OpenTK.Graphics; namespace osu.Game.Overlays.Profile @@ -18,8 +19,17 @@ namespace osu.Game.Overlays.Profile public abstract string Identifier { get; } private readonly FillFlowContainer content; + protected override Container Content => content; + public virtual User User + { + get { return user; } + set { user = value; } + } + + private User user; + protected ProfileSection() { Direction = FillDirection.Vertical; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs new file mode 100644 index 0000000000..52b68e7b30 --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs @@ -0,0 +1,164 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Select.Leaderboards; +using System.Linq; +using osu.Framework.Localisation; +using System.Globalization; +using osu.Game.Rulesets.Scoring; +using System; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Overlays.Profile.Sections.Ranks +{ + public class DrawableScore : Container + { + private readonly FillFlowContainer stats; + private readonly FillFlowContainer metadata; + private readonly ModContainer modContainer; + private readonly Score score; + private readonly double? weight; + + public DrawableScore(Score score, double? weight = null) + { + this.score = score; + this.weight = weight; + + Children = new Drawable[] + { + new DrawableRank(score.Rank) + { + RelativeSizeAxes = Axes.Y, + Width = 60, + FillMode = FillMode.Fit, + }, + stats = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Direction = FillDirection.Vertical, + }, + metadata = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Left = 70 }, + Direction = FillDirection.Vertical, + Child = new OsuSpriteText + { + Text = score.Date.LocalDateTime.ToShortDateString(), + TextSize = 11, + Colour = OsuColour.Gray(0xAA), + Depth = -1, + }, + }, + modContainer = new ModContainer + { + AutoSizeAxes = Axes.Y, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Width = 60, + Margin = new MarginPadding { Right = 150 } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colour, LocalisationEngine locale, BeatmapSetOverlay beatmapSetOverlay) + { + stats.Add(new OsuSpriteText + { + Text = $"{Math.Round(score.PP ?? 0)}pp", + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + TextSize = 18, + Font = "Exo2.0-BoldItalic", + }); + + if (weight.HasValue) + { + stats.Add(new OsuSpriteText + { + Text = $"weighted: {Math.Round(score.PP * weight ?? 0)}pp ({weight.Value.ToString("0%", CultureInfo.CurrentCulture)})", + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Colour = colour.GrayA, + TextSize = 11, + Font = "Exo2.0-RegularItalic", + }); + } + + stats.Add(new OsuSpriteText + { + Text = "accuracy: " + score.Accuracy.ToString("0.00%"), + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Colour = colour.GrayA, + TextSize = 11, + Font = "Exo2.0-RegularItalic", + }); + + metadata.Add(new OsuHoverContainer + { + AutoSizeAxes = Axes.Both, + Action = () => + { + if (score.Beatmap.OnlineBeatmapSetID.HasValue) beatmapSetOverlay.ShowBeatmapSet(score.Beatmap.OnlineBeatmapSetID.Value); + }, + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new OsuSpriteText + { + Current = locale.GetUnicodePreference( + $"{score.Beatmap.Metadata.TitleUnicode ?? score.Beatmap.Metadata.Title} [{score.Beatmap.Version}] ", + $"{score.Beatmap.Metadata.Title ?? score.Beatmap.Metadata.TitleUnicode} [{score.Beatmap.Version}] " + ), + TextSize = 15, + Font = "Exo2.0-SemiBoldItalic", + }, + new OsuSpriteText + { + Current = locale.GetUnicodePreference(score.Beatmap.Metadata.ArtistUnicode, score.Beatmap.Metadata.Artist), + TextSize = 12, + Padding = new MarginPadding { Top = 3 }, + Font = "Exo2.0-RegularItalic", + }, + }, + }, + }); + + foreach (Mod mod in score.Mods) + modContainer.Add(new ModIcon(mod) + { + AutoSizeAxes = Axes.Both, + Scale = new Vector2(0.5f), + }); + } + + private class ModContainer : FlowContainer + { + protected override IEnumerable ComputeLayoutPositions() + { + int count = FlowingChildren.Count(); + for (int i = 0; i < count; i++) + yield return new Vector2(DrawWidth * i * (count == 1 ? 0 : 1f / (count - 1)), 0); + } + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index 5ea135fcac..d7df239003 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -1,6 +1,21 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays.Profile.Sections.Ranks; +using System; +using System.Linq; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Rulesets; +using osu.Game.Users; +using osu.Game.Graphics.UserInterface; +using OpenTK; +using osu.Framework.Allocation; + namespace osu.Game.Overlays.Profile.Sections { public class RanksSection : ProfileSection @@ -8,5 +23,148 @@ namespace osu.Game.Overlays.Profile.Sections public override string Title => "Ranks"; public override string Identifier => "top_ranks"; + + private readonly ScoreContainer best, first; + + public RanksSection() + { + Children = new Drawable[] + { + best = new ScoreContainer(ScoreType.Best, "Best Performance", true), + first = new ScoreContainer(ScoreType.Firsts, "First Place Ranks"), + }; + } + + public override User User + { + get + { + return base.User; + } + + set + { + base.User = value; + best.User = value; + first.User = value; + } + } + + private class ScoreContainer : FillFlowContainer + { + private readonly FillFlowContainer scoreContainer; + private readonly OsuSpriteText missing; + private readonly OsuHoverContainer showMoreButton; + private readonly LoadingAnimation showMoreLoading; + + private readonly ScoreType type; + private int visiblePages; + private User user; + private readonly bool includeWeigth; + + private RulesetStore rulesets; + private APIAccess api; + + public User User + { + set + { + user = value; + visiblePages = 0; + scoreContainer.Clear(); + showMoreButton.Hide(); + missing.Show(); + showMore(); + } + } + + public ScoreContainer(ScoreType type, string header, bool includeWeigth = false) + { + this.type = type; + this.includeWeigth = includeWeigth; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + + Children = new Drawable[] + { + new OsuSpriteText + { + TextSize = 15, + Text = header, + Font = "Exo2.0-RegularItalic", + Margin = new MarginPadding { Top = 10, Bottom = 10 }, + }, + scoreContainer = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + }, + showMoreButton = new OsuHoverContainer + { + Alpha = 0, + Action = showMore, + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Child = new OsuSpriteText + { + TextSize = 14, + Text = "show more", + } + }, + showMoreLoading = new LoadingAnimation + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Size = new Vector2(14), + }, + missing = new OsuSpriteText + { + TextSize = 14, + Text = "No awesome performance records yet. :(", + }, + }; + } + + [BackgroundDependencyLoader] + private void load(APIAccess api, RulesetStore rulesets) + { + this.api = api; + this.rulesets = rulesets; + } + + private void showMore() + { + var req = new GetUserScoresRequest(user.Id, type, visiblePages++ * 5); + + showMoreLoading.Show(); + showMoreButton.Hide(); + + req.Success += scores => + { + foreach (var s in scores) + s.ApplyRuleset(rulesets.GetRuleset(s.OnlineRulesetID)); + + showMoreButton.FadeTo(scores.Count == 5 ? 1 : 0); + showMoreLoading.Hide(); + + if (scores.Any()) + { + missing.Hide(); + foreach (OnlineScore score in scores) + scoreContainer.Add(new DrawableScore(score, includeWeigth ? Math.Pow(0.95, scoreContainer.Count) : -1) + { + RelativeSizeAxes = Axes.X, + Height = 60, + }); + } + }; + + Schedule(() => { api.Queue(req); }); + } + } } } diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 088b0a1335..e6c45f6826 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -92,12 +92,12 @@ namespace osu.Game.Overlays sections = new ProfileSection[] { new AboutSection(), - new RecentSection(), + //new RecentSection(), new RanksSection(), - new MedalsSection(), - new HistoricalSection(), - new BeatmapsSection(), - new KudosuSection() + //new MedalsSection(), + //new HistoricalSection(), + //new BeatmapsSection(), + //new KudosuSection() }; tabs = new ProfileTabControl { @@ -175,6 +175,8 @@ namespace osu.Game.Overlays var sec = sections.FirstOrDefault(s => s.Identifier == id); if (sec != null) { + sec.User = user; + sectionsContainer.Add(sec); tabs.AddItem(sec); } diff --git a/osu.Game/Rulesets/Objects/SliderCurve.cs b/osu.Game/Rulesets/Objects/SliderCurve.cs index 8bf85e498c..363c330d3d 100644 --- a/osu.Game/Rulesets/Objects/SliderCurve.cs +++ b/osu.Game/Rulesets/Objects/SliderCurve.cs @@ -200,4 +200,4 @@ namespace osu.Game.Rulesets.Objects return interpolateVertices(indexOfDistance(d), d) + Offset; } } -} \ No newline at end of file +} diff --git a/osu.Game/Rulesets/Scoring/Score.cs b/osu.Game/Rulesets/Scoring/Score.cs index c4ffa4e93c..6a06f364c6 100644 --- a/osu.Game/Rulesets/Scoring/Score.cs +++ b/osu.Game/Rulesets/Scoring/Score.cs @@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Scoring public double Health { get; set; } = 1; + public double? PP { get; set; } + public int MaxCombo { get; set; } public int Combo { get; set; } diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 47a39d4644..7f7068d341 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -6,13 +6,14 @@ using OpenTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using OpenTK; namespace osu.Game.Rulesets.UI { - public class ModIcon : Container + public class ModIcon : Container, IHasTooltip { private readonly SpriteIcon modIcon; private readonly SpriteIcon background; @@ -27,12 +28,16 @@ namespace osu.Game.Rulesets.UI private readonly ModType type; + public string TooltipText { get; } + public ModIcon(Mod mod) { if (mod == null) throw new ArgumentNullException(nameof(mod)); type = mod.Type; + TooltipText = mod.Name; + Children = new Drawable[] { background = new SpriteIcon diff --git a/osu.Game/Screens/Edit/Screens/Compose/Compose.cs b/osu.Game/Screens/Edit/Screens/Compose/Compose.cs index 2fe40dd010..2349c261cf 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Compose.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Compose.cs @@ -6,6 +6,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Screens.Edit.Screens.Compose.Timeline; namespace osu.Game.Screens.Edit.Screens.Compose { @@ -13,6 +14,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose { public Compose() { + ScrollableTimeline timeline; Children = new[] { new Container @@ -31,11 +33,22 @@ namespace osu.Game.Screens.Edit.Screens.Compose { Name = "Content", RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = 17, Vertical = 10 } + Padding = new MarginPadding { Horizontal = 17, Vertical = 10 }, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = 115 }, + Child = timeline = new ScrollableTimeline { RelativeSizeAxes = Axes.Both } + } + } } } } }; + + timeline.Beatmap.BindTo(Beatmap); } } } diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/BeatmapWaveformGraph.cs b/osu.Game/Screens/Edit/Screens/Compose/Timeline/BeatmapWaveformGraph.cs new file mode 100644 index 0000000000..5acee675e8 --- /dev/null +++ b/osu.Game/Screens/Edit/Screens/Compose/Timeline/BeatmapWaveformGraph.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Audio; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; + +namespace osu.Game.Screens.Edit.Screens.Compose.Timeline +{ + public class BeatmapWaveformGraph : CompositeDrawable + { + public readonly Bindable Beatmap = new Bindable(); + + private readonly WaveformGraph graph; + + public BeatmapWaveformGraph() + { + InternalChild = graph = new WaveformGraph { RelativeSizeAxes = Axes.Both }; + Beatmap.ValueChanged += b => graph.Waveform = b.Waveform; + } + + /// + /// Gets or sets the . + /// + public float Resolution + { + get { return graph.Resolution; } + set { graph.Resolution = value; } + } + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollableTimeline.cs b/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollableTimeline.cs new file mode 100644 index 0000000000..81beb4a4de --- /dev/null +++ b/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollableTimeline.cs @@ -0,0 +1,131 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Edit.Screens.Compose.Timeline +{ + public class ScrollableTimeline : CompositeDrawable + { + public readonly Bindable Beatmap = new Bindable(); + + private readonly ScrollingTimelineContainer timelineContainer; + + public ScrollableTimeline() + { + Masking = true; + CornerRadius = 5; + + OsuCheckbox hitObjectsCheckbox; + OsuCheckbox hitSoundsCheckbox; + OsuCheckbox waveformCheckbox; + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex("111") + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new Container + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex("222") + }, + new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Y, + Width = 160, + Padding = new MarginPadding { Horizontal = 15 }, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 4), + Children = new[] + { + hitObjectsCheckbox = new OsuCheckbox { LabelText = "Hitobjects" }, + hitSoundsCheckbox = new OsuCheckbox { LabelText = "Hitsounds" }, + waveformCheckbox = new OsuCheckbox { LabelText = "Waveform" } + } + } + } + }, + new Container + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex("333") + }, + new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Masking = true, + Children = new[] + { + new TimelineButton + { + RelativeSizeAxes = Axes.Y, + Height = 0.5f, + Icon = FontAwesome.fa_search_plus, + Action = () => timelineContainer.Zoom++ + }, + new TimelineButton + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Y, + Height = 0.5f, + Icon = FontAwesome.fa_search_minus, + Action = () => timelineContainer.Zoom-- + }, + } + } + } + }, + timelineContainer = new ScrollingTimelineContainer { RelativeSizeAxes = Axes.Y } + } + } + }; + + hitObjectsCheckbox.Current.Value = true; + hitSoundsCheckbox.Current.Value = true; + waveformCheckbox.Current.Value = true; + + timelineContainer.Beatmap.BindTo(Beatmap); + timelineContainer.WaveformVisible.BindTo(waveformCheckbox.Current); + } + + protected override void Update() + { + base.Update(); + + timelineContainer.Size = new Vector2(DrawSize.X - timelineContainer.DrawPosition.X, 1); + } + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollingTimelineContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollingTimelineContainer.cs new file mode 100644 index 0000000000..47a77090b2 --- /dev/null +++ b/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollingTimelineContainer.cs @@ -0,0 +1,141 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using OpenTK; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Game.Beatmaps; +using osu.Game.Graphics; + +namespace osu.Game.Screens.Edit.Screens.Compose.Timeline +{ + internal class ScrollingTimelineContainer : ScrollContainer + { + public readonly Bindable HitObjectsVisible = new Bindable(); + public readonly Bindable HitSoundsVisible = new Bindable(); + public readonly Bindable WaveformVisible = new Bindable(); + public readonly Bindable Beatmap = new Bindable(); + + private readonly BeatmapWaveformGraph waveform; + + public ScrollingTimelineContainer() + : base(Direction.Horizontal) + { + Masking = true; + + Add(waveform = new BeatmapWaveformGraph + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex("222"), + Depth = float.MaxValue + }); + + Content.AutoSizeAxes = Axes.None; + Content.RelativeSizeAxes = Axes.Both; + + waveform.Beatmap.BindTo(Beatmap); + WaveformVisible.ValueChanged += waveformVisibilityChanged; + + Zoom = 10; + } + + private float minZoom = 1; + /// + /// The minimum zoom level allowed. + /// + public float MinZoom + { + get { return minZoom; } + set + { + if (value <= 0) + throw new ArgumentOutOfRangeException(nameof(value)); + if (minZoom == value) + return; + minZoom = value; + + // Update the zoom level + Zoom = Zoom; + } + } + + private float maxZoom = 30; + /// + /// The maximum zoom level allowed. + /// + public float MaxZoom + { + get { return maxZoom; } + set + { + if (value <= 0) + throw new ArgumentOutOfRangeException(nameof(value)); + if (maxZoom == value) + return; + maxZoom = value; + + // Update the zoom level + Zoom = Zoom; + } + } + + private float zoom = 1; + /// + /// The current zoom level. + /// + public float Zoom + { + get { return zoom; } + set + { + value = MathHelper.Clamp(value, MinZoom, MaxZoom); + if (zoom == value) + return; + zoom = value; + + // Make the zoom target default to the center of the graph if it hasn't been set + if (relativeContentZoomTarget == null) + relativeContentZoomTarget = ToSpaceOfOtherDrawable(DrawSize / 2, Content).X / Content.DrawSize.X; + if (localZoomTarget == null) + localZoomTarget = DrawSize.X / 2; + + Content.ResizeWidthTo(Zoom); + + // Update the scroll position to focus on the zoom target + float scrollPos = Content.DrawSize.X * relativeContentZoomTarget.Value - localZoomTarget.Value; + ScrollTo(scrollPos, false); + + relativeContentZoomTarget = null; + localZoomTarget = null; + } + } + + /// + /// Zoom target as a relative position in the space. + /// + private float? relativeContentZoomTarget; + + /// + /// Zoom target as a position in our local space. + /// + private float? localZoomTarget; + + protected override bool OnWheel(InputState state) + { + if (!state.Keyboard.ControlPressed) + return base.OnWheel(state); + + relativeContentZoomTarget = Content.ToLocalSpace(state.Mouse.NativeState.Position).X / Content.DrawSize.X; + localZoomTarget = ToLocalSpace(state.Mouse.NativeState.Position).X; + + Zoom += state.Mouse.WheelDelta; + + return true; + } + + private void waveformVisibilityChanged(bool visible) => waveform.FadeTo(visible ? 1 : 0, 200, Easing.OutQuint); + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineButton.cs b/osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineButton.cs new file mode 100644 index 0000000000..0c6fc5d133 --- /dev/null +++ b/osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineButton.cs @@ -0,0 +1,52 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Edit.Screens.Compose.Timeline +{ + public class TimelineButton : CompositeDrawable + { + public Action Action; + public readonly BindableBool Enabled = new BindableBool(true); + + public FontAwesome Icon + { + get { return button.Icon; } + set { button.Icon = value; } + } + + private readonly IconButton button; + + public TimelineButton() + { + InternalChild = button = new IconButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + IconColour = OsuColour.Gray(0.35f), + IconHoverColour = Color4.White, + HoverColour = OsuColour.Gray(0.25f), + FlashColour = OsuColour.Gray(0.5f), + Action = () => Action?.Invoke() + }; + + button.Enabled.BindTo(Enabled); + Width = button.ButtonSize.X; + } + + protected override void Update() + { + base.Update(); + + button.ButtonSize = new Vector2(button.ButtonSize.X, DrawHeight); + } + } +} diff --git a/osu.Game/Tests/Visual/TestCaseEditorComposeTimeline.cs b/osu.Game/Tests/Visual/TestCaseEditorComposeTimeline.cs new file mode 100644 index 0000000000..02c32dfa56 --- /dev/null +++ b/osu.Game/Tests/Visual/TestCaseEditorComposeTimeline.cs @@ -0,0 +1,46 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; +using osu.Game.Screens.Edit.Screens.Compose.Timeline; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseEditorComposeTimeline : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(ScrollableTimeline), typeof(ScrollingTimelineContainer), typeof(BeatmapWaveformGraph), typeof(TimelineButton) }; + + private readonly ScrollableTimeline timeline; + + public TestCaseEditorComposeTimeline() + { + Children = new Drawable[] + { + new MusicController + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + State = Visibility.Visible + }, + timeline = new ScrollableTimeline + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1000, 100) + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuGameBase osuGame) + { + timeline.Beatmap.BindTo(osuGame.Beatmap); + } + } +} diff --git a/osu.Game/Tests/Visual/TestCaseIconButton.cs b/osu.Game/Tests/Visual/TestCaseIconButton.cs index bec8f8314b..acde9df4a9 100644 --- a/osu.Game/Tests/Visual/TestCaseIconButton.cs +++ b/osu.Game/Tests/Visual/TestCaseIconButton.cs @@ -25,14 +25,18 @@ namespace osu.Game.Tests.Visual Children = new[] { new NamedIconButton("No change", new IconButton()), - new NamedIconButton("Green colours", new IconButton + new NamedIconButton("Background colours", new IconButton { - IconColour = Color4.LightGreen, FlashColour = Color4.DarkGreen, - HoverColour = Color4.Green + HoverColour = Color4.Green, }), new NamedIconButton("Full-width", new IconButton { ButtonSize = new Vector2(200, 30) }), - new NamedIconButton("Unchanging size", new IconButton(), false) + new NamedIconButton("Unchanging size", new IconButton(), false), + new NamedIconButton("Icon colours", new IconButton + { + IconColour = Color4.Green, + IconHoverColour = Color4.Red + }) } }; } diff --git a/osu.Game/Tests/Visual/TestCaseMedalOverlay.cs b/osu.Game/Tests/Visual/TestCaseMedalOverlay.cs index fecf37538b..9a26eefd63 100644 --- a/osu.Game/Tests/Visual/TestCaseMedalOverlay.cs +++ b/osu.Game/Tests/Visual/TestCaseMedalOverlay.cs @@ -1,7 +1,10 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; +using System.Collections.Generic; using osu.Game.Overlays; +using osu.Game.Overlays.MedalSplash; using osu.Game.Users; namespace osu.Game.Tests.Visual @@ -10,6 +13,12 @@ namespace osu.Game.Tests.Visual { public override string Description => @"medal get!"; + public override IReadOnlyList RequiredTypes => new[] + { + typeof(MedalOverlay), + typeof(DrawableMedal), + }; + public TestCaseMedalOverlay() { AddStep(@"display", () => diff --git a/osu.Game/Tests/Visual/TestCaseUserRanks.cs b/osu.Game/Tests/Visual/TestCaseUserRanks.cs new file mode 100644 index 0000000000..9667897a7d --- /dev/null +++ b/osu.Game/Tests/Visual/TestCaseUserRanks.cs @@ -0,0 +1,47 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Overlays.Profile.Sections; +using osu.Game.Overlays.Profile.Sections.Ranks; +using osu.Game.Users; +using System; +using System.Collections.Generic; + +namespace osu.Game.Tests.Visual +{ + internal class TestCaseUserRanks : OsuTestCase + { + public override string Description => "showing your latest achievements"; + + public override IReadOnlyList RequiredTypes => new[] { typeof(DrawableScore), typeof(RanksSection) }; + + public TestCaseUserRanks() + { + RanksSection ranks; + + Add(new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.2f) + }, + new ScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = ranks = new RanksSection(), + }, + } + }); + + AddStep("Show cookiezi", () => ranks.User = new User { Id = 124493 }); + } + } +} diff --git a/osu.Game/Tests/Visual/TestCaseWaveform.cs b/osu.Game/Tests/Visual/TestCaseWaveform.cs new file mode 100644 index 0000000000..fc21b86c5d --- /dev/null +++ b/osu.Game/Tests/Visual/TestCaseWaveform.cs @@ -0,0 +1,88 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; +using osu.Game.Screens.Edit.Screens.Compose.Timeline; + +namespace osu.Game.Tests.Visual +{ + internal class TestCaseWaveform : OsuTestCase + { + private readonly Bindable beatmapBacking = new Bindable(); + + public TestCaseWaveform() + { + FillFlowContainer flow; + Child = flow = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + new MusicController + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Y = 100, + State = Visibility.Visible + }, + } + }; + + for (int i = 1; i <= 16; i *= 2) + { + var newDisplay = new BeatmapWaveformGraph + { + RelativeSizeAxes = Axes.Both, + Resolution = 1f / i + }; + + newDisplay.Beatmap.BindTo(beatmapBacking); + + flow.Add(new Container + { + RelativeSizeAxes = Axes.X, + Height = 100, + Children = new Drawable[] + { + newDisplay, + new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.75f + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = $"Resolution: {1f / i:0.00}" + } + } + } + } + }); + } + } + + [BackgroundDependencyLoader] + private void load(OsuGameBase osuGame) => beatmapBacking.BindTo(osuGame.Beatmap); + } +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 81f360f640..ebedf32da0 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -57,7 +57,6 @@ false AnyCPU true - AllRules.ruleset false false false @@ -77,7 +76,6 @@ false AnyCPU true - AllRules.ruleset false false @@ -103,7 +101,6 @@ false 6 prompt - AllRules.ruleset --tests false @@ -266,6 +263,9 @@ + + + @@ -274,6 +274,8 @@ + + @@ -302,6 +304,7 @@ + @@ -312,7 +315,6 @@ - @@ -370,7 +372,7 @@ - + @@ -395,6 +397,7 @@ + @@ -439,6 +442,7 @@ + @@ -629,6 +633,8 @@ + + @@ -755,6 +761,7 @@ + @@ -788,6 +795,8 @@ + +