diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementCounter.cs new file mode 100644 index 0000000000..27f2362047 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementCounter.cs @@ -0,0 +1,136 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Diagnostics; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play.HUD.JudgementCounter; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public partial class TestSceneJudgementCounter : OsuTestScene + { + private ScoreProcessor scoreProcessor = null!; + private JudgementTally judgementTally = null!; + private TestJudgementCounterDisplay counter = null!; + + private readonly Bindable lastJudgementResult = new Bindable(); + + private int iteration; + + [SetUpSteps] + public void SetupSteps() => AddStep("Create components", () => + { + var ruleset = CreateRuleset(); + + Debug.Assert(ruleset != null); + + scoreProcessor = new ScoreProcessor(ruleset); + Child = new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] { (typeof(ScoreProcessor), scoreProcessor), (typeof(Ruleset), ruleset) }, + Children = new Drawable[] + { + judgementTally = new JudgementTally(), + new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] { (typeof(JudgementTally), judgementTally) }, + Child = counter = new TestJudgementCounterDisplay + { + Margin = new MarginPadding { Top = 100 }, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + } + } + }, + }; + }); + + protected override Ruleset CreateRuleset() => new ManiaRuleset(); + + private void applyOneJudgement(HitResult result) + { + lastJudgementResult.Value = new OsuJudgementResult(new HitObject + { + StartTime = iteration * 10000 + }, new OsuJudgement()) + { + Type = result, + }; + scoreProcessor.ApplyResult(lastJudgementResult.Value); + + iteration++; + } + + [Test] + public void TestAddJudgementsToCounters() + { + AddRepeatStep("Add judgement", () => applyOneJudgement(HitResult.Great), 2); + AddRepeatStep("Add judgement", () => applyOneJudgement(HitResult.Miss), 2); + AddRepeatStep("Add judgement", () => applyOneJudgement(HitResult.Meh), 2); + AddRepeatStep("Add judgement", () => applyOneJudgement(HitResult.LargeTickHit), 2); + AddStep("Show all judgements", () => counter.Mode.Value = JudgementCounterDisplay.DisplayMode.All); + AddAssert("Check value added whilst hidden", () => hiddenCount() == 2); + } + + [Test] + public void TestAddWhilstHidden() + { + AddRepeatStep("Add judgement", () => applyOneJudgement(HitResult.LargeTickHit), 2); + AddAssert("Check value added whilst hidden", () => hiddenCount() == 2); + AddStep("Show all judgements", () => counter.Mode.Value = JudgementCounterDisplay.DisplayMode.All); + } + + [Test] + public void TestChangeFlowDirection() + { + AddStep("Set direction vertical", () => counter.Direction.Value = FillDirection.Vertical); + AddStep("Set direction horizontal", () => counter.Direction.Value = FillDirection.Vertical); + } + + [Test] + public void TestHideJudgementNames() + { + AddStep("Hide judgement names", () => counter.ShowName.Value = false); + } + + [Test] + public void TestHideMaxValue() + { + AddStep("Hide max judgement", () => counter.ShowMax.Value = false); + AddStep("Show max judgement", () => counter.ShowMax.Value = true); + } + + [Test] + public void TestCycleDisplayModes() + { + AddStep("Show all judgements", () => counter.Mode.Value = JudgementCounterDisplay.DisplayMode.All); + AddStep("Show normal judgements", () => counter.Mode.Value = JudgementCounterDisplay.DisplayMode.Normal); + AddStep("Show basic judgements", () => counter.Mode.Value = JudgementCounterDisplay.DisplayMode.Simple); + } + + private int hiddenCount() + { + var num = counter.JudgementContainer.Children.OfType().First(child => child.Result.ResultInfo.Type == HitResult.LargeTickHit); + return num.Result.ResultCount.Value; + } + + private partial class TestJudgementCounterDisplay : JudgementCounterDisplay + { + public new FillFlowContainer JudgementContainer => base.JudgementContainer; + } + } +} diff --git a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounter.cs b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounter.cs new file mode 100644 index 0000000000..d9709eb88a --- /dev/null +++ b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounter.cs @@ -0,0 +1,118 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Screens.Play.HUD.JudgementCounter +{ + public partial class JudgementCounter : OverlayContainer + { + public BindableBool ShowName = new BindableBool(); + public Bindable Direction = new Bindable(); + + public readonly JudgementCounterInfo Result; + + public JudgementCounter(JudgementCounterInfo result) + { + Result = result; + } + + private OsuSpriteText resultName = null!; + private FillFlowContainer flowContainer = null!; + private JudgementRollingCounter counter = null!; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AutoSizeAxes = Axes.Both; + InternalChild = flowContainer = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + counter = new JudgementRollingCounter + { + Current = Result.ResultCount + }, + resultName = new OsuSpriteText + { + Font = OsuFont.Numeric.With(size: 8), + Text = Result.ResultInfo.Displayname + } + } + }; + + var result = Result.ResultInfo.Type; + + if (result.IsBasic()) + { + Colour = colours.ForHitResult(Result.ResultInfo.Type); + return; + } + + if (!result.IsBonus()) + { + Colour = colours.PurpleLight; + return; + } + + Colour = colours.PurpleLighter; + } + + protected override void LoadComplete() + { + ShowName.BindValueChanged(value => + { + if (value.NewValue) + { + resultName.Show(); + return; + } + + resultName.Hide(); + }, true); + + Direction.BindValueChanged(direction => + { + flowContainer.Direction = direction.NewValue; + + if (direction.NewValue == FillDirection.Vertical) + { + changeAnchor(Anchor.TopLeft); + return; + } + + changeAnchor(Anchor.BottomLeft); + + void changeAnchor(Anchor anchor) => counter.Anchor = resultName.Anchor = counter.Origin = resultName.Origin = anchor; + }, true); + + base.LoadComplete(); + } + + protected override void PopIn() + { + this.FadeInFromZero(500, Easing.OutQuint); + } + + protected override void PopOut() + { + this.FadeOut(100); + } + + private sealed partial class JudgementRollingCounter : RollingCounter + { + protected override OsuSpriteText CreateSpriteText() + => base.CreateSpriteText().With(s => s.Font = s.Font.With(fixedWidth: true, size: 16)); + + protected override double RollingDuration => 750; + } + } +} diff --git a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounterDisplay.cs b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounterDisplay.cs new file mode 100644 index 0000000000..b392ce8e04 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounterDisplay.cs @@ -0,0 +1,133 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Configuration; +using osu.Game.Rulesets.Scoring; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Screens.Play.HUD.JudgementCounter +{ + public partial class JudgementCounterDisplay : CompositeDrawable, ISkinnableDrawable + { + public bool UsesFixedAnchor { get; set; } + + [SettingSource("Counter direction")] + public Bindable Direction { get; set; } = new Bindable(); + + [SettingSource("Show judgement names")] + public BindableBool ShowName { get; set; } = new BindableBool(true); + + [SettingSource("Show max judgement")] + public BindableBool ShowMax { get; set; } = new BindableBool(true); + + [SettingSource("Display mode")] + public Bindable Mode { get; set; } = new Bindable(); + + [Resolved] + private JudgementTally tally { get; set; } = null!; + + protected FillFlowContainer JudgementContainer = null!; + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = JudgementContainer = new FillFlowContainer + { + Direction = Direction.Value, + Spacing = new Vector2(10), + AutoSizeAxes = Axes.Both + }; + + foreach (var result in tally.Results) + { + JudgementContainer.Add(createCounter(result)); + } + } + + protected override void Update() + { + Size = JudgementContainer.Size; + base.Update(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Direction.BindValueChanged(direction => JudgementContainer.Direction = direction.NewValue); + Mode.BindValueChanged(_ => updateCounter(), true); + + ShowMax.BindValueChanged(value => + { + var firstChild = JudgementContainer.Children.FirstOrDefault(); + + if (value.NewValue) + { + firstChild?.Show(); + return; + } + + firstChild?.Hide(); + }, true); + } + + private void updateCounter() + { + var counters = JudgementContainer.Children.OfType().ToList(); + + switch (Mode.Value) + { + case DisplayMode.Simple: + foreach (var counter in counters.Where(counter => counter.Result.ResultInfo.Type.IsBasic())) + counter.Show(); + + foreach (var counter in counters.Where(counter => !counter.Result.ResultInfo.Type.IsBasic())) + counter.Hide(); + + break; + + case DisplayMode.Normal: + foreach (var counter in counters.Where(counter => !counter.Result.ResultInfo.Type.IsBonus())) + counter.Show(); + + foreach (var counter in counters.Where(counter => counter.Result.ResultInfo.Type.IsBonus())) + counter.Hide(); + + break; + + case DisplayMode.All: + foreach (JudgementCounter counter in counters.Where(counter => !counter.IsPresent)) + counter.Show(); + + break; + + default: + throw new ArgumentOutOfRangeException(); + } + } + + public enum DisplayMode + { + Simple, + Normal, + All + } + + private JudgementCounter createCounter(JudgementCounterInfo info) + { + JudgementCounter counter = new JudgementCounter(info) + { + ShowName = { BindTarget = ShowName }, + Direction = { BindTarget = Direction } + }; + return counter; + } + } +} diff --git a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementTally.cs b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementTally.cs index 2bc89aeea1..9cbeea7a2a 100644 --- a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementTally.cs +++ b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementTally.cs @@ -9,8 +9,8 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Play.HUD.JudgementCounter { @@ -22,14 +22,14 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter public List Results = new List(); [BackgroundDependencyLoader] - private void load(DrawableRuleset ruleset) + private void load(IBindable working) { - foreach (var result in ruleset.Ruleset.GetHitResults()) + foreach (var result in working.Value.BeatmapInfo.Ruleset.CreateInstance().GetHitResults()) { Results.Add(new JudgementCounterInfo { ResultInfo = (result.result, result.displayName), - ResultCount = new BindableInt(), + ResultCount = new BindableInt() }); } }