diff --git a/osu-framework b/osu-framework index 06e426da03..34c9f17a6a 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 06e426da039f7bb54aedf5d82c21b8d858a0310e +Subproject commit 34c9f17a6ac6fa5e9fd5569f9e119331316c1313 diff --git a/osu.Desktop.VisualTests/Tests/TestCaseBeatmapDetailArea.cs b/osu.Desktop.VisualTests/Tests/TestCaseBeatmapDetailArea.cs new file mode 100644 index 0000000000..bb7df19202 --- /dev/null +++ b/osu.Desktop.VisualTests/Tests/TestCaseBeatmapDetailArea.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 OpenTK; +using osu.Framework.Graphics; +using osu.Framework.Screens.Testing; +using osu.Game.Screens.Select; + +namespace osu.Desktop.VisualTests.Tests +{ + internal class TestCaseBeatmapDetailArea : TestCase + { + public override string Description => @"Beatmap details in song select"; + + public override void Reset() + { + base.Reset(); + + Add(new BeatmapDetailArea + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(550f, 450f), + }); + } + } +} \ No newline at end of file diff --git a/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs b/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs new file mode 100644 index 0000000000..3087b90e36 --- /dev/null +++ b/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs @@ -0,0 +1,77 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.MathUtils; +using osu.Framework.Screens.Testing; +using osu.Game.Modes.Objects.Drawables; +using osu.Game.Modes.Taiko.Judgements; +using osu.Game.Modes.Taiko.Objects; +using osu.Game.Modes.Taiko.UI; + +namespace osu.Desktop.VisualTests.Tests +{ + internal class TestCaseTaikoPlayfield : TestCase + { + public override string Description => "Taiko playfield"; + + private TaikoPlayfield playfield; + + public override void Reset() + { + base.Reset(); + + AddButton("Hit!", addHitJudgement); + AddButton("Miss :(", addMissJudgement); + + Add(playfield = new TaikoPlayfield + { + Y = 200 + }); + } + + private void addHitJudgement() + { + TaikoHitResult hitResult = RNG.Next(2) == 0 ? TaikoHitResult.Good : TaikoHitResult.Great; + + playfield.OnJudgement(new DrawableTestHit(new Hit()) + { + X = RNG.NextSingle(hitResult == TaikoHitResult.Good ? -0.1f : -0.05f, hitResult == TaikoHitResult.Good ? 0.1f : 0.05f), + Judgement = new TaikoJudgementInfo + { + Result = HitResult.Hit, + TaikoResult = hitResult, + TimeOffset = 0, + ComboAtHit = 1, + SecondHit = RNG.Next(10) == 0 + } + }); + } + + private void addMissJudgement() + { + playfield.OnJudgement(new DrawableTestHit(new Hit()) + { + Judgement = new TaikoJudgementInfo + { + Result = HitResult.Miss, + TimeOffset = 0, + ComboAtHit = 0 + } + }); + } + + private class DrawableTestHit : DrawableHitObject + { + public DrawableTestHit(TaikoHitObject hitObject) + : base(hitObject) + { + } + + protected override TaikoJudgementInfo CreateJudgementInfo() => new TaikoJudgementInfo(); + + protected override void UpdateState(ArmedState state) + { + } + } + } +} diff --git a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj index b67b4c4bb3..e63306300d 100644 --- a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj +++ b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj @@ -23,7 +23,7 @@ false LocalIntranet v4.5 - true + true publish\ true Disk @@ -194,6 +194,7 @@ + @@ -206,6 +207,7 @@ + diff --git a/osu.Game.Modes.Catch/Judgements/CatchJudgementInfo.cs b/osu.Game.Modes.Catch/Judgements/CatchJudgementInfo.cs index 33e84d2f97..53e0c6c0bf 100644 --- a/osu.Game.Modes.Catch/Judgements/CatchJudgementInfo.cs +++ b/osu.Game.Modes.Catch/Judgements/CatchJudgementInfo.cs @@ -7,5 +7,8 @@ namespace osu.Game.Modes.Catch.Judgements { public class CatchJudgementInfo : JudgementInfo { + public override string ScoreString => string.Empty; + + public override string MaxScoreString => string.Empty; } } diff --git a/osu.Game.Modes.Mania/Judgements/ManiaJudgementInfo.cs b/osu.Game.Modes.Mania/Judgements/ManiaJudgementInfo.cs index a75f95abe7..c65bd87b6b 100644 --- a/osu.Game.Modes.Mania/Judgements/ManiaJudgementInfo.cs +++ b/osu.Game.Modes.Mania/Judgements/ManiaJudgementInfo.cs @@ -7,5 +7,8 @@ namespace osu.Game.Modes.Mania.Judgements { public class ManiaJudgementInfo : JudgementInfo { + public override string ScoreString => string.Empty; + + public override string MaxScoreString => string.Empty; } } diff --git a/osu.Game.Modes.Osu/Judgements/OsuJudgementInfo.cs b/osu.Game.Modes.Osu/Judgements/OsuJudgementInfo.cs index 20d36efe55..b945bad8a1 100644 --- a/osu.Game.Modes.Osu/Judgements/OsuJudgementInfo.cs +++ b/osu.Game.Modes.Osu/Judgements/OsuJudgementInfo.cs @@ -4,6 +4,7 @@ using OpenTK; using osu.Game.Modes.Judgements; using osu.Game.Modes.Osu.Objects.Drawables; +using osu.Framework.Extensions; namespace osu.Game.Modes.Osu.Judgements { @@ -24,6 +25,10 @@ namespace osu.Game.Modes.Osu.Judgements /// public OsuScoreResult MaxScore = OsuScoreResult.Hit300; + public override string ScoreString => Score.GetDescription(); + + public override string MaxScoreString => MaxScore.GetDescription(); + public int ScoreValue => scoreToInt(Score); public int MaxScoreValue => scoreToInt(MaxScore); diff --git a/osu.Game.Modes.Osu/Objects/Drawables/DrawableOsuJudgementInfo.cs b/osu.Game.Modes.Osu/Objects/Drawables/DrawableOsuJudgementInfo.cs new file mode 100644 index 0000000000..15832bcb75 --- /dev/null +++ b/osu.Game.Modes.Osu/Objects/Drawables/DrawableOsuJudgementInfo.cs @@ -0,0 +1,31 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics.Transforms; +using osu.Game.Modes.Objects.Drawables; +using osu.Game.Modes.Osu.Judgements; +using OpenTK; +using osu.Game.Modes.Judgements; + +namespace osu.Game.Modes.Osu.Objects.Drawables +{ + public class DrawableOsuJudgementInfo : DrawableJudgementInfo + { + public DrawableOsuJudgementInfo(OsuJudgementInfo judgement) : base(judgement) + { + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (Judgement.Result != HitResult.Miss) + { + JudgementText.TransformSpacingTo(new Vector2(14, 0), 1800, EasingTypes.OutQuint); + FadeOut(500); + } + + Expire(); + } + } +} \ No newline at end of file diff --git a/osu.Game.Modes.Osu/Objects/Drawables/HitExplosion.cs b/osu.Game.Modes.Osu/Objects/Drawables/HitExplosion.cs deleted file mode 100644 index f2ccf554d9..0000000000 --- a/osu.Game.Modes.Osu/Objects/Drawables/HitExplosion.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Transforms; -using osu.Game.Graphics.Sprites; -using osu.Game.Modes.Objects.Drawables; -using osu.Game.Modes.Osu.Judgements; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Modes.Osu.Objects.Drawables -{ - public class HitExplosion : FillFlowContainer - { - private readonly OsuJudgementInfo judgement; - private readonly SpriteText line1; - private readonly SpriteText line2; - - public HitExplosion(OsuJudgementInfo judgement, OsuHitObject h = null) - { - this.judgement = judgement; - AutoSizeAxes = Axes.Both; - Origin = Anchor.Centre; - - Direction = FillDirection.Vertical; - Spacing = new Vector2(0, 2); - Position = (h?.StackedEndPosition ?? Vector2.Zero) + judgement.PositionOffset; - - Children = new Drawable[] - { - line1 = new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = judgement.Score.GetDescription(), - Font = @"Venera", - TextSize = 16, - }, - line2 = new OsuSpriteText - { - Text = judgement.Combo.GetDescription(), - Font = @"Venera", - TextSize = 11, - } - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - if (judgement.Result == HitResult.Miss) - { - FadeInFromZero(60); - - ScaleTo(1.6f); - ScaleTo(1, 100, EasingTypes.In); - - MoveToOffset(new Vector2(0, 100), 800, EasingTypes.InQuint); - RotateTo(40, 800, EasingTypes.InQuint); - - Delay(600); - FadeOut(200); - } - else - { - line1.TransformSpacingTo(new Vector2(14, 0), 1800, EasingTypes.OutQuint); - line2.TransformSpacingTo(new Vector2(14, 0), 1800, EasingTypes.OutQuint); - FadeOut(500); - } - - switch (judgement.Result) - { - case HitResult.Miss: - Colour = Color4.Red; - break; - } - - Expire(); - } - } -} \ No newline at end of file diff --git a/osu.Game.Modes.Osu/UI/OsuPlayfield.cs b/osu.Game.Modes.Osu/UI/OsuPlayfield.cs index 87c80f94bc..8924fe71e7 100644 --- a/osu.Game.Modes.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Modes.Osu/UI/OsuPlayfield.cs @@ -85,7 +85,11 @@ namespace osu.Game.Modes.Osu.UI public override void OnJudgement(DrawableHitObject judgedObject) { - HitExplosion explosion = new HitExplosion(judgedObject.Judgement, judgedObject.HitObject); + DrawableOsuJudgementInfo explosion = new DrawableOsuJudgementInfo(judgedObject.Judgement) + { + Origin = Anchor.Centre, + Position = judgedObject.HitObject.StackedEndPosition + judgedObject.Judgement.PositionOffset + }; judgementLayer.Add(explosion); } diff --git a/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj b/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj index 12135a38fb..1c1add8b94 100644 --- a/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj +++ b/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj @@ -58,7 +58,7 @@ - + diff --git a/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs index d78c347f22..0606ee4d5a 100644 --- a/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -2,18 +2,74 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Legacy; +using osu.Game.Modes.Objects; +using osu.Game.Modes.Objects.Types; using osu.Game.Modes.Taiko.Objects; using System.Collections.Generic; +using System.Linq; namespace osu.Game.Modes.Taiko.Beatmaps { internal class TaikoBeatmapConverter : IBeatmapConverter { + private const float legacy_velocity_scale = 1.4f; + private const float bash_convert_factor = 1.65f; + public Beatmap Convert(Beatmap original) { + if (original is LegacyBeatmap) + original.TimingInfo.ControlPoints.ForEach(c => c.VelocityAdjustment /= legacy_velocity_scale); + return new Beatmap(original) { - HitObjects = new List() // Todo: Implement + HitObjects = convertHitObjects(original.HitObjects) + }; + } + + private List convertHitObjects(List hitObjects) + { + return hitObjects.Select(convertHitObject).ToList(); + } + + private TaikoHitObject convertHitObject(HitObject original) + { + // Check if this HitObject is already a TaikoHitObject, and return it if so + TaikoHitObject originalTaiko = original as TaikoHitObject; + if (originalTaiko != null) + return originalTaiko; + + IHasDistance distanceData = original as IHasDistance; + IHasRepeats repeatsData = original as IHasRepeats; + IHasEndTime endTimeData = original as IHasEndTime; + + if (distanceData != null) + { + return new DrumRoll + { + StartTime = original.StartTime, + Sample = original.Sample, + + Distance = distanceData.Distance * (repeatsData?.RepeatCount ?? 1) + }; + } + + if (endTimeData != null) + { + // We compute the end time manually to add in the Bash convert factor + return new Bash + { + StartTime = original.StartTime, + Sample = original.Sample, + + EndTime = original.StartTime + endTimeData.Duration * bash_convert_factor + }; + } + + return new Hit + { + StartTime = original.StartTime, + Sample = original.Sample, }; } } diff --git a/osu.Game.Modes.Taiko/Judgements/TaikoHitResult.cs b/osu.Game.Modes.Taiko/Judgements/TaikoHitResult.cs index d425616b66..cbc3919c4f 100644 --- a/osu.Game.Modes.Taiko/Judgements/TaikoHitResult.cs +++ b/osu.Game.Modes.Taiko/Judgements/TaikoHitResult.cs @@ -1,11 +1,15 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.ComponentModel; + namespace osu.Game.Modes.Taiko.Judgements { public enum TaikoHitResult { + [Description("GOOD")] Good, + [Description("GREAT")] Great } } diff --git a/osu.Game.Modes.Taiko/Judgements/TaikoJudgementInfo.cs b/osu.Game.Modes.Taiko/Judgements/TaikoJudgementInfo.cs index d9e81d4d77..3312661e2a 100644 --- a/osu.Game.Modes.Taiko/Judgements/TaikoJudgementInfo.cs +++ b/osu.Game.Modes.Taiko/Judgements/TaikoJudgementInfo.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Modes.Judgements; +using osu.Framework.Extensions; namespace osu.Game.Modes.Taiko.Judgements { @@ -37,6 +38,10 @@ namespace osu.Game.Modes.Taiko.Judgements /// public int MaxAccuracyScoreValue => NumericResultForAccuracy(MAX_HIT_RESULT); + public override string ScoreString => TaikoResult.GetDescription(); + + public override string MaxScoreString => MAX_HIT_RESULT.GetDescription(); + /// /// Whether this Judgement has a secondary hit in the case of finishers. /// diff --git a/osu.Game.Modes.Taiko/Objects/Hit.cs b/osu.Game.Modes.Taiko/Objects/Hit.cs new file mode 100644 index 0000000000..ad8d07d901 --- /dev/null +++ b/osu.Game.Modes.Taiko/Objects/Hit.cs @@ -0,0 +1,35 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps.Timing; +using osu.Game.Database; + +namespace osu.Game.Modes.Taiko.Objects +{ + public class Hit : TaikoHitObject + { + /// + /// The hit window that results in a "GREAT" hit. + /// + public double HitWindowGreat = 35; + + /// + /// The hit window that results in a "GOOD" hit. + /// + public double HitWindowGood = 80; + + /// + /// The hit window that results in a "MISS". + /// + public double HitWindowMiss = 95; + + public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty) + { + base.ApplyDefaults(timing, difficulty); + + HitWindowGreat = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 50, 35, 20); + HitWindowGood = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 120, 80, 50); + HitWindowMiss = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 135, 95, 70); + } + } +} diff --git a/osu.Game.Modes.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Modes.Taiko/Objects/TaikoHitObject.cs index 61d8ed5f01..4505065489 100644 --- a/osu.Game.Modes.Taiko/Objects/TaikoHitObject.cs +++ b/osu.Game.Modes.Taiko/Objects/TaikoHitObject.cs @@ -7,28 +7,13 @@ using osu.Game.Modes.Objects; namespace osu.Game.Modes.Taiko.Objects { - public class TaikoHitObject : HitObject + public abstract class TaikoHitObject : HitObject { /// /// HitCircle radius. /// public const float CIRCLE_RADIUS = 64; - /// - /// The hit window that results in a "GREAT" hit. - /// - public double HitWindowGreat = 35; - - /// - /// The hit window that results in a "GOOD" hit. - /// - public double HitWindowGood = 80; - - /// - /// The hit window that results in a "MISS". - /// - public double HitWindowMiss = 95; - /// /// The time to scroll in the HitObject. /// @@ -37,7 +22,7 @@ namespace osu.Game.Modes.Taiko.Objects /// /// Whether this HitObject is in Kiai time. /// - public bool Kiai; + public bool Kiai { get; protected set; } public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty) { @@ -50,10 +35,6 @@ namespace osu.Game.Modes.Taiko.Objects if (overridePoint != null) Kiai |= overridePoint.KiaiMode; - - HitWindowGreat = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 50, 35, 20); - HitWindowGood = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 120, 80, 50); - HitWindowMiss = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 135, 95, 70); } } } \ No newline at end of file diff --git a/osu.Game.Modes.Taiko/UI/DrawableTaikoJudgementInfo.cs b/osu.Game.Modes.Taiko/UI/DrawableTaikoJudgementInfo.cs new file mode 100644 index 0000000000..87f321d557 --- /dev/null +++ b/osu.Game.Modes.Taiko/UI/DrawableTaikoJudgementInfo.cs @@ -0,0 +1,57 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Modes.Taiko.Judgements; +using osu.Game.Modes.Objects.Drawables; +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Modes.Judgements; + +namespace osu.Game.Modes.Taiko.UI +{ + /// + /// Text that is shown as judgement when a hit object is hit or missed. + /// + public class DrawableTaikoJudgementInfo : DrawableJudgementInfo + { + /// + /// Creates a new judgement text. + /// + /// The judgement to visualise. + public DrawableTaikoJudgementInfo(TaikoJudgementInfo judgement) + : base(judgement) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + switch (Judgement.Result) + { + case HitResult.Hit: + switch (Judgement.TaikoResult) + { + case TaikoHitResult.Good: + Colour = colours.GreenLight; + break; + case TaikoHitResult.Great: + Colour = colours.BlueLight; + break; + } + break; + } + } + + protected override void LoadComplete() + { + switch (Judgement.Result) + { + case HitResult.Hit: + MoveToY(-100, 500); + break; + } + + base.LoadComplete(); + } + } +} \ No newline at end of file diff --git a/osu.Game.Modes.Taiko/UI/HitExplosion.cs b/osu.Game.Modes.Taiko/UI/HitExplosion.cs new file mode 100644 index 0000000000..3aa7977617 --- /dev/null +++ b/osu.Game.Modes.Taiko/UI/HitExplosion.cs @@ -0,0 +1,78 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Transforms; +using osu.Game.Graphics; +using osu.Game.Modes.Taiko.Judgements; +using osu.Game.Modes.Taiko.Objects; + +namespace osu.Game.Modes.Taiko.UI +{ + /// + /// A circle explodes from the hit target to indicate a hitobject has been hit. + /// + internal class HitExplosion : CircularContainer + { + private readonly TaikoJudgementInfo judgement; + private readonly Box innerFill; + + public HitExplosion(TaikoJudgementInfo judgement) + { + this.judgement = judgement; + + Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2); + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + RelativePositionAxes = Axes.Both; + + BorderColour = Color4.White; + BorderThickness = 1; + + Alpha = 0.15f; + Masking = true; + + Children = new[] + { + innerFill = new Box + { + RelativeSizeAxes = Axes.Both, + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + if (judgement.SecondHit) + Size *= 1.5f; + + switch (judgement.TaikoResult) + { + case TaikoHitResult.Good: + innerFill.Colour = colours.Green; + break; + case TaikoHitResult.Great: + innerFill.Colour = colours.Blue; + break; + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ScaleTo(5f, 1000, EasingTypes.OutQuint); + FadeOut(500); + + Expire(); + } + } +} diff --git a/osu.Game.Modes.Taiko/UI/HitTarget.cs b/osu.Game.Modes.Taiko/UI/HitTarget.cs new file mode 100644 index 0000000000..d38af3390e --- /dev/null +++ b/osu.Game.Modes.Taiko/UI/HitTarget.cs @@ -0,0 +1,105 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Modes.Taiko.Objects; + +namespace osu.Game.Modes.Taiko.UI +{ + /// + /// A component that is displayed at the hit position in the taiko playfield. + /// + internal class HitTarget : Container + { + /// + /// Diameter of normal hit object circles. + /// + private const float normal_diameter = TaikoHitObject.CIRCLE_RADIUS * 2 * TaikoPlayfield.PLAYFIELD_SCALE; + + /// + /// Diameter of finisher hit object circles. + /// + private const float finisher_diameter = normal_diameter * 1.5f; + + /// + /// The 1px inner border of the taiko playfield. + /// + private const float border_offset = 1; + + /// + /// Thickness of all drawn line pieces. + /// + private const float border_thickness = 2.5f; + + public HitTarget() + { + RelativeSizeAxes = Axes.Y; + + Children = new Drawable[] + { + new Box + { + Name = "Bar Upper", + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Y = border_offset, + Size = new Vector2(border_thickness, (TaikoPlayfield.PlayfieldHeight - finisher_diameter) / 2f - border_offset), + Alpha = 0.1f + }, + new CircularContainer + { + Name = "Finisher Ring", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(finisher_diameter), + Masking = true, + BorderColour = Color4.White, + BorderThickness = border_thickness, + Alpha = 0.1f, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + } + }, + new CircularContainer + { + Name = "Normal Ring", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(normal_diameter), + Masking = true, + BorderColour = Color4.White, + BorderThickness = border_thickness, + Alpha = 0.5f, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + } + }, + new Box + { + Name = "Bar Lower", + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Y = -border_offset, + Size = new Vector2(border_thickness, (TaikoPlayfield.PlayfieldHeight - finisher_diameter) / 2f - border_offset), + Alpha = 0.1f + }, + }; + } + } +} diff --git a/osu.Game.Modes.Taiko/UI/InputDrum.cs b/osu.Game.Modes.Taiko/UI/InputDrum.cs new file mode 100644 index 0000000000..1787670c7a --- /dev/null +++ b/osu.Game.Modes.Taiko/UI/InputDrum.cs @@ -0,0 +1,149 @@ +// 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.Input; +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.Framework.Graphics.Transforms; +using osu.Framework.Input; +using osu.Game.Graphics; + +namespace osu.Game.Modes.Taiko.UI +{ + /// + /// A component of the playfield that captures input and displays input as a drum. + /// + internal class InputDrum : Container + { + public InputDrum() + { + Size = new Vector2(TaikoPlayfield.PlayfieldHeight); + + const float middle_split = 10; + + Children = new Drawable[] + { + new TaikoHalfDrum(false) + { + Name = "Left Half", + Anchor = Anchor.Centre, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Both, + X = -middle_split / 2, + RimKey = Key.D, + CentreKey = Key.F + }, + new TaikoHalfDrum(true) + { + Name = "Right Half", + Anchor = Anchor.Centre, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + X = middle_split / 2, + Position = new Vector2(-1f, 0), + RimKey = Key.K, + CentreKey = Key.J + } + }; + } + + /// + /// A half-drum. Contains one centre and one rim hit. + /// + private class TaikoHalfDrum : Container + { + /// + /// The key to be used for the rim of the half-drum. + /// + public Key RimKey; + + /// + /// The key to be used for the centre of the half-drum. + /// + public Key CentreKey; + + private readonly Sprite rim; + private readonly Sprite rimHit; + private readonly Sprite centre; + private readonly Sprite centreHit; + + public TaikoHalfDrum(bool flipped) + { + Masking = true; + + Children = new Drawable[] + { + rim = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both + }, + rimHit = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Alpha = 0, + BlendingMode = BlendingMode.Additive, + }, + centre = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.7f) + }, + centreHit = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.7f), + Alpha = 0, + BlendingMode = BlendingMode.Additive + } + }; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures, OsuColour colours) + { + rim.Texture = textures.Get(@"Play/Taiko/taiko-drum-outer"); + rimHit.Texture = textures.Get(@"Play/Taiko/taiko-drum-outer-hit"); + centre.Texture = textures.Get(@"Play/Taiko/taiko-drum-inner"); + centreHit.Texture = textures.Get(@"Play/Taiko/taiko-drum-inner-hit"); + + rimHit.Colour = colours.Blue; + centreHit.Colour = colours.Pink; + } + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + if (args.Repeat) + return false; + + Drawable target = null; + + if (args.Key == CentreKey) + target = centreHit; + else if (args.Key == RimKey) + target = rimHit; + + if (target != null) + { + target.FadeTo(Math.Min(target.Alpha + 0.4f, 1), 40, EasingTypes.OutQuint); + target.Delay(40); + target.FadeOut(600, EasingTypes.OutQuint); + } + + return false; + } + } + } +} diff --git a/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs index f3ae600501..b322e167df 100644 --- a/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs @@ -4,38 +4,192 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; using osu.Game.Modes.Taiko.Objects; using osu.Game.Modes.UI; using OpenTK; using OpenTK.Graphics; using osu.Game.Modes.Taiko.Judgements; +using osu.Game.Modes.Objects.Drawables; +using osu.Game.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics.Primitives; namespace osu.Game.Modes.Taiko.UI { public class TaikoPlayfield : Playfield { + /// + /// The default play field height. + /// + public const float PLAYFIELD_BASE_HEIGHT = 242; + + /// + /// The play field height scale. + /// + public const float PLAYFIELD_SCALE = 0.65f; + + /// + /// The play field height after scaling. + /// + public static float PlayfieldHeight => PLAYFIELD_BASE_HEIGHT * PLAYFIELD_SCALE; + + /// + /// The offset from which the center of the hit target lies at. + /// + private const float hit_target_offset = 80; + + /// + /// The size of the left area of the playfield. This area contains the input drum. + /// + private const float left_area_size = 240; + + protected override Container Content => hitObjectContainer; + + private readonly Container hitExplosionContainer; + //private Container barLineContainer; + private readonly Container judgementContainer; + + private readonly Container hitObjectContainer; + //private Container topLevelHitContainer; + private readonly Container leftBackgroundContainer; + private readonly Container rightBackgroundContainer; + private readonly Box leftBackground; + private readonly Box rightBackground; + public TaikoPlayfield() { RelativeSizeAxes = Axes.X; - Size = new Vector2(1, 100); - Anchor = Anchor.Centre; - Origin = Anchor.Centre; + Height = PlayfieldHeight; + + AddInternal(new Drawable[] + { + rightBackgroundContainer = new Container + { + RelativeSizeAxes = Axes.Both, + BorderThickness = 2, + Masking = true, + EdgeEffect = new EdgeEffect + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.2f), + Radius = 5, + }, + Children = new Drawable[] + { + rightBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.6f + }, + } + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = left_area_size }, + Children = new Drawable[] + { + new Container + { + Padding = new MarginPadding { Left = hit_target_offset }, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + hitExplosionContainer = new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2), + Scale = new Vector2(PLAYFIELD_SCALE), + BlendingMode = BlendingMode.Additive + }, + //barLineContainer = new Container + //{ + // RelativeSizeAxes = Axes.Both, + //}, + new HitTarget + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + }, + hitObjectContainer = new Container + { + RelativeSizeAxes = Axes.Both, + }, + judgementContainer = new Container + { + RelativeSizeAxes = Axes.Both, + BlendingMode = BlendingMode.Additive + }, + }, + }, + } + }, + leftBackgroundContainer = new Container + { + Size = new Vector2(left_area_size, PlayfieldHeight), + BorderThickness = 1, + Children = new Drawable[] + { + leftBackground = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new InputDrum + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.X, + Position = new Vector2(0.10f, 0), + Scale = new Vector2(0.9f) + }, + new Box + { + Anchor = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = 10, + ColourInfo = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)), + }, + } + }, + //topLevelHitContainer = new Container + //{ + // RelativeSizeAxes = Axes.Both, + //} + }); } [BackgroundDependencyLoader] - private void load(TextureStore textures) + private void load(OsuColour colours) { - Add(new Box { RelativeSizeAxes = Axes.Both, Alpha = 0.5f }); + leftBackgroundContainer.BorderColour = colours.Gray0; + leftBackground.Colour = colours.Gray1; - Add(new Sprite + rightBackgroundContainer.BorderColour = colours.Gray1; + rightBackground.Colour = colours.Gray0; + } + + public override void Add(DrawableHitObject h) + { + h.Depth = (float)h.HitObject.StartTime; + + base.Add(h); + } + + public override void OnJudgement(DrawableHitObject judgedObject) + { + bool wasHit = judgedObject.Judgement.Result == HitResult.Hit; + + if (wasHit) + hitExplosionContainer.Add(new HitExplosion(judgedObject.Judgement)); + + judgementContainer.Add(new DrawableTaikoJudgementInfo(judgedObject.Judgement) { - Texture = textures.Get(@"Menu/logo"), - Origin = Anchor.Centre, - Scale = new Vector2(0.2f), - RelativePositionAxes = Axes.Both, - Position = new Vector2(0.1f, 0.5f), - Colour = Color4.Gray + Anchor = wasHit ? Anchor.TopLeft : Anchor.CentreLeft, + Origin = wasHit ? Anchor.BottomCentre : Anchor.Centre, + RelativePositionAxes = Axes.X, + X = wasHit ? judgedObject.Position.X : 0, }); } } diff --git a/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj b/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj index bf270e60c5..312bde5f04 100644 --- a/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj +++ b/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj @@ -55,10 +55,15 @@ + + + + + @@ -76,10 +81,6 @@ {C76BF5B3-985E-4D39-95FE-97C9C879B83A} osu.Framework - - {C92A607B-1FDD-4954-9F92-03FF547D9080} - osu.Game.Modes.Osu - {0D3FBF8A-7464-4CF7-8C90-3E7886DF2D4D} osu.Game diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index ae936f3f49..39fb1bfa8a 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Beatmaps.IO Assert.IsTrue(File.Exists(temp)); var importer = new BeatmapIPCChannel(client); - if (!importer.ImportAsync(temp).Wait(1000)) + if (!importer.ImportAsync(temp).Wait(5000)) Assert.Fail(@"IPC took too long to send"); ensureLoaded(osu); diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index 5f6de4bee4..3e62780e5c 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -28,6 +28,8 @@ namespace osu.Game.Graphics.UserInterface public OsuTabControl() { + TabContainer.Spacing = new Vector2(10f, 0f); + if (!typeof(T).IsEnum) throw new InvalidOperationException("OsuTabControl only supports enums as the generic type argument"); @@ -142,7 +144,7 @@ namespace osu.Game.Graphics.UserInterface { text = new OsuSpriteText { - Margin = new MarginPadding(5), + Margin = new MarginPadding { Top = 5, Bottom = 5 }, Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, TextSize = 14, diff --git a/osu.Game/Graphics/UserInterface/OsuTabControlCheckBox.cs b/osu.Game/Graphics/UserInterface/OsuTabControlCheckBox.cs new file mode 100644 index 0000000000..7dc1212318 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/OsuTabControlCheckBox.cs @@ -0,0 +1,140 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Transforms; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Graphics.UserInterface +{ + /// + /// A checkbox styled to be placed in line with an + /// + public class OsuTabControlCheckBox : CheckBox + { + private readonly Box box; + private readonly SpriteText text; + private readonly TextAwesome icon; + + public event EventHandler Action; + + private Color4? accentColour; + public Color4 AccentColour + { + get { return accentColour.GetValueOrDefault(); } + set + { + accentColour = value; + + if (State != CheckBoxState.Checked) + { + text.Colour = AccentColour; + icon.Colour = AccentColour; + } + } + } + + public string Text + { + get { return text.Text; } + set { text.Text = value; } + } + + protected override void OnChecked() + { + fadeIn(); + icon.Icon = FontAwesome.fa_check_circle_o; + Action?.Invoke(this, State); + } + + protected override void OnUnchecked() + { + fadeOut(); + icon.Icon = FontAwesome.fa_circle_o; + Action?.Invoke(this, State); + } + + private const float transition_length = 500; + + private void fadeIn() + { + box.FadeIn(transition_length, EasingTypes.OutQuint); + text.FadeColour(Color4.White, transition_length, EasingTypes.OutQuint); + } + + private void fadeOut() + { + box.FadeOut(transition_length, EasingTypes.OutQuint); + text.FadeColour(AccentColour, transition_length, EasingTypes.OutQuint); + } + + protected override bool OnHover(InputState state) + { + fadeIn(); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + if (State == CheckBoxState.Unchecked) + fadeOut(); + + base.OnHoverLost(state); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + if (accentColour == null) + AccentColour = colours.Blue; + } + + public OsuTabControlCheckBox() + { + AutoSizeAxes = Axes.Both; + + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Top = 5, Bottom = 5, }, + Spacing = new Vector2(5f, 0f), + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + text = new OsuSpriteText + { + TextSize = 14, + Font = @"Exo2.0-Bold", + }, + icon = new TextAwesome + { + TextSize = 14, + Icon = FontAwesome.fa_circle_o, + Shadow = true, + }, + }, + }, + box = new Box + { + RelativeSizeAxes = Axes.X, + Height = 1, + Alpha = 0, + Colour = Color4.White, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + } + }; + } + } +} diff --git a/osu.Game/Modes/Judgements/DrawableJudgementInfo.cs b/osu.Game/Modes/Judgements/DrawableJudgementInfo.cs new file mode 100644 index 0000000000..85b357a995 --- /dev/null +++ b/osu.Game/Modes/Judgements/DrawableJudgementInfo.cs @@ -0,0 +1,94 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Transforms; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Modes.Objects.Drawables; + +namespace osu.Game.Modes.Judgements +{ + /// + /// A drawable object which visualises the hit result of a . + /// + /// The type of judgement to visualise. + public class DrawableJudgementInfo : Container + where TJudgement : JudgementInfo + { + protected readonly TJudgement Judgement; + + protected readonly SpriteText JudgementText; + + /// + /// Creates a drawable which visualises a . + /// + /// The judgement to visualise. + public DrawableJudgementInfo(TJudgement judgement) + { + Judgement = judgement; + + AutoSizeAxes = Axes.Both; + + string scoreString = judgement.Result == HitResult.Hit ? judgement.ScoreString : judgement.Result.GetDescription(); + + Children = new[] + { + JudgementText = new OsuSpriteText + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Text = scoreString.ToUpper(), + Font = @"Venera", + TextSize = 16 + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + switch (Judgement.Result) + { + case HitResult.Miss: + Colour = colours.Red; + break; + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + FadeInFromZero(100, EasingTypes.OutQuint); + + switch (Judgement.Result) + { + case HitResult.Miss: + ScaleTo(1.6f); + ScaleTo(1, 100, EasingTypes.In); + + MoveToOffset(new Vector2(0, 100), 800, EasingTypes.InQuint); + RotateTo(40, 800, EasingTypes.InQuint); + + Delay(600); + FadeOut(200); + break; + case HitResult.Hit: + ScaleTo(0.9f); + ScaleTo(1, 500, EasingTypes.OutElastic); + + Delay(250); + FadeOut(250, EasingTypes.OutQuint); + break; + } + + Expire(); + } + } +} diff --git a/osu.Game/Modes/Judgements/JudgementInfo.cs b/osu.Game/Modes/Judgements/JudgementInfo.cs index 8e7539134e..a3cb9ba51f 100644 --- a/osu.Game/Modes/Judgements/JudgementInfo.cs +++ b/osu.Game/Modes/Judgements/JudgementInfo.cs @@ -5,10 +5,31 @@ using osu.Game.Modes.Objects.Drawables; namespace osu.Game.Modes.Judgements { - public class JudgementInfo + public abstract class JudgementInfo { - public ulong? ComboAtHit; + /// + /// Whether this judgement is the result of a hit or a miss. + /// public HitResult? Result; + + /// + /// The offset at which this judgement occurred. + /// public double TimeOffset; + + /// + /// The combo after this judgement was processed. + /// + public ulong? ComboAtHit; + + /// + /// The string representation for the score achieved. + /// + public abstract string ScoreString { get; } + + /// + /// The string representation for the max score achievable. + /// + public abstract string MaxScoreString { get; } } } \ No newline at end of file diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs new file mode 100644 index 0000000000..21e4d643f2 --- /dev/null +++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs @@ -0,0 +1,111 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Game.Beatmaps; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Screens.Select.Leaderboards; + +namespace osu.Game.Screens.Select +{ + public class BeatmapDetailArea : Container + { + private readonly Container content; + protected override Container Content => content; + + public readonly Container Details; //todo: replace with a real details view when added + public readonly Leaderboard Leaderboard; + + private APIAccess api; + + private WorkingBeatmap beatmap; + public WorkingBeatmap Beatmap + { + get + { + return beatmap; + } + set + { + beatmap = value; + if (IsLoaded) Schedule(updateScores); + } + } + + public BeatmapDetailArea() + { + AddInternal(new Drawable[] + { + new BeatmapDetailAreaTabControl + { + RelativeSizeAxes = Axes.X, + OnFilter = (tab, mods) => + { + switch (tab) + { + case BeatmapDetailTab.Details: + Details.Show(); + Leaderboard.Hide(); + break; + default: + Details.Hide(); + Leaderboard.Show(); + break; + } + + //for now let's always update scores. + updateScores(); + }, + }, + content = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = BeatmapDetailAreaTabControl.HEIGHT }, + }, + }); + + Add(new Drawable[] + { + Details = new Container + { + RelativeSizeAxes = Axes.Both, + }, + Leaderboard = new Leaderboard + { + RelativeSizeAxes = Axes.Both, + } + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + updateScores(); + } + + [BackgroundDependencyLoader(permitNulls: true)] + private void load(APIAccess api) + { + this.api = api; + } + + private GetScoresRequest getScoresRequest; + private void updateScores() + { + if (!IsLoaded) return; + + Leaderboard.Scores = null; + getScoresRequest?.Cancel(); + + if (api == null || beatmap?.BeatmapInfo == null || !Leaderboard.IsPresent) return; + + getScoresRequest = new GetScoresRequest(beatmap.BeatmapInfo); + getScoresRequest.Success += r => Leaderboard.Scores = r.Scores; + api.Queue(getScoresRequest); + } + } +} diff --git a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs new file mode 100644 index 0000000000..9dc8de96d1 --- /dev/null +++ b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs @@ -0,0 +1,79 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Select +{ + public class BeatmapDetailAreaTabControl : Container + { + public static readonly float HEIGHT = 24; + private readonly OsuTabControlCheckBox modsCheckbox; + private readonly OsuTabControl tabs; + + public Action OnFilter; //passed the selected tab and if mods is checked + + private void invokeOnFilter() + { + OnFilter?.Invoke(tabs.SelectedItem, modsCheckbox.State == CheckBoxState.Checked); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colour) + { + modsCheckbox.AccentColour = tabs.AccentColour = colour.YellowLight; + } + + public BeatmapDetailAreaTabControl() + { + Height = HEIGHT; + + Children = new Drawable[] + { + new Box + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = 1, + Colour = Color4.White.Opacity(0.2f), + }, + tabs = new OsuTabControl + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Both, + }, + modsCheckbox = new OsuTabControlCheckBox + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Text = @"Mods", + }, + }; + + tabs.ItemChanged += (sender, e) => invokeOnFilter(); + modsCheckbox.Action += (sender, e) => invokeOnFilter(); + + tabs.SelectedItem = BeatmapDetailTab.Global; + } + } + + public enum BeatmapDetailTab + { + Details, + Local, + Country, + Global, + Friends + } +} diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 1bf95f295e..4ce40cb943 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -74,7 +74,7 @@ namespace osu.Game.Screens.Select.Leaderboards RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Spacing = new Vector2(0f, 5f), - Padding = new MarginPadding(5), + Padding = new MarginPadding { Top = 10, Bottom = 5 }, }, }, }, diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 959d4492cf..e3a6371c27 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -8,11 +8,9 @@ using osu.Framework.Graphics.Primitives; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Online.API.Requests; using osu.Game.Overlays.Mods; using osu.Game.Screens.Edit; using osu.Game.Screens.Play; -using osu.Game.Screens.Select.Leaderboards; namespace osu.Game.Screens.Select { @@ -20,7 +18,7 @@ namespace osu.Game.Screens.Select { private OsuScreen player; private readonly ModSelectOverlay modSelect; - private readonly Leaderboard leaderboard; + private readonly BeatmapDetailArea beatmapDetails; public PlaySongSelect() { @@ -32,9 +30,10 @@ namespace osu.Game.Screens.Select Margin = new MarginPadding { Bottom = 50 } }); - LeftContent.Add(leaderboard = new Leaderboard + LeftContent.Add(beatmapDetails = new BeatmapDetailArea { RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 10, Right = 5 }, }); } @@ -52,29 +51,15 @@ namespace osu.Game.Screens.Select }, Key.Number3); } - private GetScoresRequest getScoresRequest; - protected override void OnBeatmapChanged(WorkingBeatmap beatmap) { beatmap?.Mods.BindTo(modSelect.SelectedMods); - updateLeaderboard(beatmap); + beatmapDetails.Beatmap = beatmap; base.OnBeatmapChanged(beatmap); } - private void updateLeaderboard(WorkingBeatmap beatmap) - { - leaderboard.Scores = null; - getScoresRequest?.Cancel(); - - if (beatmap?.BeatmapInfo == null) return; - - getScoresRequest = new GetScoresRequest(beatmap.BeatmapInfo); - getScoresRequest.Success += r => leaderboard.Scores = r.Scores; - Game.API.Queue(getScoresRequest); - } - protected override void OnResuming(Screen last) { player = null; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index bfb787cd51..378dfec57b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -96,6 +96,7 @@ + @@ -362,6 +363,9 @@ + + +