From a25b6e6a099233f02ec88121de7f9b508d32d378 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Thu, 10 Mar 2022 00:42:58 -0800 Subject: [PATCH 001/124] Proof of Concept draft for Taiko touch input --- osu.Game.Rulesets.Taiko/TaikoInputManager.cs | 2 + osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 - .../UI/DrawableTaikoRuleset.cs | 6 + .../UI/DrumTouchInputArea.cs | 141 ++++++++++++++++++ osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 5 +- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 1 + 6 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs diff --git a/osu.Game.Rulesets.Taiko/TaikoInputManager.cs b/osu.Game.Rulesets.Taiko/TaikoInputManager.cs index 058bec5111..20766029bb 100644 --- a/osu.Game.Rulesets.Taiko/TaikoInputManager.cs +++ b/osu.Game.Rulesets.Taiko/TaikoInputManager.cs @@ -2,11 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System.ComponentModel; +using osu.Framework.Allocation; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Taiko { + [Cached] // Used for touch input, see DrumTouchInputArea. public class TaikoInputManager : RulesetInputManager { public TaikoInputManager(RulesetInfo ruleset) diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index e56aabaf9d..4a22619f06 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -48,8 +48,6 @@ namespace osu.Game.Rulesets.Taiko public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] { - new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre), - new KeyBinding(InputKey.MouseRight, TaikoAction.LeftRim), new KeyBinding(InputKey.D, TaikoAction.LeftRim), new KeyBinding(InputKey.F, TaikoAction.LeftCentre), new KeyBinding(InputKey.J, TaikoAction.RightCentre), diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 824b95639b..cad134067f 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -52,6 +52,12 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.X, Depth = float.MaxValue }); + + DrumTouchInputArea touchInput = new DrumTouchInputArea(Playfield) { + RelativePositionAxes = Axes.Both, + RelativeSizeAxes = Axes.Both, + }; + Overlays.Add(touchInput); } protected override void UpdateAfterChildren() diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs new file mode 100644 index 0000000000..647187226a --- /dev/null +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -0,0 +1,141 @@ +// 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.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Framework.Logging; +using osu.Game.Graphics; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Taiko.UI +{ + /// + /// An overlay that captures and displays Taiko mouse and touch input. + /// The boundaries of this overlay defines the interactable area for touch input. + /// A secondary InputDrum is attached by this overlay, which defines the circulary boundary which distinguishes "centre" from "rim" hits, and also displays input. + /// + public class DrumTouchInputArea : Container + { + // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) + private const float overhangPercent = 0.33f; + private readonly InputDrum touchInputDrum; + + [Resolved(canBeNull: true)] + private TaikoInputManager taikoInputManager { get; set; } + + private KeyBindingContainer keyBindingContainer; + + // Which Taiko action was pressed by the last OnMouseDown event, so that the corresponding action can be released OnMouseUp even if the cursor position moved + private TaikoAction mouseAction; + + // A map of (Finger Index OnTouchDown -> Which Taiko action was pressed), so that the corresponding action can be released OnTouchUp is released even if the touch position moved + private Dictionary touchActions = new Dictionary(Enum.GetNames(typeof(TouchSource)).Length); + + private Playfield playfield; + + public DrumTouchInputArea(Playfield playfield) { + this.playfield = playfield; + + RelativeSizeAxes = Axes.Both; + RelativePositionAxes = Axes.Both; + Children = new Drawable[] + { + new Box() { + Alpha = 0.0f, + Colour = new OsuColour().Blue, + + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + }, + new Container() { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + + Children = new Drawable[] + { + touchInputDrum = new InputDrum(playfield.HitObjectContainer) { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } + }, + }; + + } + protected override void LoadComplete() + { + keyBindingContainer = taikoInputManager?.KeyBindingContainer; + + Padding = new MarginPadding { + Top = playfield.ScreenSpaceDrawQuad.BottomLeft.Y, + Bottom = -DrawHeight * overhangPercent, + }; + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + mouseAction = getTaikoActionFromInput(e.ScreenSpaceMouseDownPosition); + keyBindingContainer?.TriggerPressed(mouseAction); + return base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseUpEvent e) + { + keyBindingContainer?.TriggerReleased(mouseAction); + base.OnMouseUp(e); + } + + protected override bool OnTouchDown(TouchDownEvent e) + { + TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceTouchDownPosition); + if (touchActions.ContainsKey(e.Touch.Source)) { + touchActions[e.Touch.Source] = taikoAction; + } + else { + touchActions.Add(e.Touch.Source, taikoAction); + } + keyBindingContainer?.TriggerPressed(touchActions[e.Touch.Source]); + + return base.OnTouchDown(e); + } + + protected override void OnTouchUp(TouchUpEvent e) + { + keyBindingContainer?.TriggerReleased(touchActions[e.Touch.Source]); + base.OnTouchUp(e); + } + + private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { + bool leftSide = inputIsOnLeftSide(inputPosition); + bool centreHit = inputIsCenterHit(inputPosition); + + return leftSide ? + (centreHit ? TaikoAction.LeftCentre : TaikoAction.LeftRim) : + (centreHit ? TaikoAction.RightCentre : TaikoAction.RightRim); + } + + private bool inputIsOnLeftSide(Vector2 inputPosition) { + Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; + return inputPositionToDrumCentreDelta.X < 0f; + } + + private bool inputIsCenterHit(Vector2 inputPosition) { + Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; + + float inputDrumRadius = Math.Max(touchInputDrum.Width, touchInputDrum.DrawHeight) / 2f; + float centreRadius = (inputDrumRadius * InputDrum.centre_size); + return inputPositionToDrumCentreDelta.Length <= centreRadius; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 16be20f7f3..1949d09de1 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Taiko.UI /// internal class InputDrum : Container { + public const float centre_size = 0.7f; private const float middle_split = 0.025f; [Cached] @@ -122,14 +123,14 @@ namespace osu.Game.Rulesets.Taiko.UI Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.7f) + Size = new Vector2(centre_size) }, centreHit = new Sprite { Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.7f), + Size = new Vector2(centre_size), Alpha = 0, Blending = BlendingParameters.Additive } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index d650cab729..2f7e0dc08f 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -144,6 +144,7 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, }, drumRollHitContainer.CreateProxy(), + // new DrumTouchInputArea(this), }; RegisterPool(50); From 317869078f470889330702b7b99b6146569b4f75 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Thu, 10 Mar 2022 05:09:07 -0800 Subject: [PATCH 002/124] Basic functionality for Taiko touch input now complete --- .../UI/DrawableTaikoRuleset.cs | 12 ++-- .../UI/DrumSamplePlayer.cs | 55 ++++++++++++++++ .../UI/DrumTouchInputArea.cs | 62 ++++++++----------- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 19 +----- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 4 +- 5 files changed, 93 insertions(+), 59 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index cad134067f..9cd5b0ca7f 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects; @@ -35,6 +36,8 @@ namespace osu.Game.Rulesets.Taiko.UI private SkinnableDrawable scroller; + private DrumTouchInputArea drumTouchInputArea; + public DrawableTaikoRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) : base(ruleset, beatmap, mods) { @@ -53,11 +56,8 @@ namespace osu.Game.Rulesets.Taiko.UI Depth = float.MaxValue }); - DrumTouchInputArea touchInput = new DrumTouchInputArea(Playfield) { - RelativePositionAxes = Axes.Both, - RelativeSizeAxes = Axes.Both, - }; - Overlays.Add(touchInput); + if ((drumTouchInputArea = CreateDrumTouchInputArea()) != null) + KeyBindingInputManager.Add(drumTouchInputArea); } protected override void UpdateAfterChildren() @@ -76,6 +76,8 @@ namespace osu.Game.Rulesets.Taiko.UI return ControlPoints[result]; } + public DrumTouchInputArea CreateDrumTouchInputArea() => new DrumTouchInputArea(); + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer(); protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs new file mode 100644 index 0000000000..458dbc6ca1 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs @@ -0,0 +1,55 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Taiko.UI +{ + internal class DrumSamplePlayer : Container, IKeyBindingHandler { + private DrumSampleTriggerSource leftRimSampleTriggerSource; + private DrumSampleTriggerSource leftCentreSampleTriggerSource; + private DrumSampleTriggerSource rightCentreSampleTriggerSource; + private DrumSampleTriggerSource rightRimSampleTriggerSource; + + public DrumSamplePlayer(HitObjectContainer hitObjectContainer) { + Children = new Drawable[] + { + leftRimSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), + leftCentreSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), + rightCentreSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), + rightRimSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), + }; + } + + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Action == TaikoAction.LeftRim) + { + leftRimSampleTriggerSource.Play(HitType.Rim); + } + else if (e.Action == TaikoAction.LeftCentre) + { + leftCentreSampleTriggerSource.Play(HitType.Centre); + } + else if (e.Action == TaikoAction.RightCentre) + { + rightCentreSampleTriggerSource.Play(HitType.Centre); + } + else if (e.Action == TaikoAction.RightRim) + { + rightRimSampleTriggerSource.Play(HitType.Rim); + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 647187226a..d610e84c8d 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -10,9 +10,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Framework.Logging; using osu.Game.Graphics; -using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Taiko.UI @@ -25,11 +23,9 @@ namespace osu.Game.Rulesets.Taiko.UI public class DrumTouchInputArea : Container { // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) - private const float overhangPercent = 0.33f; - private readonly InputDrum touchInputDrum; - - [Resolved(canBeNull: true)] - private TaikoInputManager taikoInputManager { get; set; } + private const float overhangPercent = 0.35f; + private InputDrum touchInputDrum; + private Circle drumBackground; private KeyBindingContainer keyBindingContainer; @@ -39,55 +35,55 @@ namespace osu.Game.Rulesets.Taiko.UI // A map of (Finger Index OnTouchDown -> Which Taiko action was pressed), so that the corresponding action can be released OnTouchUp is released even if the touch position moved private Dictionary touchActions = new Dictionary(Enum.GetNames(typeof(TouchSource)).Length); - private Playfield playfield; - - public DrumTouchInputArea(Playfield playfield) { - this.playfield = playfield; - + public DrumTouchInputArea() { RelativeSizeAxes = Axes.Both; RelativePositionAxes = Axes.Both; Children = new Drawable[] { - new Box() { - Alpha = 0.0f, - Colour = new OsuColour().Blue, - - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, - }, new Container() { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, - Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Children = new Drawable[] { - touchInputDrum = new InputDrum(playfield.HitObjectContainer) { + drumBackground = new Circle() { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fit, + Alpha = 0.9f, + }, + touchInputDrum = new InputDrum() { Anchor = Anchor.Centre, Origin = Anchor.Centre, }, } }, }; - } + protected override void LoadComplete() { - keyBindingContainer = taikoInputManager?.KeyBindingContainer; - Padding = new MarginPadding { - Top = playfield.ScreenSpaceDrawQuad.BottomLeft.Y, - Bottom = -DrawHeight * overhangPercent, + Top = TaikoPlayfield.DEFAULT_HEIGHT * 2f, // Visual elements should start right below the playfield + Bottom = -touchInputDrum.DrawHeight * overhangPercent, // The drum should go past the bottom of the screen so that it can be wider }; } + [BackgroundDependencyLoader] + private void load(TaikoInputManager taikoInputManager, OsuColour colours) + { + keyBindingContainer = taikoInputManager?.KeyBindingContainer; + drumBackground.Colour = colours.Gray0; + } + protected override bool OnMouseDown(MouseDownEvent e) { mouseAction = getTaikoActionFromInput(e.ScreenSpaceMouseDownPosition); keyBindingContainer?.TriggerPressed(mouseAction); - return base.OnMouseDown(e); + return true; } protected override void OnMouseUp(MouseUpEvent e) @@ -99,20 +95,16 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool OnTouchDown(TouchDownEvent e) { TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceTouchDownPosition); - if (touchActions.ContainsKey(e.Touch.Source)) { - touchActions[e.Touch.Source] = taikoAction; - } - else { - touchActions.Add(e.Touch.Source, taikoAction); - } + touchActions.Add(e.Touch.Source, taikoAction); keyBindingContainer?.TriggerPressed(touchActions[e.Touch.Source]); - return base.OnTouchDown(e); + return true; } protected override void OnTouchUp(TouchUpEvent e) { keyBindingContainer?.TriggerReleased(touchActions[e.Touch.Source]); + touchActions.Remove(e.Touch.Source); base.OnTouchUp(e); } diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 1949d09de1..76594b86c1 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -10,8 +10,6 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Rulesets.UI; using osu.Game.Skinning; using osuTK; @@ -25,13 +23,8 @@ namespace osu.Game.Rulesets.Taiko.UI public const float centre_size = 0.7f; private const float middle_split = 0.025f; - [Cached] - private DrumSampleTriggerSource sampleTriggerSource; - - public InputDrum(HitObjectContainer hitObjectContainer) + public InputDrum() { - sampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer); - RelativeSizeAxes = Axes.Both; } @@ -70,8 +63,7 @@ namespace osu.Game.Rulesets.Taiko.UI CentreAction = TaikoAction.RightCentre } } - }), - sampleTriggerSource + }) }; } @@ -95,9 +87,6 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Sprite centre; private readonly Sprite centreHit; - [Resolved] - private DrumSampleTriggerSource sampleTriggerSource { get; set; } - public TaikoHalfDrum(bool flipped) { Masking = true; @@ -158,15 +147,11 @@ namespace osu.Game.Rulesets.Taiko.UI { target = centreHit; back = centre; - - sampleTriggerSource.Play(HitType.Centre); } else if (e.Action == RimAction) { target = rimHit; back = rim; - - sampleTriggerSource.Play(HitType.Rim); } if (target != null) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 2f7e0dc08f..71aee107d2 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -123,7 +123,7 @@ namespace osu.Game.Rulesets.Taiko.UI Children = new Drawable[] { new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new PlayfieldBackgroundLeft()), - new InputDrum(HitObjectContainer) + new InputDrum() { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -144,7 +144,7 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, }, drumRollHitContainer.CreateProxy(), - // new DrumTouchInputArea(this), + new DrumSamplePlayer(HitObjectContainer), }; RegisterPool(50); From 5ce57fa34a70a23971e56495a96ee86081395166 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Thu, 10 Mar 2022 06:17:06 -0800 Subject: [PATCH 003/124] Improve readability for getTaikoActionFromInput --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index d610e84c8d..a2c33a85e8 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -109,12 +109,12 @@ namespace osu.Game.Rulesets.Taiko.UI } private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { - bool leftSide = inputIsOnLeftSide(inputPosition); bool centreHit = inputIsCenterHit(inputPosition); + bool leftSide = inputIsOnLeftSide(inputPosition); - return leftSide ? - (centreHit ? TaikoAction.LeftCentre : TaikoAction.LeftRim) : - (centreHit ? TaikoAction.RightCentre : TaikoAction.RightRim); + return centreHit ? + (leftSide ? TaikoAction.LeftCentre : TaikoAction.RightCentre) : + (leftSide ? TaikoAction.LeftRim : TaikoAction.RightRim); } private bool inputIsOnLeftSide(Vector2 inputPosition) { From 38c61b2c1d088e788953e0246b94923dc9b896eb Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 00:14:33 -0800 Subject: [PATCH 004/124] Fix crash when loading legacy osu!taiko skins with touch input --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 43c5c07f80..321389676a 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -8,8 +8,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Rulesets.Taiko.UI; using osu.Game.Skinning; using osuTK; @@ -112,9 +110,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy public readonly Sprite Rim; public readonly Sprite Centre; - [Resolved] - private DrumSampleTriggerSource sampleTriggerSource { get; set; } - public LegacyHalfDrum(bool flipped) { Masking = true; @@ -149,12 +144,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy if (e.Action == CentreAction) { target = Centre; - sampleTriggerSource.Play(HitType.Centre); } else if (e.Action == RimAction) { target = Rim; - sampleTriggerSource.Play(HitType.Rim); } if (target != null) From 7336c2c0bd06010ea7fabc12a91833ab1bd4e3ab Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 02:48:08 -0800 Subject: [PATCH 005/124] Fix osu!taiko alignment issue with legacy skins on Touch Controls --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 321389676a..d3eeb8e16a 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -32,6 +32,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { Child = content = new Container { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Size = new Vector2(180, 200), Children = new Drawable[] { From 0f83308f7b1c18ebca3c89333954e26e34be2b56 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 02:49:10 -0800 Subject: [PATCH 006/124] Update osu!taiko TestSceneInputDrum with InputDrum changes for touch controls --- osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs index 24db046748..10fca30865 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(200), - Child = new InputDrum(playfield.HitObjectContainer) + Child = new InputDrum() } }); } From 1ed06f30e7feff438354617b55a32f2022728ef7 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 03:33:03 -0800 Subject: [PATCH 007/124] osu!Taiko touch implementation code cleanup --- .../UI/DrumSamplePlayer.cs | 27 +++++++++---------- .../UI/DrumTouchInputArea.cs | 4 +-- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs index 458dbc6ca1..609da0b389 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs @@ -28,21 +28,20 @@ namespace osu.Game.Rulesets.Taiko.UI public bool OnPressed(KeyBindingPressEvent e) { - if (e.Action == TaikoAction.LeftRim) + switch (e.Action) { - leftRimSampleTriggerSource.Play(HitType.Rim); - } - else if (e.Action == TaikoAction.LeftCentre) - { - leftCentreSampleTriggerSource.Play(HitType.Centre); - } - else if (e.Action == TaikoAction.RightCentre) - { - rightCentreSampleTriggerSource.Play(HitType.Centre); - } - else if (e.Action == TaikoAction.RightRim) - { - rightRimSampleTriggerSource.Play(HitType.Rim); + case TaikoAction.LeftRim: + leftRimSampleTriggerSource.Play(HitType.Rim); + break; + case TaikoAction.LeftCentre: + leftCentreSampleTriggerSource.Play(HitType.Centre); + break; + case TaikoAction.RightCentre: + rightCentreSampleTriggerSource.Play(HitType.Centre); + break; + case TaikoAction.RightRim: + rightRimSampleTriggerSource.Play(HitType.Rim); + break; } return false; diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index a2c33a85e8..ccf59507d3 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.UI public class DrumTouchInputArea : Container { // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) - private const float overhangPercent = 0.35f; + private const float offscreenPercent = 0.35f; private InputDrum touchInputDrum; private Circle drumBackground; @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Taiko.UI { Padding = new MarginPadding { Top = TaikoPlayfield.DEFAULT_HEIGHT * 2f, // Visual elements should start right below the playfield - Bottom = -touchInputDrum.DrawHeight * overhangPercent, // The drum should go past the bottom of the screen so that it can be wider + Bottom = -touchInputDrum.DrawHeight * offscreenPercent, // The drum should go past the bottom of the screen so that it can be wider }; } From c33a661a4990d681ec595a7b35afc9016d6b144c Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 03:36:03 -0800 Subject: [PATCH 008/124] osu!taiko touch implementation syntax formatting cleanup --- osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs index 609da0b389..d5215c05e9 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs @@ -10,13 +10,15 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Taiko.UI { - internal class DrumSamplePlayer : Container, IKeyBindingHandler { + internal class DrumSamplePlayer : Container, IKeyBindingHandler + { private DrumSampleTriggerSource leftRimSampleTriggerSource; private DrumSampleTriggerSource leftCentreSampleTriggerSource; private DrumSampleTriggerSource rightCentreSampleTriggerSource; private DrumSampleTriggerSource rightRimSampleTriggerSource; - public DrumSamplePlayer(HitObjectContainer hitObjectContainer) { + public DrumSamplePlayer(HitObjectContainer hitObjectContainer) + { Children = new Drawable[] { leftRimSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), From 35053eaeba38a8e15b609b7ee18421c532a9321a Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 04:43:57 -0800 Subject: [PATCH 009/124] Show and hide osu!taiko touch controls overlay based on most recent input type detected --- .../UI/DrumTouchInputArea.cs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index ccf59507d3..4b2c9c9936 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -35,12 +35,15 @@ namespace osu.Game.Rulesets.Taiko.UI // A map of (Finger Index OnTouchDown -> Which Taiko action was pressed), so that the corresponding action can be released OnTouchUp is released even if the touch position moved private Dictionary touchActions = new Dictionary(Enum.GetNames(typeof(TouchSource)).Length); + private Container visibleComponents; + public DrumTouchInputArea() { RelativeSizeAxes = Axes.Both; RelativePositionAxes = Axes.Both; Children = new Drawable[] { - new Container() { + visibleComponents = new Container() { + Alpha = 0.0f, RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, Anchor = Anchor.BottomCentre, @@ -81,6 +84,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool OnMouseDown(MouseDownEvent e) { + ShowTouchControls(); mouseAction = getTaikoActionFromInput(e.ScreenSpaceMouseDownPosition); keyBindingContainer?.TriggerPressed(mouseAction); return true; @@ -94,6 +98,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool OnTouchDown(TouchDownEvent e) { + ShowTouchControls(); TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceTouchDownPosition); touchActions.Add(e.Touch.Source, taikoAction); keyBindingContainer?.TriggerPressed(touchActions[e.Touch.Source]); @@ -108,6 +113,21 @@ namespace osu.Game.Rulesets.Taiko.UI base.OnTouchUp(e); } + protected override bool OnKeyDown(KeyDownEvent e) + { + HideTouchControls(); + return false; + } + + + public void ShowTouchControls() { + visibleComponents.Animate(components => components.FadeIn(500, Easing.OutQuint)); + } + + public void HideTouchControls() { + visibleComponents.Animate(components => components.FadeOut(2000, Easing.OutQuint)); + } + private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { bool centreHit = inputIsCenterHit(inputPosition); bool leftSide = inputIsOnLeftSide(inputPosition); From 848b005097de38e213d7f33cf474e33840b7af6f Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 04:48:57 -0800 Subject: [PATCH 010/124] Remove unneccessary whitespace --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 4b2c9c9936..a944d3770b 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -119,7 +119,6 @@ namespace osu.Game.Rulesets.Taiko.UI return false; } - public void ShowTouchControls() { visibleComponents.Animate(components => components.FadeIn(500, Easing.OutQuint)); } From ac17c047f6264eff3c289810f83f1f5110e495d0 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Sat, 12 Mar 2022 05:01:40 -0800 Subject: [PATCH 011/124] Code formatting --- .../UI/DrumTouchInputArea.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index a944d3770b..046816c013 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -37,7 +37,8 @@ namespace osu.Game.Rulesets.Taiko.UI private Container visibleComponents; - public DrumTouchInputArea() { + public DrumTouchInputArea() + { RelativeSizeAxes = Axes.Both; RelativePositionAxes = Axes.Both; Children = new Drawable[] @@ -102,7 +103,6 @@ namespace osu.Game.Rulesets.Taiko.UI TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceTouchDownPosition); touchActions.Add(e.Touch.Source, taikoAction); keyBindingContainer?.TriggerPressed(touchActions[e.Touch.Source]); - return true; } @@ -119,15 +119,18 @@ namespace osu.Game.Rulesets.Taiko.UI return false; } - public void ShowTouchControls() { + public void ShowTouchControls() + { visibleComponents.Animate(components => components.FadeIn(500, Easing.OutQuint)); } - public void HideTouchControls() { + public void HideTouchControls() + { visibleComponents.Animate(components => components.FadeOut(2000, Easing.OutQuint)); } - private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { + private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) + { bool centreHit = inputIsCenterHit(inputPosition); bool leftSide = inputIsOnLeftSide(inputPosition); @@ -136,12 +139,14 @@ namespace osu.Game.Rulesets.Taiko.UI (leftSide ? TaikoAction.LeftRim : TaikoAction.RightRim); } - private bool inputIsOnLeftSide(Vector2 inputPosition) { + private bool inputIsOnLeftSide(Vector2 inputPosition) + { Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; return inputPositionToDrumCentreDelta.X < 0f; } - private bool inputIsCenterHit(Vector2 inputPosition) { + private bool inputIsCenterHit(Vector2 inputPosition) + { Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; float inputDrumRadius = Math.Max(touchInputDrum.Width, touchInputDrum.DrawHeight) / 2f; From 6f99455d9490469af3938fe19fcef361bdab1098 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Sat, 12 Mar 2022 05:17:45 -0800 Subject: [PATCH 012/124] Improve centre input size fitting for legacy skins --- .../Skinning/Legacy/LegacyInputDrum.cs | 1 + osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 2 +- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 8 ++++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index d3eeb8e16a..8ab9e56aec 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -18,6 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy /// internal class LegacyInputDrum : Container { + public float centre_size = 0.5f; private LegacyHalfDrum left; private LegacyHalfDrum right; private Container content; diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 046816c013..5f4eecd3bb 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -150,7 +150,7 @@ namespace osu.Game.Rulesets.Taiko.UI Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; float inputDrumRadius = Math.Max(touchInputDrum.Width, touchInputDrum.DrawHeight) / 2f; - float centreRadius = (inputDrumRadius * InputDrum.centre_size); + float centreRadius = (inputDrumRadius * touchInputDrum.centre_size); return inputPositionToDrumCentreDelta.Length <= centreRadius; } } diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 76594b86c1..a7ae763ab5 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.UI /// internal class InputDrum : Container { - public const float centre_size = 0.7f; + public float centre_size = 0.7f; private const float middle_split = 0.025f; public InputDrum() @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.UI Scale = new Vector2(0.9f), Children = new Drawable[] { - new TaikoHalfDrum(false) + new TaikoHalfDrum(false, centre_size) { Name = "Left Half", Anchor = Anchor.Centre, @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.UI RimAction = TaikoAction.LeftRim, CentreAction = TaikoAction.LeftCentre }, - new TaikoHalfDrum(true) + new TaikoHalfDrum(true, centre_size) { Name = "Right Half", Anchor = Anchor.Centre, @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Sprite centre; private readonly Sprite centreHit; - public TaikoHalfDrum(bool flipped) + public TaikoHalfDrum(bool flipped, float centre_size) { Masking = true; From 9db80c33350d194fef952c0fb98e0c1252b09097 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Sat, 12 Mar 2022 05:32:02 -0800 Subject: [PATCH 013/124] Code cleanup --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 5f4eecd3bb..82b289ef14 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.UI // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) private const float offscreenPercent = 0.35f; private InputDrum touchInputDrum; - private Circle drumBackground; + private Circle drumBackground; private KeyBindingContainer keyBindingContainer; @@ -70,7 +70,8 @@ namespace osu.Game.Rulesets.Taiko.UI protected override void LoadComplete() { - Padding = new MarginPadding { + Padding = new MarginPadding + { Top = TaikoPlayfield.DEFAULT_HEIGHT * 2f, // Visual elements should start right below the playfield Bottom = -touchInputDrum.DrawHeight * offscreenPercent, // The drum should go past the bottom of the screen so that it can be wider }; From 2de6bb033bffdaeeb8a1095264b9ad745b0065c3 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Sat, 12 Mar 2022 07:51:40 -0800 Subject: [PATCH 014/124] Adjust default touch drum overlay size to be more comfortable on phones --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 82b289ef14..e501a40d20 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.UI public class DrumTouchInputArea : Container { // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) - private const float offscreenPercent = 0.35f; + private const float offscreenPercent = 0.4f; private InputDrum touchInputDrum; private Circle drumBackground; @@ -62,6 +62,7 @@ namespace osu.Game.Rulesets.Taiko.UI touchInputDrum = new InputDrum() { Anchor = Anchor.Centre, Origin = Anchor.Centre, + centre_size = 0.8f, }, } }, From b628a65cfa5e790961ec3db6edc27fe41c89334b Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Sat, 12 Mar 2022 08:03:24 -0800 Subject: [PATCH 015/124] Revert "Adjust default touch drum overlay size to be more comfortable on phones" This reverts commit 2de6bb033bffdaeeb8a1095264b9ad745b0065c3. --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index e501a40d20..82b289ef14 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.UI public class DrumTouchInputArea : Container { // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) - private const float offscreenPercent = 0.4f; + private const float offscreenPercent = 0.35f; private InputDrum touchInputDrum; private Circle drumBackground; @@ -62,7 +62,6 @@ namespace osu.Game.Rulesets.Taiko.UI touchInputDrum = new InputDrum() { Anchor = Anchor.Centre, Origin = Anchor.Centre, - centre_size = 0.8f, }, } }, From f7a658450fb2592a171165b57528786d32190d0d Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Wed, 1 Jun 2022 00:54:49 -0700 Subject: [PATCH 016/124] Move DrawableTaikoRulesetTestScene's hardcoded beatmap to CreateBeatmap(..) method instead of load(..) method, so that the class is more extensible and reusable --- .../DrawableTaikoRulesetTestScene.cs | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs index f5e7304c12..2853aa32f4 100644 --- a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs +++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs @@ -21,13 +21,29 @@ namespace osu.Game.Rulesets.Taiko.Tests protected DrawableTaikoRuleset DrawableRuleset { get; private set; } protected Container PlayfieldContainer { get; private set; } + protected ControlPointInfo controlPointInfo { get; private set; } + [BackgroundDependencyLoader] private void load() { - var controlPointInfo = new ControlPointInfo(); + controlPointInfo = new ControlPointInfo(); controlPointInfo.Add(0, new TimingControlPoint()); - IWorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap + IWorkingBeatmap beatmap = CreateWorkingBeatmap(CreateBeatmap(new TaikoRuleset().RulesetInfo)); + + Add(PlayfieldContainer = new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + Height = DEFAULT_PLAYFIELD_CONTAINER_HEIGHT, + Children = new[] { DrawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset)) } + }); + } + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + return new Beatmap { HitObjects = new List { new Hit { Type = HitType.Centre } }, BeatmapInfo = new BeatmapInfo @@ -39,19 +55,10 @@ namespace osu.Game.Rulesets.Taiko.Tests Title = @"Sample Beatmap", Author = { Username = @"peppy" }, }, - Ruleset = new TaikoRuleset().RulesetInfo + Ruleset = ruleset }, ControlPointInfo = controlPointInfo - }); - - Add(PlayfieldContainer = new Container - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - Height = DEFAULT_PLAYFIELD_CONTAINER_HEIGHT, - Children = new[] { DrawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(new TaikoRuleset().RulesetInfo)) } - }); + }; } } } From ae996d1404611ad99c5e66d4ee650ecdbac6a86a Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Wed, 1 Jun 2022 00:56:03 -0700 Subject: [PATCH 017/124] Add manual test scene for DrumTouchInputArea --- .../TestSceneDrumTouchInputArea.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs new file mode 100644 index 0000000000..e82594cf4f --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs @@ -0,0 +1,53 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.UI; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + [TestFixture] + public class TestSceneDrumTouchInputArea : DrawableTaikoRulesetTestScene + { + protected const double NUM_HIT_OBJECTS = 10; + protected const double HIT_OBJECT_TIME_SPACING_MS = 1000; + + [BackgroundDependencyLoader] + private void load() + { + var drumTouchInputArea = new DrumTouchInputArea(); + DrawableRuleset.KeyBindingInputManager.Add(drumTouchInputArea); + drumTouchInputArea.ShowTouchControls(); + } + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + List hitObjects = new List(); + + for (var i = 0; i < NUM_HIT_OBJECTS; i++) { + hitObjects.Add(new Hit + { + StartTime = Time.Current + i * HIT_OBJECT_TIME_SPACING_MS, + IsStrong = isOdd(i), + Type = isOdd(i / 2) ? HitType.Centre : HitType.Rim + }); + } + + var beatmap = new Beatmap + { + BeatmapInfo = { Ruleset = ruleset }, + HitObjects = hitObjects + }; + + return beatmap; + } + + private bool isOdd(int number) { + return number % 2 == 0; + } + } +} From fcc05396bcb2425482741e9cfc55c4bf32a580a7 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Wed, 1 Jun 2022 00:57:24 -0700 Subject: [PATCH 018/124] Remove unused import --- osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index b5bf33bc9f..e52c95e34a 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects; From f6e9dfe7bfda690cee565adbbb06cce0fb7cd039 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Wed, 1 Jun 2022 01:03:21 -0700 Subject: [PATCH 019/124] Fix naming rule violations --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs | 2 +- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 6 +++--- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 8ab9e56aec..7a74567abb 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy /// internal class LegacyInputDrum : Container { - public float centre_size = 0.5f; + public float CentreSize = 0.5f; private LegacyHalfDrum left; private LegacyHalfDrum right; private Container content; diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 82b289ef14..959666ffe9 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.UI public class DrumTouchInputArea : Container { // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) - private const float offscreenPercent = 0.35f; + private const float offscreen_percent = 0.35f; private InputDrum touchInputDrum; private Circle drumBackground; @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Taiko.UI Padding = new MarginPadding { Top = TaikoPlayfield.DEFAULT_HEIGHT * 2f, // Visual elements should start right below the playfield - Bottom = -touchInputDrum.DrawHeight * offscreenPercent, // The drum should go past the bottom of the screen so that it can be wider + Bottom = -touchInputDrum.DrawHeight * offscreen_percent, // The drum should go past the bottom of the screen so that it can be wider }; } @@ -151,7 +151,7 @@ namespace osu.Game.Rulesets.Taiko.UI Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; float inputDrumRadius = Math.Max(touchInputDrum.Width, touchInputDrum.DrawHeight) / 2f; - float centreRadius = (inputDrumRadius * touchInputDrum.centre_size); + float centreRadius = (inputDrumRadius * touchInputDrum.CentreSize); return inputPositionToDrumCentreDelta.Length <= centreRadius; } } diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index a7ae763ab5..f1120e44ab 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.UI /// internal class InputDrum : Container { - public float centre_size = 0.7f; + public float CentreSize = 0.7f; private const float middle_split = 0.025f; public InputDrum() @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.UI Scale = new Vector2(0.9f), Children = new Drawable[] { - new TaikoHalfDrum(false, centre_size) + new TaikoHalfDrum(false, CentreSize) { Name = "Left Half", Anchor = Anchor.Centre, @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.UI RimAction = TaikoAction.LeftRim, CentreAction = TaikoAction.LeftCentre }, - new TaikoHalfDrum(true, centre_size) + new TaikoHalfDrum(true, CentreSize) { Name = "Right Half", Anchor = Anchor.Centre, From f3d4cd3f9545040d4b62b22a3e32cafaac5bc6e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Jun 2022 14:36:07 +0900 Subject: [PATCH 020/124] Fix various code inspection issues --- .../DrawableTaikoRulesetTestScene.cs | 2 +- .../TestSceneDrumTouchInputArea.cs | 8 ++++--- .../UI/DrumSamplePlayer.cs | 11 ++++++---- .../UI/DrumTouchInputArea.cs | 22 ++++++++++--------- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 6 ++--- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 2 +- 6 files changed, 29 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs index 2853aa32f4..a099795d12 100644 --- a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs +++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Tests protected DrawableTaikoRuleset DrawableRuleset { get; private set; } protected Container PlayfieldContainer { get; private set; } - protected ControlPointInfo controlPointInfo { get; private set; } + private ControlPointInfo controlPointInfo { get; set; } [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs index e82594cf4f..45555a55c1 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs @@ -28,7 +28,8 @@ namespace osu.Game.Rulesets.Taiko.Tests { List hitObjects = new List(); - for (var i = 0; i < NUM_HIT_OBJECTS; i++) { + for (int i = 0; i < NUM_HIT_OBJECTS; i++) + { hitObjects.Add(new Hit { StartTime = Time.Current + i * HIT_OBJECT_TIME_SPACING_MS, @@ -36,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Type = isOdd(i / 2) ? HitType.Centre : HitType.Rim }); } - + var beatmap = new Beatmap { BeatmapInfo = { Ruleset = ruleset }, @@ -46,7 +47,8 @@ namespace osu.Game.Rulesets.Taiko.Tests return beatmap; } - private bool isOdd(int number) { + private bool isOdd(int number) + { return number % 2 == 0; } } diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs index d5215c05e9..47a9094ed2 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs @@ -12,10 +12,10 @@ namespace osu.Game.Rulesets.Taiko.UI { internal class DrumSamplePlayer : Container, IKeyBindingHandler { - private DrumSampleTriggerSource leftRimSampleTriggerSource; - private DrumSampleTriggerSource leftCentreSampleTriggerSource; - private DrumSampleTriggerSource rightCentreSampleTriggerSource; - private DrumSampleTriggerSource rightRimSampleTriggerSource; + private readonly DrumSampleTriggerSource leftRimSampleTriggerSource; + private readonly DrumSampleTriggerSource leftCentreSampleTriggerSource; + private readonly DrumSampleTriggerSource rightCentreSampleTriggerSource; + private readonly DrumSampleTriggerSource rightRimSampleTriggerSource; public DrumSamplePlayer(HitObjectContainer hitObjectContainer) { @@ -35,12 +35,15 @@ namespace osu.Game.Rulesets.Taiko.UI case TaikoAction.LeftRim: leftRimSampleTriggerSource.Play(HitType.Rim); break; + case TaikoAction.LeftCentre: leftCentreSampleTriggerSource.Play(HitType.Centre); break; + case TaikoAction.RightCentre: rightCentreSampleTriggerSource.Play(HitType.Centre); break; + case TaikoAction.RightRim: rightRimSampleTriggerSource.Play(HitType.Rim); break; diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 959666ffe9..8d1b8a4478 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -24,8 +24,9 @@ namespace osu.Game.Rulesets.Taiko.UI { // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) private const float offscreen_percent = 0.35f; - private InputDrum touchInputDrum; - private Circle drumBackground; + + private readonly InputDrum touchInputDrum; + private readonly Circle drumBackground; private KeyBindingContainer keyBindingContainer; @@ -33,9 +34,9 @@ namespace osu.Game.Rulesets.Taiko.UI private TaikoAction mouseAction; // A map of (Finger Index OnTouchDown -> Which Taiko action was pressed), so that the corresponding action can be released OnTouchUp is released even if the touch position moved - private Dictionary touchActions = new Dictionary(Enum.GetNames(typeof(TouchSource)).Length); + private readonly Dictionary touchActions = new Dictionary(Enum.GetNames(typeof(TouchSource)).Length); - private Container visibleComponents; + private readonly Container visibleComponents; public DrumTouchInputArea() { @@ -43,7 +44,8 @@ namespace osu.Game.Rulesets.Taiko.UI RelativePositionAxes = Axes.Both; Children = new Drawable[] { - visibleComponents = new Container() { + visibleComponents = new Container + { Alpha = 0.0f, RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, @@ -51,7 +53,8 @@ namespace osu.Game.Rulesets.Taiko.UI Origin = Anchor.BottomCentre, Children = new Drawable[] { - drumBackground = new Circle() { + drumBackground = new Circle + { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, Anchor = Anchor.Centre, @@ -59,7 +62,8 @@ namespace osu.Game.Rulesets.Taiko.UI FillMode = FillMode.Fit, Alpha = 0.9f, }, - touchInputDrum = new InputDrum() { + touchInputDrum = new InputDrum + { Anchor = Anchor.Centre, Origin = Anchor.Centre, }, @@ -135,9 +139,7 @@ namespace osu.Game.Rulesets.Taiko.UI bool centreHit = inputIsCenterHit(inputPosition); bool leftSide = inputIsOnLeftSide(inputPosition); - return centreHit ? - (leftSide ? TaikoAction.LeftCentre : TaikoAction.RightCentre) : - (leftSide ? TaikoAction.LeftRim : TaikoAction.RightRim); + return centreHit ? (leftSide ? TaikoAction.LeftCentre : TaikoAction.RightCentre) : (leftSide ? TaikoAction.LeftRim : TaikoAction.RightRim); } private bool inputIsOnLeftSide(Vector2 inputPosition) diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index f1120e44ab..e984bef3ce 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Sprite centre; private readonly Sprite centreHit; - public TaikoHalfDrum(bool flipped, float centre_size) + public TaikoHalfDrum(bool flipped, float centreSize) { Masking = true; @@ -112,14 +112,14 @@ namespace osu.Game.Rulesets.Taiko.UI Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Size = new Vector2(centre_size) + Size = new Vector2(centreSize) }, centreHit = new Sprite { Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Size = new Vector2(centre_size), + Size = new Vector2(centreSize), Alpha = 0, Blending = BlendingParameters.Additive } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index e24f861269..b83364ddf2 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -123,7 +123,7 @@ namespace osu.Game.Rulesets.Taiko.UI Children = new Drawable[] { new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new PlayfieldBackgroundLeft()), - new InputDrum() + new InputDrum { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, From 45adca17da95f3ff1179661a8d87e48e13145806 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Jun 2022 14:39:57 +0900 Subject: [PATCH 021/124] Make `DrumSamplePlayer` a `CompositeDrawable` --- osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs index 47a9094ed2..b65e2af3d8 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Taiko.UI { - internal class DrumSamplePlayer : Container, IKeyBindingHandler + internal class DrumSamplePlayer : CompositeDrawable, IKeyBindingHandler { private readonly DrumSampleTriggerSource leftRimSampleTriggerSource; private readonly DrumSampleTriggerSource leftCentreSampleTriggerSource; @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.UI public DrumSamplePlayer(HitObjectContainer hitObjectContainer) { - Children = new Drawable[] + InternalChildren = new Drawable[] { leftRimSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), leftCentreSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), From 859a83ac900e48466921e3bf869b0d8bc86f9211 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Jun 2022 14:48:26 +0900 Subject: [PATCH 022/124] Remove unused field and fix typo --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs | 1 - osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 7a74567abb..d3eeb8e16a 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -18,7 +18,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy /// internal class LegacyInputDrum : Container { - public float CentreSize = 0.5f; private LegacyHalfDrum left; private LegacyHalfDrum right; private Container content; diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 8d1b8a4478..a66c52df1f 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.UI /// /// An overlay that captures and displays Taiko mouse and touch input. /// The boundaries of this overlay defines the interactable area for touch input. - /// A secondary InputDrum is attached by this overlay, which defines the circulary boundary which distinguishes "centre" from "rim" hits, and also displays input. + /// A secondary InputDrum is attached by this overlay, which defines the circular boundary which distinguishes "centre" from "rim" hits, and also displays input. /// public class DrumTouchInputArea : Container { From c4089b71bd038c7694f358120b34bdd27bd83e2f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 06:00:41 +0300 Subject: [PATCH 023/124] Store maximum score results from simulated autoplay --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 56bbe031e6..031c14a091 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -126,6 +126,9 @@ namespace osu.Game.Rulesets.Scoring private bool beatmapApplied; private readonly Dictionary scoreResultCounts = new Dictionary(); + + private Dictionary? maximumResultCounts; + private readonly List hitEvents = new List(); private HitObject? lastHitObject; @@ -410,12 +413,16 @@ namespace osu.Game.Rulesets.Scoring { base.Reset(storeResults); - scoreResultCounts.Clear(); hitEvents.Clear(); lastHitObject = null; if (storeResults) + { maximumScoringValues = currentScoringValues; + maximumResultCounts = new Dictionary(scoreResultCounts); + } + + scoreResultCounts.Clear(); currentScoringValues = default; currentMaximumScoringValues = default; From 0f0b19da4a78801d12c1242ee8b4cd4b33260768 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 06:01:13 +0300 Subject: [PATCH 024/124] Populate score with remaining "miss" statistics on fail/exit --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 27 +++++++++++++++++++++ osu.Game/Screens/Play/Player.cs | 20 ++++++++------- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 031c14a091..63ad97376f 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -430,6 +430,7 @@ namespace osu.Game.Rulesets.Scoring TotalScore.Value = 0; Accuracy.Value = 1; Combo.Value = 0; + Rank.Disabled = false; Rank.Value = ScoreRank.X; HighestCombo.Value = 0; } @@ -452,6 +453,32 @@ namespace osu.Game.Rulesets.Scoring score.TotalScore = (long)Math.Round(ComputeFinalScore(ScoringMode.Standardised, score)); } + /// + /// Populates the given score with remaining statistics as "missed" and marks it with rank. + /// + public void FailScore(ScoreInfo score) + { + if (Rank.Value == ScoreRank.F) + return; + + Rank.Value = ScoreRank.F; + Rank.Disabled = true; + + Debug.Assert(maximumResultCounts != null); + + if (maximumResultCounts.TryGetValue(HitResult.LargeTickHit, out int maximumLargeTick)) + scoreResultCounts[HitResult.LargeTickMiss] = maximumLargeTick - scoreResultCounts.GetValueOrDefault(HitResult.LargeTickHit); + + if (maximumResultCounts.TryGetValue(HitResult.SmallTickHit, out int maximumSmallTick)) + scoreResultCounts[HitResult.SmallTickMiss] = maximumSmallTick - scoreResultCounts.GetValueOrDefault(HitResult.SmallTickHit); + + int maximumBasic = maximumResultCounts.Single(kvp => kvp.Key.IsBasic()).Value; + int currentBasic = scoreResultCounts.Where(kvp => kvp.Key.IsBasic() && kvp.Key != HitResult.Miss).Sum(kvp => kvp.Value); + scoreResultCounts[HitResult.Miss] = maximumBasic - currentBasic; + + PopulateScore(score); + } + public override void ResetFromReplayFrame(ReplayFrame frame) { base.ResetFromReplayFrame(frame); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 516364e13a..490252ff2b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -267,12 +267,7 @@ namespace osu.Game.Screens.Play }, FailOverlay = new FailOverlay { - SaveReplay = () => - { - Score.ScoreInfo.Passed = false; - Score.ScoreInfo.Rank = ScoreRank.F; - return prepareAndImportScore(); - }, + SaveReplay = prepareAndImportScore, OnRetry = Restart, OnQuit = () => PerformExit(true), }, @@ -831,7 +826,6 @@ namespace osu.Game.Screens.Play return false; GameplayState.HasFailed = true; - Score.ScoreInfo.Passed = false; updateGameplayState(); @@ -849,9 +843,17 @@ namespace osu.Game.Screens.Play return true; } - // Called back when the transform finishes + /// + /// Invoked when the fail animation has finished. + /// private void onFailComplete() { + // fail completion is a good point to mark a score as failed, + // since the last judgement that caused the fail only applies to score processor after onFail. + // todo: this should probably be handled better. + Score.ScoreInfo.Passed = false; + ScoreProcessor.FailScore(Score.ScoreInfo); + GameplayClockContainer.Stop(); FailOverlay.Retries = RestartCount; @@ -1030,7 +1032,7 @@ namespace osu.Game.Screens.Play if (prepareScoreForDisplayTask == null) { Score.ScoreInfo.Passed = false; - Score.ScoreInfo.Rank = ScoreRank.F; + ScoreProcessor.FailScore(Score.ScoreInfo); } // EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous. From 3c2a885872865ba46d4d99c1cc89beacaa5c07a0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 06:01:48 +0300 Subject: [PATCH 025/124] Add test coverage --- .../Gameplay/TestSceneScoreProcessor.cs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index 9e1d786d87..dc6c9158d7 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -15,6 +16,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Tests.Visual; namespace osu.Game.Tests.Gameplay @@ -91,6 +93,41 @@ namespace osu.Game.Tests.Gameplay Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(0)); } + [Test] + public void TestFailScore() + { + var beatmap = new Beatmap + { + HitObjects = + { + new TestHitObject(), + new TestHitObject(HitResult.LargeTickHit), + new TestHitObject(HitResult.SmallTickHit), + new TestHitObject(), + new TestHitObject(HitResult.LargeTickHit), + new TestHitObject(HitResult.SmallTickHit), + } + }; + + var scoreProcessor = new ScoreProcessor(new OsuRuleset()); + scoreProcessor.ApplyBeatmap(beatmap); + + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].CreateJudgement()) { Type = HitResult.Ok }); + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].CreateJudgement()) { Type = HitResult.LargeTickHit }); + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[2], beatmap.HitObjects[2].CreateJudgement()) { Type = HitResult.SmallTickMiss }); + + var score = new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo }; + scoreProcessor.FailScore(score); + + Assert.That(score.Rank, Is.EqualTo(ScoreRank.F)); + Assert.That(score.Statistics.Count(kvp => kvp.Value > 0), Is.EqualTo(5)); + Assert.That(score.Statistics[HitResult.Ok], Is.EqualTo(1)); + Assert.That(score.Statistics[HitResult.Miss], Is.EqualTo(1)); + Assert.That(score.Statistics[HitResult.LargeTickHit], Is.EqualTo(1)); + Assert.That(score.Statistics[HitResult.LargeTickMiss], Is.EqualTo(1)); + Assert.That(score.Statistics[HitResult.SmallTickMiss], Is.EqualTo(2)); + } + private class TestJudgement : Judgement { public override HitResult MaxResult { get; } @@ -100,5 +137,17 @@ namespace osu.Game.Tests.Gameplay MaxResult = maxResult; } } + + private class TestHitObject : HitObject + { + private readonly HitResult maxResult; + + public TestHitObject(HitResult maxResult = HitResult.Perfect) + { + this.maxResult = maxResult; + } + + public override Judgement CreateJudgement() => new TestJudgement(maxResult); + } } } From c1bcbd9c8a01c37a4df9aa9f3c1bb455ea83aaec Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 07:20:58 +0300 Subject: [PATCH 026/124] Fix fail score not handling bonus/tick-only beatmaps --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 63ad97376f..7fd21ddc54 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -472,7 +472,7 @@ namespace osu.Game.Rulesets.Scoring if (maximumResultCounts.TryGetValue(HitResult.SmallTickHit, out int maximumSmallTick)) scoreResultCounts[HitResult.SmallTickMiss] = maximumSmallTick - scoreResultCounts.GetValueOrDefault(HitResult.SmallTickHit); - int maximumBasic = maximumResultCounts.Single(kvp => kvp.Key.IsBasic()).Value; + int maximumBasic = maximumResultCounts.SingleOrDefault(kvp => kvp.Key.IsBasic()).Value; int currentBasic = scoreResultCounts.Where(kvp => kvp.Key.IsBasic() && kvp.Key != HitResult.Miss).Sum(kvp => kvp.Value); scoreResultCounts[HitResult.Miss] = maximumBasic - currentBasic; From ad09e728fd5dd5c1b1f94672f8486ec066b778f3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 08:12:06 +0300 Subject: [PATCH 027/124] Move `Passed` assignment inside `FailScore` --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 ++ osu.Game/Screens/Play/Player.cs | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 7fd21ddc54..ce9e06ce9c 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -461,6 +461,8 @@ namespace osu.Game.Rulesets.Scoring if (Rank.Value == ScoreRank.F) return; + score.Passed = false; + Rank.Value = ScoreRank.F; Rank.Disabled = true; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 490252ff2b..9a058e45c5 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -851,7 +851,6 @@ namespace osu.Game.Screens.Play // fail completion is a good point to mark a score as failed, // since the last judgement that caused the fail only applies to score processor after onFail. // todo: this should probably be handled better. - Score.ScoreInfo.Passed = false; ScoreProcessor.FailScore(Score.ScoreInfo); GameplayClockContainer.Stop(); @@ -1030,10 +1029,7 @@ namespace osu.Game.Screens.Play // if arriving here and the results screen preparation task hasn't run, it's safe to say the user has not completed the beatmap. if (prepareScoreForDisplayTask == null) - { - Score.ScoreInfo.Passed = false; ScoreProcessor.FailScore(Score.ScoreInfo); - } // EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous. // To resolve test failures, forcefully end playing synchronously when this screen exits. From 9df49db45ff3a5a740221eab9bf618a3b98100dc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 08:12:46 +0300 Subject: [PATCH 028/124] Include bonus/ignore judgements in statistics fill logic --- osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs | 8 +++++++- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index dc6c9158d7..4123412ab6 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -103,9 +103,11 @@ namespace osu.Game.Tests.Gameplay new TestHitObject(), new TestHitObject(HitResult.LargeTickHit), new TestHitObject(HitResult.SmallTickHit), + new TestHitObject(HitResult.SmallBonus), new TestHitObject(), new TestHitObject(HitResult.LargeTickHit), new TestHitObject(HitResult.SmallTickHit), + new TestHitObject(HitResult.LargeBonus), } }; @@ -115,17 +117,21 @@ namespace osu.Game.Tests.Gameplay scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].CreateJudgement()) { Type = HitResult.Ok }); scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].CreateJudgement()) { Type = HitResult.LargeTickHit }); scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[2], beatmap.HitObjects[2].CreateJudgement()) { Type = HitResult.SmallTickMiss }); + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[3], beatmap.HitObjects[3].CreateJudgement()) { Type = HitResult.SmallBonus }); var score = new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo }; scoreProcessor.FailScore(score); Assert.That(score.Rank, Is.EqualTo(ScoreRank.F)); - Assert.That(score.Statistics.Count(kvp => kvp.Value > 0), Is.EqualTo(5)); + Assert.That(score.Passed, Is.False); + Assert.That(score.Statistics.Count(kvp => kvp.Value > 0), Is.EqualTo(7)); Assert.That(score.Statistics[HitResult.Ok], Is.EqualTo(1)); Assert.That(score.Statistics[HitResult.Miss], Is.EqualTo(1)); Assert.That(score.Statistics[HitResult.LargeTickHit], Is.EqualTo(1)); Assert.That(score.Statistics[HitResult.LargeTickMiss], Is.EqualTo(1)); Assert.That(score.Statistics[HitResult.SmallTickMiss], Is.EqualTo(2)); + Assert.That(score.Statistics[HitResult.SmallBonus], Is.EqualTo(1)); + Assert.That(score.Statistics[HitResult.IgnoreMiss], Is.EqualTo(1)); } private class TestJudgement : Judgement diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index ce9e06ce9c..56d025e9a5 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -474,6 +474,10 @@ namespace osu.Game.Rulesets.Scoring if (maximumResultCounts.TryGetValue(HitResult.SmallTickHit, out int maximumSmallTick)) scoreResultCounts[HitResult.SmallTickMiss] = maximumSmallTick - scoreResultCounts.GetValueOrDefault(HitResult.SmallTickHit); + int maximumBonusOrIgnore = maximumResultCounts.Where(kvp => kvp.Key.IsBonus() || kvp.Key == HitResult.IgnoreHit).Sum(kvp => kvp.Value); + int currentBonusOrIgnore = scoreResultCounts.Where(kvp => kvp.Key.IsBonus() || kvp.Key == HitResult.IgnoreHit).Sum(kvp => kvp.Value); + scoreResultCounts[HitResult.IgnoreMiss] = maximumBonusOrIgnore - currentBonusOrIgnore; + int maximumBasic = maximumResultCounts.SingleOrDefault(kvp => kvp.Key.IsBasic()).Value; int currentBasic = scoreResultCounts.Where(kvp => kvp.Key.IsBasic() && kvp.Key != HitResult.Miss).Sum(kvp => kvp.Value); scoreResultCounts[HitResult.Miss] = maximumBasic - currentBasic; From a97170a2723e6de2f436b08485c54734c7108829 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 08:16:17 +0300 Subject: [PATCH 029/124] Keep `Rank` bindable enabled on score fail --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 56d025e9a5..59fee0f97b 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -462,9 +462,7 @@ namespace osu.Game.Rulesets.Scoring return; score.Passed = false; - Rank.Value = ScoreRank.F; - Rank.Disabled = true; Debug.Assert(maximumResultCounts != null); From aca19a005ef1db984b315d5aa529f207b0ddac7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 02:05:18 +0900 Subject: [PATCH 030/124] Add versioning to difficulty calculators --- .../Difficulty/CatchDifficultyCalculator.cs | 2 ++ .../Difficulty/ManiaDifficultyCalculator.cs | 2 ++ osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 2 ++ .../Difficulty/TaikoDifficultyCalculator.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 5 +++++ 5 files changed, 13 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index b64d860417..f37479f84a 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty private float halfCatcherWidth; + public override int Version => 20220701; + public CatchDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) { diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 178094476f..5d30d33190 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -31,6 +31,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty private readonly bool isForCurrentRuleset; private readonly double originalOverallDifficulty; + public override int Version => 20220701; + public ManiaDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) { diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 75d9469da3..9f4a405113 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty private const double difficulty_multiplier = 0.0675; private double hitWindowGreat; + public override int Version => 20220701; + public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) { diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 228856cbe9..9267d1ee3c 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private const double colour_skill_multiplier = 0.01; private const double stamina_skill_multiplier = 0.021; + public override int Version => 20220701; + public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) { diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 9c58a53b97..8dd1b51cae 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -34,6 +34,11 @@ namespace osu.Game.Rulesets.Difficulty private readonly IRulesetInfo ruleset; private readonly IWorkingBeatmap beatmap; + /// + /// A yymmdd version which is used to discern when reprocessing is required. + /// + public virtual int Version => 0; + protected DifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) { this.ruleset = ruleset; From 68f28ff660183a869fc2803b98beaec13dfdce1e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 02:27:29 +0900 Subject: [PATCH 031/124] Add last applied version to `RulesetInfo` --- osu.Game/Database/RealmAccess.cs | 3 ++- osu.Game/Rulesets/RulesetInfo.cs | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index dff2bdddbd..7aac94576f 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -63,8 +63,9 @@ namespace osu.Game.Database /// 17 2022-07-16 Added CountryCode to RealmUser. /// 18 2022-07-19 Added OnlineMD5Hash and LastOnlineUpdate to BeatmapInfo. /// 19 2022-07-19 Added DateSubmitted and DateRanked to BeatmapSetInfo. + /// 20 2022-07-21 Added LastAppliedDifficultyVersion to RulesetInfo. /// - private const int schema_version = 19; + private const int schema_version = 20; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index b5e5fa1561..91954320a4 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -4,6 +4,7 @@ using System; using JetBrains.Annotations; using osu.Framework.Testing; +using osu.Game.Rulesets.Difficulty; using Realms; namespace osu.Game.Rulesets @@ -22,6 +23,11 @@ namespace osu.Game.Rulesets public string InstantiationInfo { get; set; } = string.Empty; + /// + /// Stores the last applied + /// + public int LastAppliedDifficultyVersion { get; set; } + public RulesetInfo(string shortName, string name, string instantiationInfo, int onlineID) { ShortName = shortName; @@ -86,7 +92,8 @@ namespace osu.Game.Rulesets Name = Name, ShortName = ShortName, InstantiationInfo = InstantiationInfo, - Available = Available + Available = Available, + LastAppliedDifficultyVersion = LastAppliedDifficultyVersion, }; public Ruleset CreateInstance() From 57a41c689756f8372ea06fb9eb040bf27940ca37 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 03:18:57 +0900 Subject: [PATCH 032/124] Add basic background processor --- osu.Game/BackgroundBeatmapProcessor.cs | 112 +++++++++++++++++++++++++ osu.Game/OsuGame.cs | 2 + osu.Game/OsuGameBase.cs | 3 +- 3 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 osu.Game/BackgroundBeatmapProcessor.cs diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs new file mode 100644 index 0000000000..09db107744 --- /dev/null +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -0,0 +1,112 @@ +// 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.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Logging; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Rulesets; + +namespace osu.Game +{ + public class BackgroundBeatmapProcessor : Component + { + [Resolved] + private RulesetStore rulesetStore { get; set; } = null!; + + [Resolved] + private RealmAccess realmAccess { get; set; } = null!; + + [Resolved] + private BeatmapUpdater beatmapUpdater { get; set; } = null!; + + [Resolved] + private IBindable gameBeatmap { get; set; } = null!; + + protected override void LoadComplete() + { + base.LoadComplete(); + + Task.Run(() => + { + Logger.Log("Beginning background beatmap processing.."); + checkForOutdatedStarRatings(); + processBeatmapSetsWithMissingMetrics(); + Logger.Log("Finished background beatmap processing!"); + }); + } + + /// + /// Check whether the databased difficulty calculation version matches the latest ruleset provided version. + /// If it doesn't, clear out any existing difficulties so they can be incrementally recalculated. + /// + private void checkForOutdatedStarRatings() + { + foreach (var ruleset in rulesetStore.AvailableRulesets) + { + // beatmap being passed in is arbitrary here. just needs to be non-null. + int currentVersion = ruleset.CreateInstance().CreateDifficultyCalculator(gameBeatmap.Value).Version; + + if (ruleset.LastAppliedDifficultyVersion < currentVersion) + { + Logger.Log($"Resetting star ratings for {ruleset.Name} (difficulty calculation version updated from {ruleset.LastAppliedDifficultyVersion} to {currentVersion})"); + + int countReset = 0; + + realmAccess.Write(r => + { + foreach (var b in r.All()) + { + if (b.Ruleset.ShortName == ruleset.ShortName) + { + b.StarRating = 0; + countReset++; + } + } + + r.Find(ruleset.ShortName).LastAppliedDifficultyVersion = currentVersion; + }); + + Logger.Log($"Finished resetting {countReset} beatmap sets for {ruleset.Name}"); + } + } + } + + private void processBeatmapSetsWithMissingMetrics() + { + // TODO: rate limit and pause processing during gameplay. + + HashSet beatmapSetIds = new HashSet(); + + realmAccess.Run(r => + { + foreach (var b in r.All().Where(b => b.StarRating == 0 || (b.OnlineID > 0 && b.LastOnlineUpdate == null))) + { + Debug.Assert(b.BeatmapSet != null); + beatmapSetIds.Add(b.BeatmapSet.ID); + } + }); + + foreach (var id in beatmapSetIds) + { + realmAccess.Run(r => + { + var set = r.Find(id); + + if (set != null) + { + Logger.Log($"Background processing {set}"); + beatmapUpdater.Process(set); + } + }); + } + } + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 69d02b95ff..4f08511783 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -904,6 +904,8 @@ namespace osu.Game loadComponentSingleFile(CreateHighPerformanceSession(), Add); + loadComponentSingleFile(new BackgroundBeatmapProcessor(), Add); + chatOverlay.State.BindValueChanged(_ => updateChatPollRate()); // Multiplayer modes need to increase poll rate temporarily. API.Activity.BindValueChanged(_ => updateChatPollRate(), true); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 6a5c6835ba..3b81b5c8cd 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -280,8 +280,7 @@ namespace osu.Game AddInternal(difficultyCache); // TODO: OsuGame or OsuGameBase? - beatmapUpdater = new BeatmapUpdater(BeatmapManager, difficultyCache, API, Storage); - + dependencies.CacheAs(beatmapUpdater = new BeatmapUpdater(BeatmapManager, difficultyCache, API, Storage)); dependencies.CacheAs(spectatorClient = new OnlineSpectatorClient(endpoints)); dependencies.CacheAs(multiplayerClient = new OnlineMultiplayerClient(endpoints)); dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints)); From 04f48d88627bf2cfe44f26871968a50cb204f8a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 03:55:05 +0900 Subject: [PATCH 033/124] Add better log output and sleeping during gameplay sections --- osu.Game/BackgroundBeatmapProcessor.cs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs index 09db107744..8cc725a3f8 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -13,6 +14,7 @@ using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Rulesets; +using osu.Game.Screens.Play; namespace osu.Game { @@ -30,6 +32,9 @@ namespace osu.Game [Resolved] private IBindable gameBeatmap { get; set; } = null!; + [Resolved] + private ILocalUserPlayInfo? localUserPlayInfo { get; set; } + protected override void LoadComplete() { base.LoadComplete(); @@ -81,10 +86,10 @@ namespace osu.Game private void processBeatmapSetsWithMissingMetrics() { - // TODO: rate limit and pause processing during gameplay. - HashSet beatmapSetIds = new HashSet(); + Logger.Log("Querying for beatmap sets to reprocess..."); + realmAccess.Run(r => { foreach (var b in r.All().Where(b => b.StarRating == 0 || (b.OnlineID > 0 && b.LastOnlineUpdate == null))) @@ -94,15 +99,25 @@ namespace osu.Game } }); + Logger.Log($"Found {beatmapSetIds.Count} beatmap sets which require reprocessing."); + + int i = 0; + foreach (var id in beatmapSetIds) { + while (localUserPlayInfo?.IsPlaying.Value == true) + { + Logger.Log("Background processing sleeping 30s due to active gameplay..."); + Thread.Sleep(30000); + } + realmAccess.Run(r => { var set = r.Find(id); if (set != null) { - Logger.Log($"Background processing {set}"); + Logger.Log($"Background processing {set} ({++i} / {beatmapSetIds.Count})"); beatmapUpdater.Process(set); } }); From d5e0dba9da649ebfbc19bac7e19e5b5871081231 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 17:39:07 +0900 Subject: [PATCH 034/124] Change default value of `StarRating` to `-1` --- osu.Game/BackgroundBeatmapProcessor.cs | 4 ++-- osu.Game/Beatmaps/BeatmapInfo.cs | 6 +++++- osu.Game/Database/RealmAccess.cs | 12 +++++++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs index 8cc725a3f8..bd3fab9135 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -71,7 +71,7 @@ namespace osu.Game { if (b.Ruleset.ShortName == ruleset.ShortName) { - b.StarRating = 0; + b.StarRating = -1; countReset++; } } @@ -92,7 +92,7 @@ namespace osu.Game realmAccess.Run(r => { - foreach (var b in r.All().Where(b => b.StarRating == 0 || (b.OnlineID > 0 && b.LastOnlineUpdate == null))) + foreach (var b in r.All().Where(b => b.StarRating < 0 || (b.OnlineID > 0 && b.LastOnlineUpdate == null))) { Debug.Assert(b.BeatmapSet != null); beatmapSetIds.Add(b.BeatmapSet.ID); diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 3ee306cc9a..466448d771 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -87,7 +87,11 @@ namespace osu.Game.Beatmaps public string Hash { get; set; } = string.Empty; - public double StarRating { get; set; } + /// + /// Defaults to -1 (meaning not-yet-calculated). + /// Will likely be superseded with a better storage considering ruleset/mods. + /// + public double StarRating { get; set; } = -1; [Indexed] public string MD5Hash { get; set; } = string.Empty; diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 7aac94576f..2754696130 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -63,7 +63,7 @@ namespace osu.Game.Database /// 17 2022-07-16 Added CountryCode to RealmUser. /// 18 2022-07-19 Added OnlineMD5Hash and LastOnlineUpdate to BeatmapInfo. /// 19 2022-07-19 Added DateSubmitted and DateRanked to BeatmapSetInfo. - /// 20 2022-07-21 Added LastAppliedDifficultyVersion to RulesetInfo. + /// 20 2022-07-21 Added LastAppliedDifficultyVersion to RulesetInfo, changed default value of BeatmapInfo.StarRating to -1. /// private const int schema_version = 20; @@ -781,6 +781,16 @@ namespace osu.Game.Database case 14: foreach (var beatmap in migration.NewRealm.All()) beatmap.UserSettings = new BeatmapUserSettings(); + + break; + + case 20: + foreach (var beatmap in migration.NewRealm.All()) + { + if (beatmap.StarRating == 0) + beatmap.StarRating = -1; + } + break; } } From 1374738a0da5fbc024862819c5b09172365c88fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 18:15:21 +0900 Subject: [PATCH 035/124] Add test coverage --- .../BackgroundBeatmapProcessorTests.cs | 132 ++++++++++++++++++ osu.Game/BackgroundBeatmapProcessor.cs | 6 +- 2 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs diff --git a/osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs b/osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs new file mode 100644 index 0000000000..4012e3f851 --- /dev/null +++ b/osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs @@ -0,0 +1,132 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps.IO; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Database +{ + [HeadlessTest] + public class BackgroundBeatmapProcessorTests : OsuTestScene, ILocalUserPlayInfo + { + public IBindable IsPlaying => isPlaying; + + private readonly Bindable isPlaying = new Bindable(); + + private BeatmapSetInfo importedSet = null!; + + [BackgroundDependencyLoader] + private void load(OsuGameBase osu) + { + importedSet = BeatmapImportHelper.LoadQuickOszIntoOsu(osu).GetResultSafely(); + } + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Set not playing", () => isPlaying.Value = false); + } + + [Test] + public void TestDifficultyProcessing() + { + AddAssert("Difficulty is initially set", () => + { + return Realm.Run(r => + { + var beatmapSetInfo = r.Find(importedSet.ID); + return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0); + }); + }); + + AddStep("Reset difficulty", () => + { + Realm.Write(r => + { + var beatmapSetInfo = r.Find(importedSet.ID); + foreach (var b in beatmapSetInfo.Beatmaps) + b.StarRating = -1; + }); + }); + + AddStep("Run background processor", () => + { + Add(new TestBackgroundBeatmapProcessor()); + }); + + AddUntilStep("wait for difficulties repopulated", () => + { + return Realm.Run(r => + { + var beatmapSetInfo = r.Find(importedSet.ID); + return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0); + }); + }); + } + + [Test] + public void TestDifficultyProcessingWhilePlaying() + { + AddAssert("Difficulty is initially set", () => + { + return Realm.Run(r => + { + var beatmapSetInfo = r.Find(importedSet.ID); + return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0); + }); + }); + + AddStep("Set playing", () => isPlaying.Value = true); + + AddStep("Reset difficulty", () => + { + Realm.Write(r => + { + var beatmapSetInfo = r.Find(importedSet.ID); + foreach (var b in beatmapSetInfo.Beatmaps) + b.StarRating = -1; + }); + }); + + AddStep("Run background processor", () => + { + Add(new TestBackgroundBeatmapProcessor()); + }); + + AddWaitStep("wait some", 500); + + AddAssert("Difficulty still not populated", () => + { + return Realm.Run(r => + { + var beatmapSetInfo = r.Find(importedSet.ID); + return beatmapSetInfo.Beatmaps.All(b => b.StarRating == -1); + }); + }); + + AddStep("Set not playing", () => isPlaying.Value = false); + + AddUntilStep("wait for difficulties repopulated", () => + { + return Realm.Run(r => + { + var beatmapSetInfo = r.Find(importedSet.ID); + return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0); + }); + }); + } + + public class TestBackgroundBeatmapProcessor : BackgroundBeatmapProcessor + { + protected override int TimeToSleepDuringGameplay => 10; + } + } +} diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs index bd3fab9135..9b04f487a8 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -35,6 +35,8 @@ namespace osu.Game [Resolved] private ILocalUserPlayInfo? localUserPlayInfo { get; set; } + protected virtual int TimeToSleepDuringGameplay => 30000; + protected override void LoadComplete() { base.LoadComplete(); @@ -107,8 +109,8 @@ namespace osu.Game { while (localUserPlayInfo?.IsPlaying.Value == true) { - Logger.Log("Background processing sleeping 30s due to active gameplay..."); - Thread.Sleep(30000); + Logger.Log("Background processing sleeping due to active gameplay..."); + Thread.Sleep(TimeToSleepDuringGameplay); } realmAccess.Run(r => From 94cd641fb4ca493a8e30972134defcf220efce8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 18:27:31 +0900 Subject: [PATCH 036/124] Change migration to trigger reprocessing on every local beatmap Was originally relying on the fact that this would be triggered due to a null `LastOnlineUpdate`, but wouldn't cover the case of beatmaps with no `OnlineID`. --- osu.Game/Database/RealmAccess.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 2754696130..826341a5b9 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -785,11 +785,10 @@ namespace osu.Game.Database break; case 20: + // As we now have versioned difficulty calculations, let's reset + // all star ratings and have `BackgroundBeatmapProcessor` recalculate them. foreach (var beatmap in migration.NewRealm.All()) - { - if (beatmap.StarRating == 0) - beatmap.StarRating = -1; - } + beatmap.StarRating = -1; break; } From a4f071fe53e2d1d3e105ffdf0d20c4f65eec5db0 Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Thu, 21 Jul 2022 08:26:48 -0500 Subject: [PATCH 037/124] Make zoom sensitivity relative to containers max zoom --- .../Screens/Edit/Compose/Components/Timeline/TimelineArea.cs | 4 +++- .../Compose/Components/Timeline/ZoomableScrollContainer.cs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index 97dc04b9fa..c25a4834d3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -20,6 +20,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly Drawable userContent; + private const float zoom_button_sensitivity = 0.02f; + public TimelineArea(Drawable content = null) { RelativeSizeAxes = Axes.X; @@ -154,6 +156,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Timeline.TicksVisible.BindTo(ticksCheckbox.Current); } - private void changeZoom(float change) => Timeline.Zoom += change; + private void changeZoom(float change) => Timeline.Zoom += change * Timeline.MaxZoom * zoom_button_sensitivity; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 80cdef38e9..7a4d45301f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -104,6 +104,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline set => updateZoom(value); } + private const float zoom_scroll_sensitivity = 0.02f; + private void updateZoom(float? value = null) { float newZoom = Math.Clamp(value ?? Zoom, MinZoom, MaxZoom); @@ -127,7 +129,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (e.AltPressed) { // zoom when holding alt. - setZoomTarget(zoomTarget + e.ScrollDelta.Y, zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); + setZoomTarget(zoomTarget + e.ScrollDelta.Y * MaxZoom * zoom_scroll_sensitivity, zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); return true; } From 3fad481a96799fecc55bb1819a4a28ed2f12972a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 22:27:22 +0900 Subject: [PATCH 038/124] Avoid using `RollingCounter` in fps counter It wasn't made to be updated every frame, and it shows. Inaccurate for reasons I'm not really interested in investigating, because I don't want to incur the `Transorm` overhead in the first place for an fps counter. Was only used originally out of convenience. --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 57 ++++++++++++------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 91e0429c85..6982f94abc 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -22,8 +22,8 @@ namespace osu.Game.Graphics.UserInterface { public class FPSCounter : VisibilityContainer, IHasCustomTooltip { - private RollingCounter counterUpdateFrameTime = null!; - private RollingCounter counterDrawFPS = null!; + private OsuSpriteText counterUpdateFrameTime = null!; + private OsuSpriteText counterDrawFPS = null!; private Container mainContent = null!; @@ -35,6 +35,9 @@ namespace osu.Game.Graphics.UserInterface private readonly BindableBool showFpsDisplay = new BindableBool(true); + private double displayedFpsCount; + private double displayedFrameTime; + [Resolved] private OsuColour colours { get; set; } = null!; @@ -77,20 +80,23 @@ namespace osu.Game.Graphics.UserInterface AutoSizeAxes = Axes.Both, Children = new Drawable[] { - counterUpdateFrameTime = new FrameTimeCounter + counterUpdateFrameTime = new OsuSpriteText { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Margin = new MarginPadding(1), + Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold), + Spacing = new Vector2(-1), Y = -2, }, - counterDrawFPS = new FramesPerSecondCounter + counterDrawFPS = new OsuSpriteText { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Margin = new MarginPadding(2), + Font = OsuFont.Default.With(fixedWidth: true, size: 13, weight: FontWeight.SemiBold), + Spacing = new Vector2(-2), Y = 10, - Scale = new Vector2(0.8f), } } }, @@ -183,37 +189,48 @@ namespace osu.Game.Graphics.UserInterface const double spike_time_ms = 20; - bool hasUpdateSpike = counterUpdateFrameTime.Current.Value < spike_time_ms && newUpdateFrameTime > spike_time_ms; + bool hasUpdateSpike = displayedFrameTime < spike_time_ms && newUpdateFrameTime > spike_time_ms; // use elapsed frame time rather then FramesPerSecond to better catch stutter frames. - bool hasDrawSpike = counterDrawFPS.Current.Value > (1000 / spike_time_ms) && newDrawFrameTime > spike_time_ms; + bool hasDrawSpike = displayedFpsCount > (1000 / spike_time_ms) && newDrawFrameTime > spike_time_ms; - // If the frame time spikes up, make sure it shows immediately on the counter. - if (hasUpdateSpike) - counterUpdateFrameTime.SetCountWithoutRolling(newUpdateFrameTime); - else - counterUpdateFrameTime.Current.Value = newUpdateFrameTime; + // note that we use an elapsed time here of 1 intentionally. + // this weights all updates equally. if we passed in the elapsed time, longer frames would be weighted incorrectly lower. + displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, newUpdateFrameTime, hasUpdateSpike ? 0 : 200, 1); if (hasDrawSpike) // show spike time using raw elapsed value, to account for `FramesPerSecond` being so averaged spike frames don't show. - counterDrawFPS.SetCountWithoutRolling(1000 / newDrawFrameTime); + displayedFpsCount = 1000 / newDrawFrameTime; else - counterDrawFPS.Current.Value = newDrawFps; + displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, newDrawFps, 200, Time.Elapsed); - counterDrawFPS.Colour = getColour(counterDrawFPS.DisplayedCount / aimDrawFPS); - - double displayedUpdateFPS = 1000 / counterUpdateFrameTime.DisplayedCount; - counterUpdateFrameTime.Colour = getColour(displayedUpdateFPS / aimUpdateFPS); + updateFpsDisplay(); + updateFrameTimeDisplay(); bool hasSignificantChanges = aimRatesChanged || hasDrawSpike || hasUpdateSpike - || counterDrawFPS.DisplayedCount < aimDrawFPS * 0.8 - || displayedUpdateFPS < aimUpdateFPS * 0.8; + || displayedFpsCount < aimDrawFPS * 0.8 + || 1000 / displayedFrameTime < aimUpdateFPS * 0.8; if (hasSignificantChanges) displayTemporarily(); } + private void updateFpsDisplay() + { + counterDrawFPS.Colour = getColour(displayedFpsCount / aimDrawFPS); + counterDrawFPS.Text = $"{displayedFpsCount:#,0}fps"; + } + + private void updateFrameTimeDisplay() + { + counterUpdateFrameTime.Text = displayedFrameTime < 5 + ? $"{displayedFrameTime:N1}ms" + : $"{displayedFrameTime:N0}ms"; + + counterUpdateFrameTime.Colour = getColour((1000 / displayedFrameTime) / aimUpdateFPS); + } + private bool updateAimFPS() { if (gameHost.UpdateThread.Clock.Throttling) From 3d2603e0eb149451919950b4d3c350642070f829 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 22:51:35 +0900 Subject: [PATCH 039/124] Remove unused classes --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 46 ------------------- 1 file changed, 46 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 6982f94abc..d9d8a03b29 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; -using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Framework.Threading; using osu.Framework.Utils; @@ -270,50 +269,5 @@ namespace osu.Game.Graphics.UserInterface public ITooltip GetCustomTooltip() => new FPSCounterTooltip(); public object TooltipContent => this; - - public class FramesPerSecondCounter : RollingCounter - { - protected override double RollingDuration => 1000; - - protected override OsuSpriteText CreateSpriteText() - { - return new OsuSpriteText - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold), - Spacing = new Vector2(-2), - }; - } - - protected override LocalisableString FormatCount(double count) - { - return $"{count:#,0}fps"; - } - } - - public class FrameTimeCounter : RollingCounter - { - protected override double RollingDuration => 1000; - - protected override OsuSpriteText CreateSpriteText() - { - return new OsuSpriteText - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold), - Spacing = new Vector2(-1), - }; - } - - protected override LocalisableString FormatCount(double count) - { - if (count < 1) - return $"{count:N1}ms"; - - return $"{count:N0}ms"; - } - } } } From fc6445caeab481a09e5aa1793c64ba9b9b19fbf7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 22:55:45 +0900 Subject: [PATCH 040/124] Rate limit updates for good measure --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index d9d8a03b29..825bde9d2d 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -30,6 +30,10 @@ namespace osu.Game.Graphics.UserInterface private Container counters = null!; + private const double min_time_between_updates = 10; + + private const double spike_time_ms = 20; + private const float idle_background_alpha = 0.4f; private readonly BindableBool showFpsDisplay = new BindableBool(true); @@ -147,6 +151,8 @@ namespace osu.Game.Graphics.UserInterface private double aimDrawFPS; private double aimUpdateFPS; + private double lastUpdate; + private void displayTemporarily() { if (!isDisplayed) @@ -186,8 +192,6 @@ namespace osu.Game.Graphics.UserInterface double newDrawFrameTime = gameHost.DrawThread.Clock.ElapsedFrameTime; double newDrawFps = gameHost.DrawThread.Clock.FramesPerSecond; - const double spike_time_ms = 20; - bool hasUpdateSpike = displayedFrameTime < spike_time_ms && newUpdateFrameTime > spike_time_ms; // use elapsed frame time rather then FramesPerSecond to better catch stutter frames. bool hasDrawSpike = displayedFpsCount > (1000 / spike_time_ms) && newDrawFrameTime > spike_time_ms; @@ -202,8 +206,13 @@ namespace osu.Game.Graphics.UserInterface else displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, newDrawFps, 200, Time.Elapsed); - updateFpsDisplay(); - updateFrameTimeDisplay(); + if (Time.Current - lastUpdate > min_time_between_updates) + { + updateFpsDisplay(); + updateFrameTimeDisplay(); + + lastUpdate = Time.Current; + } bool hasSignificantChanges = aimRatesChanged || hasDrawSpike From c140601c2de65aeb6ba5bcd58a68246ad08e3dfb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 23:02:31 +0900 Subject: [PATCH 041/124] Cleanup pass on `FPSCounter` --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 825bde9d2d..0db54c3947 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Platform; using osu.Framework.Threading; +using osu.Framework.Timing; using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Graphics.Sprites; @@ -41,6 +42,18 @@ namespace osu.Game.Graphics.UserInterface private double displayedFpsCount; private double displayedFrameTime; + private bool isDisplayed; + + private ScheduledDelegate? fadeOutDelegate; + + private double aimDrawFPS; + private double aimUpdateFPS; + + private double lastUpdate; + private ThrottledFrameClock drawClock = null!; + private ThrottledFrameClock updateClock = null!; + private ThrottledFrameClock inputClock = null!; + [Resolved] private OsuColour colours { get; set; } = null!; @@ -50,7 +63,7 @@ namespace osu.Game.Graphics.UserInterface } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, GameHost gameHost) { InternalChildren = new Drawable[] { @@ -108,6 +121,10 @@ namespace osu.Game.Graphics.UserInterface }; config.BindWith(OsuSetting.ShowFpsDisplay, showFpsDisplay); + + drawClock = gameHost.DrawThread.Clock; + updateClock = gameHost.UpdateThread.Clock; + inputClock = gameHost.InputThread.Clock; } protected override void LoadComplete() @@ -144,15 +161,6 @@ namespace osu.Game.Graphics.UserInterface base.OnHoverLost(e); } - private bool isDisplayed; - - private ScheduledDelegate? fadeOutDelegate; - - private double aimDrawFPS; - private double aimUpdateFPS; - - private double lastUpdate; - private void displayTemporarily() { if (!isDisplayed) @@ -174,9 +182,6 @@ namespace osu.Game.Graphics.UserInterface } } - [Resolved] - private GameHost gameHost { get; set; } = null!; - protected override void Update() { base.Update(); @@ -187,24 +192,19 @@ namespace osu.Game.Graphics.UserInterface // frame limiter (we want to show the FPS as it's changing, even if it isn't an outlier). bool aimRatesChanged = updateAimFPS(); - // TODO: this is wrong (elapsed clock time, not actual run time). - double newUpdateFrameTime = gameHost.UpdateThread.Clock.ElapsedFrameTime; - double newDrawFrameTime = gameHost.DrawThread.Clock.ElapsedFrameTime; - double newDrawFps = gameHost.DrawThread.Clock.FramesPerSecond; - - bool hasUpdateSpike = displayedFrameTime < spike_time_ms && newUpdateFrameTime > spike_time_ms; + bool hasUpdateSpike = displayedFrameTime < spike_time_ms && updateClock.ElapsedFrameTime > spike_time_ms; // use elapsed frame time rather then FramesPerSecond to better catch stutter frames. - bool hasDrawSpike = displayedFpsCount > (1000 / spike_time_ms) && newDrawFrameTime > spike_time_ms; + bool hasDrawSpike = displayedFpsCount > (1000 / spike_time_ms) && drawClock.ElapsedFrameTime > spike_time_ms; // note that we use an elapsed time here of 1 intentionally. // this weights all updates equally. if we passed in the elapsed time, longer frames would be weighted incorrectly lower. - displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, newUpdateFrameTime, hasUpdateSpike ? 0 : 200, 1); + displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, updateClock.ElapsedFrameTime, hasUpdateSpike ? 0 : 200, 1); if (hasDrawSpike) // show spike time using raw elapsed value, to account for `FramesPerSecond` being so averaged spike frames don't show. - displayedFpsCount = 1000 / newDrawFrameTime; + displayedFpsCount = 1000 / drawClock.ElapsedFrameTime; else - displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, newDrawFps, 200, Time.Elapsed); + displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, drawClock.FramesPerSecond, 200, Time.Elapsed); if (Time.Current - lastUpdate > min_time_between_updates) { @@ -241,10 +241,10 @@ namespace osu.Game.Graphics.UserInterface private bool updateAimFPS() { - if (gameHost.UpdateThread.Clock.Throttling) + if (updateClock.Throttling) { - double newAimDrawFPS = gameHost.DrawThread.Clock.MaximumUpdateHz; - double newAimUpdateFPS = gameHost.UpdateThread.Clock.MaximumUpdateHz; + double newAimDrawFPS = drawClock.MaximumUpdateHz; + double newAimUpdateFPS = updateClock.MaximumUpdateHz; if (aimDrawFPS != newAimDrawFPS || aimUpdateFPS != newAimUpdateFPS) { @@ -255,7 +255,7 @@ namespace osu.Game.Graphics.UserInterface } else { - double newAimFPS = gameHost.InputThread.Clock.MaximumUpdateHz; + double newAimFPS = inputClock.MaximumUpdateHz; if (aimDrawFPS != newAimFPS || aimUpdateFPS != newAimFPS) { From 0eeafea50002d18f76a27ff390075ab859030893 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 23:37:32 +0900 Subject: [PATCH 042/124] Increase responsiveness to change slightly --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index d9d8a03b29..07eb9fa3ca 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -194,13 +194,13 @@ namespace osu.Game.Graphics.UserInterface // note that we use an elapsed time here of 1 intentionally. // this weights all updates equally. if we passed in the elapsed time, longer frames would be weighted incorrectly lower. - displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, newUpdateFrameTime, hasUpdateSpike ? 0 : 200, 1); + displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, newUpdateFrameTime, hasUpdateSpike ? 0 : 100, 1); if (hasDrawSpike) // show spike time using raw elapsed value, to account for `FramesPerSecond` being so averaged spike frames don't show. displayedFpsCount = 1000 / newDrawFrameTime; else - displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, newDrawFps, 200, Time.Elapsed); + displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, newDrawFps, 100, Time.Elapsed); updateFpsDisplay(); updateFrameTimeDisplay(); From b3aa496ba783b3dc7c63303d88c940edf6dc9c99 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 00:14:30 +0900 Subject: [PATCH 043/124] Add handling of realm disposed exceptions --- osu.Game/BackgroundBeatmapProcessor.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs index 9b04f487a8..6ecd8ca5c1 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -46,6 +46,14 @@ namespace osu.Game Logger.Log("Beginning background beatmap processing.."); checkForOutdatedStarRatings(); processBeatmapSetsWithMissingMetrics(); + }).ContinueWith(t => + { + if (t.Exception?.InnerException is ObjectDisposedException) + { + Logger.Log("Finished background aborted during shutdown"); + return; + } + Logger.Log("Finished background beatmap processing!"); }); } From d796b7d53c0bb135f667cd68dbd6633a442816c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Jul 2022 21:56:23 +0200 Subject: [PATCH 044/124] Extract base mod select overlay panel presentation logic --- osu.Game/Overlays/Mods/ModPanel.cs | 237 ++--------------- .../Overlays/Mods/ModSelectOverlayPanel.cs | 250 ++++++++++++++++++ 2 files changed, 267 insertions(+), 220 deletions(-) create mode 100644 osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index 02eb395bd9..5de6290248 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -1,144 +1,42 @@ // 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 osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; -using osu.Framework.Utils; -using osu.Game.Audio; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using osuTK; -using osuTK.Input; namespace osu.Game.Overlays.Mods { - public class ModPanel : OsuClickableContainer + public class ModPanel : ModSelectOverlayPanel { public Mod Mod => modState.Mod; - public BindableBool Active => modState.Active; + public override BindableBool Active => modState.Active; public BindableBool Filtered => modState.Filtered; + protected override float IdleSwitchWidth => 54; + protected override float ExpandedSwitchWidth => 70; + private readonly ModState modState; - protected readonly Box Background; - protected readonly Container SwitchContainer; - protected readonly Container MainContentContainer; - protected readonly Box TextBackground; - protected readonly FillFlowContainer TextFlow; - - [Resolved] - protected OverlayColourProvider ColourProvider { get; private set; } = null!; - - protected const double TRANSITION_DURATION = 150; - - public const float CORNER_RADIUS = 7; - - protected const float HEIGHT = 42; - protected const float IDLE_SWITCH_WIDTH = 54; - protected const float EXPANDED_SWITCH_WIDTH = 70; - - private Colour4 activeColour; - - private readonly Bindable samplePlaybackDisabled = new BindableBool(); - private Sample? sampleOff; - private Sample? sampleOn; - public ModPanel(ModState modState) { this.modState = modState; - RelativeSizeAxes = Axes.X; - Height = 42; + Title = Mod.Name; + Description = Mod.Description; - // all below properties are applied to `Content` rather than the `ModPanel` in its entirety - // to allow external components to set these properties on the panel without affecting - // its "internal" appearance. - Content.Masking = true; - Content.CornerRadius = CORNER_RADIUS; - Content.BorderThickness = 2; - Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0); - - Children = new Drawable[] + SwitchContainer.Child = new ModSwitchSmall(Mod) { - Background = new Box - { - RelativeSizeAxes = Axes.Both - }, - SwitchContainer = new Container - { - RelativeSizeAxes = Axes.Y, - Child = new ModSwitchSmall(Mod) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Active = { BindTarget = Active }, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), - Scale = new Vector2(HEIGHT / ModSwitchSmall.DEFAULT_SIZE) - } - }, - MainContentContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Child = new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = CORNER_RADIUS, - Children = new Drawable[] - { - TextBackground = new Box - { - RelativeSizeAxes = Axes.Both - }, - TextFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding - { - Horizontal = 17.5f, - Vertical = 4 - }, - Direction = FillDirection.Vertical, - Children = new[] - { - new OsuSpriteText - { - Text = Mod.Name, - Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold), - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), - Margin = new MarginPadding - { - Left = -18 * ShearedOverlayContainer.SHEAR - } - }, - new OsuSpriteText - { - Text = Mod.Description, - Font = OsuFont.Default.With(size: 12), - RelativeSizeAxes = Axes.X, - Truncate = true, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0) - } - } - } - } - } - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Active = { BindTarget = Active }, + Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Scale = new Vector2(HEIGHT / ModSwitchSmall.DEFAULT_SIZE) }; - - Action = Active.Toggle; } public ModPanel(Mod mod) @@ -146,122 +44,21 @@ namespace osu.Game.Overlays.Mods { } - [BackgroundDependencyLoader(true)] - private void load(AudioManager audio, OsuColour colours, ISamplePlaybackDisabler? samplePlaybackDisabler) + [BackgroundDependencyLoader] + private void load(OsuColour colours) { - sampleOn = audio.Samples.Get(@"UI/check-on"); - sampleOff = audio.Samples.Get(@"UI/check-off"); - - activeColour = colours.ForModType(Mod.Type); - - if (samplePlaybackDisabler != null) - ((IBindable)samplePlaybackDisabled).BindTo(samplePlaybackDisabler.SamplePlaybackDisabled); + AccentColour = colours.ForModType(Mod.Type); } - protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet); - protected override void LoadComplete() { base.LoadComplete(); - Active.BindValueChanged(_ => - { - playStateChangeSamples(); - UpdateState(); - }); + Filtered.BindValueChanged(_ => updateFilterState(), true); - - UpdateState(); - FinishTransforms(true); - } - - private void playStateChangeSamples() - { - if (samplePlaybackDisabled.Value) - return; - - if (Active.Value) - sampleOn?.Play(); - else - sampleOff?.Play(); - } - - protected override bool OnHover(HoverEvent e) - { - UpdateState(); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - UpdateState(); - base.OnHoverLost(e); - } - - private bool mouseDown; - - protected override bool OnMouseDown(MouseDownEvent e) - { - if (e.Button == MouseButton.Left) - mouseDown = true; - - UpdateState(); - return false; - } - - protected override void OnMouseUp(MouseUpEvent e) - { - mouseDown = false; - - UpdateState(); - base.OnMouseUp(e); - } - - protected virtual Colour4 BackgroundColour => Active.Value ? activeColour.Darken(0.3f) : ColourProvider.Background3; - protected virtual Colour4 ForegroundColour => Active.Value ? activeColour : ColourProvider.Background2; - protected virtual Colour4 TextColour => Active.Value ? ColourProvider.Background6 : Colour4.White; - - protected virtual void UpdateState() - { - float targetWidth = Active.Value ? EXPANDED_SWITCH_WIDTH : IDLE_SWITCH_WIDTH; - double transitionDuration = TRANSITION_DURATION; - - Colour4 backgroundColour = BackgroundColour; - Colour4 foregroundColour = ForegroundColour; - Colour4 textColour = TextColour; - - // Hover affects colour of button background - if (IsHovered) - { - backgroundColour = backgroundColour.Lighten(0.1f); - foregroundColour = foregroundColour.Lighten(0.1f); - } - - // Mouse down adds a halfway tween of the movement - if (mouseDown) - { - targetWidth = (float)Interpolation.Lerp(IDLE_SWITCH_WIDTH, EXPANDED_SWITCH_WIDTH, 0.5f); - transitionDuration *= 4; - } - - Content.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(backgroundColour, foregroundColour), transitionDuration, Easing.OutQuint); - Background.FadeColour(backgroundColour, transitionDuration, Easing.OutQuint); - SwitchContainer.ResizeWidthTo(targetWidth, transitionDuration, Easing.OutQuint); - MainContentContainer.TransformTo(nameof(Padding), new MarginPadding - { - Left = targetWidth, - Right = CORNER_RADIUS - }, transitionDuration, Easing.OutQuint); - TextBackground.FadeColour(foregroundColour, transitionDuration, Easing.OutQuint); - TextFlow.FadeColour(textColour, transitionDuration, Easing.OutQuint); } #region Filtering support - public void ApplyFilter(Func? filter) - { - Filtered.Value = filter != null && !filter.Invoke(Mod); - } - private void updateFilterState() { this.FadeTo(Filtered.Value ? 0 : 1); diff --git a/osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs b/osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs new file mode 100644 index 0000000000..d4969e0a81 --- /dev/null +++ b/osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs @@ -0,0 +1,250 @@ +// 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.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Framework.Localisation; +using osu.Framework.Utils; +using osu.Game.Audio; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; + +namespace osu.Game.Overlays.Mods +{ + public abstract class ModSelectOverlayPanel : OsuClickableContainer, IHasAccentColour + { + public abstract BindableBool Active { get; } + + public Color4 AccentColour { get; set; } + + public LocalisableString Title + { + get => titleText.Text; + set => titleText.Text = value; + } + + public LocalisableString Description + { + get => descriptionText.Text; + set => descriptionText.Text = value; + } + + public const float CORNER_RADIUS = 7; + + protected const float HEIGHT = 42; + + protected virtual float IdleSwitchWidth => 14; + protected virtual float ExpandedSwitchWidth => 30; + protected virtual Colour4 BackgroundColour => Active.Value ? AccentColour.Darken(0.3f) : ColourProvider.Background3; + protected virtual Colour4 ForegroundColour => Active.Value ? AccentColour : ColourProvider.Background2; + protected virtual Colour4 TextColour => Active.Value ? ColourProvider.Background6 : Colour4.White; + + protected const double TRANSITION_DURATION = 150; + + protected readonly Box Background; + protected readonly Container SwitchContainer; + protected readonly Container MainContentContainer; + protected readonly Box TextBackground; + protected readonly FillFlowContainer TextFlow; + + [Resolved] + protected OverlayColourProvider ColourProvider { get; private set; } = null!; + + private readonly OsuSpriteText titleText; + private readonly OsuSpriteText descriptionText; + + private readonly Bindable samplePlaybackDisabled = new BindableBool(); + private Sample? sampleOff; + private Sample? sampleOn; + + protected ModSelectOverlayPanel() + { + RelativeSizeAxes = Axes.X; + Height = HEIGHT; + + // all below properties are applied to `Content` rather than the `ModPanel` in its entirety + // to allow external components to set these properties on the panel without affecting + // its "internal" appearance. + Content.Masking = true; + Content.CornerRadius = CORNER_RADIUS; + Content.BorderThickness = 2; + + Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0); + + Children = new Drawable[] + { + Background = new Box + { + RelativeSizeAxes = Axes.Both + }, + SwitchContainer = new Container + { + RelativeSizeAxes = Axes.Y, + }, + MainContentContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = CORNER_RADIUS, + Children = new Drawable[] + { + TextBackground = new Box + { + RelativeSizeAxes = Axes.Both + }, + TextFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding + { + Horizontal = 17.5f, + Vertical = 4 + }, + Direction = FillDirection.Vertical, + Children = new[] + { + titleText = new OsuSpriteText + { + Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold), + Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Margin = new MarginPadding + { + Left = -18 * ShearedOverlayContainer.SHEAR + } + }, + descriptionText = new OsuSpriteText + { + Font = OsuFont.Default.With(size: 12), + RelativeSizeAxes = Axes.X, + Truncate = true, + Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0) + } + } + } + } + } + } + }; + + Action = () => Active.Toggle(); + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio, ISamplePlaybackDisabler? samplePlaybackDisabler) + { + sampleOn = audio.Samples.Get(@"UI/check-on"); + sampleOff = audio.Samples.Get(@"UI/check-off"); + + if (samplePlaybackDisabler != null) + ((IBindable)samplePlaybackDisabled).BindTo(samplePlaybackDisabler.SamplePlaybackDisabled); + } + + protected sealed override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet); + + protected override void LoadComplete() + { + base.LoadComplete(); + Active.BindValueChanged(_ => + { + playStateChangeSamples(); + UpdateState(); + }); + + UpdateState(); + FinishTransforms(true); + } + + private void playStateChangeSamples() + { + if (samplePlaybackDisabled.Value) + return; + + if (Active.Value) + sampleOn?.Play(); + else + sampleOff?.Play(); + } + + protected override bool OnHover(HoverEvent e) + { + UpdateState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + UpdateState(); + base.OnHoverLost(e); + } + + private bool mouseDown; + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (e.Button == MouseButton.Left) + mouseDown = true; + + UpdateState(); + return false; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + mouseDown = false; + + UpdateState(); + base.OnMouseUp(e); + } + + protected virtual void UpdateState() + { + float targetWidth = Active.Value ? ExpandedSwitchWidth : IdleSwitchWidth; + double transitionDuration = TRANSITION_DURATION; + + Colour4 backgroundColour = BackgroundColour; + Colour4 foregroundColour = ForegroundColour; + Colour4 textColour = TextColour; + + // Hover affects colour of button background + if (IsHovered) + { + backgroundColour = backgroundColour.Lighten(0.1f); + foregroundColour = foregroundColour.Lighten(0.1f); + } + + // Mouse down adds a halfway tween of the movement + if (mouseDown) + { + targetWidth = (float)Interpolation.Lerp(IdleSwitchWidth, ExpandedSwitchWidth, 0.5f); + transitionDuration *= 4; + } + + Content.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(backgroundColour, foregroundColour), transitionDuration, Easing.OutQuint); + Background.FadeColour(backgroundColour, transitionDuration, Easing.OutQuint); + SwitchContainer.ResizeWidthTo(targetWidth, transitionDuration, Easing.OutQuint); + MainContentContainer.TransformTo(nameof(Padding), new MarginPadding + { + Left = targetWidth, + Right = CORNER_RADIUS + }, transitionDuration, Easing.OutQuint); + TextBackground.FadeColour(foregroundColour, transitionDuration, Easing.OutQuint); + TextFlow.FadeColour(textColour, transitionDuration, Easing.OutQuint); + } + } +} From 6cd18fad99b65202a87c2a7e4399b1b86990a963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Jul 2022 23:29:02 +0200 Subject: [PATCH 045/124] Fix code inspections after base panel class extraction --- osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs | 6 +++--- osu.Game/Overlays/Mods/ModColumn.cs | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs index c8d55c213a..c0a4cf2a25 100644 --- a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs +++ b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs @@ -60,7 +60,7 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Masking = true, - CornerRadius = ModPanel.CORNER_RADIUS, + CornerRadius = ModSelectOverlayPanel.CORNER_RADIUS, Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0), Children = new Drawable[] { @@ -69,7 +69,7 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Y, - Width = multiplier_value_area_width + ModPanel.CORNER_RADIUS + Width = multiplier_value_area_width + ModSelectOverlayPanel.CORNER_RADIUS }, new GridContainer { @@ -89,7 +89,7 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Masking = true, - CornerRadius = ModPanel.CORNER_RADIUS, + CornerRadius = ModSelectOverlayPanel.CORNER_RADIUS, Children = new Drawable[] { contentBackground = new Box diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 3f788d10e3..63a51aaad0 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -105,20 +105,20 @@ namespace osu.Game.Overlays.Mods TopLevelContent = new Container { RelativeSizeAxes = Axes.Both, - CornerRadius = ModPanel.CORNER_RADIUS, + CornerRadius = ModSelectOverlayPanel.CORNER_RADIUS, Masking = true, Children = new Drawable[] { new Container { RelativeSizeAxes = Axes.X, - Height = header_height + ModPanel.CORNER_RADIUS, + Height = header_height + ModSelectOverlayPanel.CORNER_RADIUS, Children = new Drawable[] { headerBackground = new Box { RelativeSizeAxes = Axes.X, - Height = header_height + ModPanel.CORNER_RADIUS + Height = header_height + ModSelectOverlayPanel.CORNER_RADIUS }, headerText = new OsuTextFlowContainer(t => { @@ -135,7 +135,7 @@ namespace osu.Game.Overlays.Mods Padding = new MarginPadding { Horizontal = 17, - Bottom = ModPanel.CORNER_RADIUS + Bottom = ModSelectOverlayPanel.CORNER_RADIUS } } } @@ -148,7 +148,7 @@ namespace osu.Game.Overlays.Mods { RelativeSizeAxes = Axes.Both, Masking = true, - CornerRadius = ModPanel.CORNER_RADIUS, + CornerRadius = ModSelectOverlayPanel.CORNER_RADIUS, BorderThickness = 3, Children = new Drawable[] { From de0a076eb6ce9835810a13977319413c4e41ac1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Jul 2022 20:56:43 +0200 Subject: [PATCH 046/124] Add model class for mod presets --- osu.Game/Rulesets/Mods/ModPreset.cs | 35 +++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 osu.Game/Rulesets/Mods/ModPreset.cs diff --git a/osu.Game/Rulesets/Mods/ModPreset.cs b/osu.Game/Rulesets/Mods/ModPreset.cs new file mode 100644 index 0000000000..367acc8a91 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModPreset.cs @@ -0,0 +1,35 @@ +// 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.Collections.Generic; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// A mod preset is a named collection of configured mods. + /// Presets are presented to the user in the mod select overlay for convenience. + /// + public class ModPreset + { + /// + /// The ruleset that the preset is valid for. + /// + public RulesetInfo RulesetInfo { get; set; } = null!; + + /// + /// The name of the mod preset. + /// + public string Name { get; set; } = string.Empty; + + /// + /// The description of the mod preset. + /// + public string Description { get; set; } = string.Empty; + + /// + /// The set of configured mods that are part of the preset. + /// + public ICollection Mods { get; set; } = Array.Empty(); + } +} From bdff7f1ef409bb48ca98a39d65c689c238281791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Jul 2022 22:01:03 +0200 Subject: [PATCH 047/124] Implement basic appearance of mod preset panels --- .../UserInterface/TestSceneModPresetPanel.cs | 74 +++++++++++++++++++ osu.Game/Overlays/Mods/ModPresetPanel.cs | 27 +++++++ .../Overlays/Mods/ModSelectOverlayPanel.cs | 2 + 3 files changed, 103 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs create mode 100644 osu.Game/Overlays/Mods/ModPresetPanel.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs new file mode 100644 index 0000000000..62e63d47bc --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs @@ -0,0 +1,74 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; +using osu.Game.Overlays.Mods; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public class TestSceneModPresetPanel : OsuTestScene + { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + + [Test] + public void TestVariousModPresets() + { + AddStep("create content", () => Child = new FillFlowContainer + { + Width = 300, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Spacing = new Vector2(0, 5), + ChildrenEnumerable = createTestPresets().Select(preset => new ModPresetPanel(preset)) + }); + } + + private static IEnumerable createTestPresets() => new[] + { + new ModPreset + { + Name = "First preset", + Description = "Please ignore", + Mods = new Mod[] + { + new OsuModHardRock(), + new OsuModDoubleTime() + } + }, + new ModPreset + { + Name = "AR0", + Description = "For good readers", + Mods = new Mod[] + { + new OsuModDifficultyAdjust + { + ApproachRate = { Value = 0 } + } + } + }, + new ModPreset + { + Name = "This preset is going to have an extraordinarily long name", + Description = "This is done so that the capability to truncate overlong texts may be demonstrated", + Mods = new Mod[] + { + new OsuModFlashlight(), + new OsuModSpinIn() + } + } + }; + } +} diff --git a/osu.Game/Overlays/Mods/ModPresetPanel.cs b/osu.Game/Overlays/Mods/ModPresetPanel.cs new file mode 100644 index 0000000000..7a55f99088 --- /dev/null +++ b/osu.Game/Overlays/Mods/ModPresetPanel.cs @@ -0,0 +1,27 @@ +// 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.Game.Graphics; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Overlays.Mods +{ + public class ModPresetPanel : ModSelectOverlayPanel + { + public override BindableBool Active { get; } = new BindableBool(); + + public ModPresetPanel(ModPreset preset) + { + Title = preset.Name; + Description = preset.Description; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.Orange1; + } + } +} diff --git a/osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs b/osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs index d4969e0a81..a794884d7d 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs @@ -122,6 +122,8 @@ namespace osu.Game.Overlays.Mods titleText = new OsuSpriteText { Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold), + RelativeSizeAxes = Axes.X, + Truncate = true, Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), Margin = new MarginPadding { From a3090003de8af21d617c373285bb98350c83933e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Jul 2022 22:34:54 +0200 Subject: [PATCH 048/124] Add tooltip showing contents of mod preset --- osu.Game/Overlays/Mods/ModPresetPanel.cs | 10 +- osu.Game/Overlays/Mods/ModPresetTooltip.cs | 115 +++++++++++++++++++++ 2 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Overlays/Mods/ModPresetTooltip.cs diff --git a/osu.Game/Overlays/Mods/ModPresetPanel.cs b/osu.Game/Overlays/Mods/ModPresetPanel.cs index 7a55f99088..39f092b91a 100644 --- a/osu.Game/Overlays/Mods/ModPresetPanel.cs +++ b/osu.Game/Overlays/Mods/ModPresetPanel.cs @@ -3,17 +3,22 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics.Cursor; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; namespace osu.Game.Overlays.Mods { - public class ModPresetPanel : ModSelectOverlayPanel + public class ModPresetPanel : ModSelectOverlayPanel, IHasCustomTooltip { + public readonly ModPreset Preset; + public override BindableBool Active { get; } = new BindableBool(); public ModPresetPanel(ModPreset preset) { + Preset = preset; + Title = preset.Name; Description = preset.Description; } @@ -23,5 +28,8 @@ namespace osu.Game.Overlays.Mods { AccentColour = colours.Orange1; } + + public ModPreset TooltipContent => Preset; + public ITooltip GetCustomTooltip() => new ModPresetTooltip(ColourProvider); } } diff --git a/osu.Game/Overlays/Mods/ModPresetTooltip.cs b/osu.Game/Overlays/Mods/ModPresetTooltip.cs new file mode 100644 index 0000000000..68da649e81 --- /dev/null +++ b/osu.Game/Overlays/Mods/ModPresetTooltip.cs @@ -0,0 +1,115 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Overlays.Mods +{ + public class ModPresetTooltip : VisibilityContainer, ITooltip + { + protected override Container Content { get; } + + private const double transition_duration = 200; + + public ModPresetTooltip(OverlayColourProvider colourProvider) + { + Width = 250; + AutoSizeAxes = Axes.Y; + + Masking = true; + CornerRadius = 7; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background6 + }, + Content = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(7), + Spacing = new Vector2(7) + } + }; + } + + private ModPreset? lastPreset; + + public void SetContent(ModPreset preset) + { + if (preset == lastPreset) + return; + + lastPreset = preset; + Content.ChildrenEnumerable = preset.Mods.Select(mod => new ModPresetRow(mod)); + } + + protected override void PopIn() => this.FadeIn(transition_duration, Easing.OutQuint); + protected override void PopOut() => this.FadeOut(transition_duration, Easing.OutQuint); + + public void Move(Vector2 pos) => Position = pos; + + private class ModPresetRow : FillFlowContainer + { + public ModPresetRow(Mod mod) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + Spacing = new Vector2(4); + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(7), + Children = new Drawable[] + { + new ModSwitchTiny(mod) + { + Active = { Value = true }, + Scale = new Vector2(0.6f), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft + }, + new OsuSpriteText + { + Text = mod.Name, + Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold), + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Margin = new MarginPadding { Bottom = 2 } + } + } + } + }; + + if (!string.IsNullOrEmpty(mod.SettingDescription)) + { + AddInternal(new OsuTextFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Left = 14 }, + Text = mod.SettingDescription + }); + } + } + } + } +} From d69dc457ba2adf3d0a6db8f89237292cebb62efd Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Thu, 21 Jul 2022 17:28:43 -0500 Subject: [PATCH 049/124] Extract zoom delta method --- .../Edit/Compose/Components/Timeline/TimelineArea.cs | 4 +--- .../Compose/Components/Timeline/ZoomableScrollContainer.cs | 6 ++++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index c25a4834d3..b47c09109b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -20,8 +20,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly Drawable userContent; - private const float zoom_button_sensitivity = 0.02f; - public TimelineArea(Drawable content = null) { RelativeSizeAxes = Axes.X; @@ -156,6 +154,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Timeline.TicksVisible.BindTo(ticksCheckbox.Current); } - private void changeZoom(float change) => Timeline.Zoom += change * Timeline.MaxZoom * zoom_button_sensitivity; + private void changeZoom(float change) => Timeline.Zoom += Timeline.CalculateZoomChange(change); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 7a4d45301f..9bd7332105 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -104,7 +104,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline set => updateZoom(value); } - private const float zoom_scroll_sensitivity = 0.02f; + private const float zoom_change_sensitivity = 0.02f; private void updateZoom(float? value = null) { @@ -129,7 +129,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (e.AltPressed) { // zoom when holding alt. - setZoomTarget(zoomTarget + e.ScrollDelta.Y * MaxZoom * zoom_scroll_sensitivity, zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); + setZoomTarget(zoomTarget + CalculateZoomChange(e.ScrollDelta.Y), zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); return true; } @@ -167,6 +167,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { } + public float CalculateZoomChange(float rawChange) => rawChange * MaxZoom * zoom_change_sensitivity; + private class TransformZoom : Transform { /// From 997fe00cdcab42a58ef1ba7533acc1278fe42e37 Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Thu, 21 Jul 2022 17:29:13 -0500 Subject: [PATCH 050/124] Fix zoom delta math --- .../Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 9bd7332105..4d95600450 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -167,7 +167,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { } - public float CalculateZoomChange(float rawChange) => rawChange * MaxZoom * zoom_change_sensitivity; + public float CalculateZoomChange(float rawChange) => rawChange * (MaxZoom - minZoom) * zoom_change_sensitivity; private class TransformZoom : Transform { From 4cec9a085a4e4e9678785f21d63aed7fee86599a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 14:44:48 +0900 Subject: [PATCH 051/124] Combine both calls to use same pathway --- .../Compose/Components/Timeline/TimelineArea.cs | 6 ++---- .../Timeline/ZoomableScrollContainer.cs | 17 ++++++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index b47c09109b..c2415ce978 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -118,7 +118,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.Y, Height = 0.5f, Icon = FontAwesome.Solid.SearchPlus, - Action = () => changeZoom(1) + Action = () => Timeline.AdjustZoomRelatively(1) }, new TimelineButton { @@ -127,7 +127,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.Y, Height = 0.5f, Icon = FontAwesome.Solid.SearchMinus, - Action = () => changeZoom(-1) + Action = () => Timeline.AdjustZoomRelatively(-1) }, } } @@ -153,7 +153,5 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Timeline.ControlPointsVisible.BindTo(controlPointsCheckbox.Current); Timeline.TicksVisible.BindTo(ticksCheckbox.Current); } - - private void changeZoom(float change) => Timeline.Zoom += Timeline.CalculateZoomChange(change); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 4d95600450..96f6ef6d02 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -104,8 +104,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline set => updateZoom(value); } - private const float zoom_change_sensitivity = 0.02f; - private void updateZoom(float? value = null) { float newZoom = Math.Clamp(value ?? Zoom, MinZoom, MaxZoom); @@ -129,7 +127,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (e.AltPressed) { // zoom when holding alt. - setZoomTarget(zoomTarget + CalculateZoomChange(e.ScrollDelta.Y), zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); + AdjustZoomRelatively(e.ScrollDelta.Y, zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); return true; } @@ -147,12 +145,19 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline zoomedContentWidthCache.Validate(); } + public void AdjustZoomRelatively(float change, float? focusPoint = null) + { + const float zoom_change_sensitivity = 0.02f; + + setZoomTarget(zoomTarget + change * (MaxZoom - minZoom) * zoom_change_sensitivity, focusPoint); + } + private float zoomTarget = 1; - private void setZoomTarget(float newZoom, float focusPoint) + private void setZoomTarget(float newZoom, float? focusPoint = null) { zoomTarget = Math.Clamp(newZoom, MinZoom, MaxZoom); - transformZoomTo(zoomTarget, focusPoint, ZoomDuration, ZoomEasing); + transformZoomTo(zoomTarget, focusPoint ?? DrawWidth / 2, ZoomDuration, ZoomEasing); OnZoomChanged(); } @@ -167,8 +172,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { } - public float CalculateZoomChange(float rawChange) => rawChange * (MaxZoom - minZoom) * zoom_change_sensitivity; - private class TransformZoom : Transform { /// From 3b913bb9ad9fd63ec185ad96a5b51f888b72110e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 22 Jul 2022 09:15:48 +0300 Subject: [PATCH 052/124] Fix sorting mode not filling up to usable area in filter control --- osu.Game/Screens/Select/FilterControl.cs | 69 ++++++++++++++---------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index e43261f374..d39862b65f 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -115,42 +115,53 @@ namespace osu.Game.Screens.Select Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, }, - new FillFlowContainer + new GridContainer { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, - Direction = FillDirection.Horizontal, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Spacing = new Vector2(OsuTabControl.HORIZONTAL_SPACING, 0), - Children = new Drawable[] + ColumnDimensions = new[] { - new OsuTabControlCheckbox + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Absolute, OsuTabControl.HORIZONTAL_SPACING), + new Dimension(), + new Dimension(GridSizeMode.Absolute, OsuTabControl.HORIZONTAL_SPACING), + new Dimension(GridSizeMode.AutoSize), + }, + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, + Content = new[] + { + new[] { - Text = "Show converted", - Current = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - }, - sortTabs = new OsuTabControl - { - RelativeSizeAxes = Axes.X, - Width = 0.5f, - Height = 24, - AutoSort = true, - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - AccentColour = colours.GreenLight, - Current = { BindTarget = sortMode } - }, - new OsuSpriteText - { - Text = SortStrings.Default, - Font = OsuFont.GetFont(size: 14), - Margin = new MarginPadding(5), - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - }, + new OsuSpriteText + { + Text = SortStrings.Default, + Font = OsuFont.GetFont(size: 14), + Margin = new MarginPadding(5), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + Empty(), + sortTabs = new OsuTabControl + { + RelativeSizeAxes = Axes.X, + Height = 24, + AutoSort = true, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + AccentColour = colours.GreenLight, + Current = { BindTarget = sortMode } + }, + Empty(), + new OsuTabControlCheckbox + { + Text = "Show converted", + Current = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + } } }, } From 6eb42d08ce2ebbe216424a7179fbe65629d0eb09 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 15:21:25 +0900 Subject: [PATCH 053/124] Fix timeline zoom receiving feedback from bindable changes This causes the focal point zooming to not work (as the focal point is lost). There's no need to handle ongoing changes to `BeatmapInfo.TimelineZoom` because it is not a property which is changed at runtime. --- .../Edit/Compose/Components/Timeline/Timeline.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 37fc4b03b2..bbe011a2e0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -41,6 +41,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private EditorClock editorClock { get; set; } + [Resolved] + private EditorBeatmap editorBeatmap { get; set; } + /// /// The timeline's scroll position in the last frame. /// @@ -68,8 +71,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// private float defaultTimelineZoom; - private readonly Bindable timelineZoomScale = new BindableDouble(1.0); - public Timeline(Drawable userContent) { this.userContent = userContent; @@ -93,7 +94,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private Bindable waveformOpacity; [BackgroundDependencyLoader] - private void load(IBindable beatmap, EditorBeatmap editorBeatmap, OsuColour colours, OsuConfigManager config) + private void load(IBindable beatmap, OsuColour colours, OsuConfigManager config) { CentreMarker centreMarker; @@ -154,12 +155,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } }, true); - timelineZoomScale.Value = editorBeatmap.BeatmapInfo.TimelineZoom; - timelineZoomScale.BindValueChanged(scale => - { - Zoom = (float)(defaultTimelineZoom * scale.NewValue); - editorBeatmap.BeatmapInfo.TimelineZoom = scale.NewValue; - }, true); + Zoom = (float)(defaultTimelineZoom * editorBeatmap.BeatmapInfo.TimelineZoom); } protected override void LoadComplete() @@ -221,7 +217,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override void OnZoomChanged() { base.OnZoomChanged(); - timelineZoomScale.Value = Zoom / defaultTimelineZoom; + editorBeatmap.BeatmapInfo.TimelineZoom = Zoom / defaultTimelineZoom; } protected override void UpdateAfterChildren() From e20458421a9ad07a409bafc3b22d4388ad0ee5a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 16:05:20 +0900 Subject: [PATCH 054/124] Update flaky timeline zoom test to output something useful --- osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs index 2cada1989e..6c5cca1874 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs @@ -5,7 +5,6 @@ using NUnit.Framework; using osu.Framework.Graphics; -using osu.Framework.Utils; namespace osu.Game.Tests.Visual.Editing { @@ -32,12 +31,12 @@ namespace osu.Game.Tests.Visual.Editing AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange); AddStep("scale zoom", () => TimelineArea.Timeline.Zoom = 200); - AddAssert("range halved", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange / 2, 1)); + AddStep("range halved", () => Assert.That(TimelineArea.Timeline.VisibleRange, Is.EqualTo(initialVisibleRange / 2).Within(1))); AddStep("descale zoom", () => TimelineArea.Timeline.Zoom = 50); - AddAssert("range doubled", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange * 2, 1)); + AddStep("range doubled", () => Assert.That(TimelineArea.Timeline.VisibleRange, Is.EqualTo(initialVisibleRange * 2).Within(1))); AddStep("restore zoom", () => TimelineArea.Timeline.Zoom = 100); - AddAssert("range restored", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange, 1)); + AddStep("range restored", () => Assert.That(TimelineArea.Timeline.VisibleRange, Is.EqualTo(initialVisibleRange).Within(1))); } [Test] From b884ed2a3d8b8fd50412134a3f162a84c1c29417 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 16:18:14 +0900 Subject: [PATCH 055/124] Make test actually test drum behaviours --- .../TestSceneDrumTouchInputArea.cs | 63 +++++++++---------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs index 45555a55c1..979464db5d 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs @@ -1,55 +1,50 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Taiko.Objects; +using osu.Framework.Graphics; +using osu.Framework.Testing; using osu.Game.Rulesets.Taiko.UI; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests { [TestFixture] - public class TestSceneDrumTouchInputArea : DrawableTaikoRulesetTestScene + public class TestSceneDrumTouchInputArea : OsuTestScene { - protected const double NUM_HIT_OBJECTS = 10; - protected const double HIT_OBJECT_TIME_SPACING_MS = 1000; + [Cached] + private TaikoInputManager taikoInputManager = new TaikoInputManager(new TaikoRuleset().RulesetInfo); - [BackgroundDependencyLoader] - private void load() + private DrumTouchInputArea drumTouchInputArea = null!; + + [SetUpSteps] + public void SetUpSteps() { - var drumTouchInputArea = new DrumTouchInputArea(); - DrawableRuleset.KeyBindingInputManager.Add(drumTouchInputArea); - drumTouchInputArea.ShowTouchControls(); - } - - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) - { - List hitObjects = new List(); - - for (int i = 0; i < NUM_HIT_OBJECTS; i++) + AddStep("create drum", () => { - hitObjects.Add(new Hit + Children = new Drawable[] { - StartTime = Time.Current + i * HIT_OBJECT_TIME_SPACING_MS, - IsStrong = isOdd(i), - Type = isOdd(i / 2) ? HitType.Centre : HitType.Rim - }); - } - - var beatmap = new Beatmap - { - BeatmapInfo = { Ruleset = ruleset }, - HitObjects = hitObjects - }; - - return beatmap; + new InputDrum + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Height = 0.5f, + }, + drumTouchInputArea = new DrumTouchInputArea + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Height = 0.5f, + }, + }; + }); } - private bool isOdd(int number) + [Test] + public void TestDrum() { - return number % 2 == 0; + AddStep("show drum", () => drumTouchInputArea.Show()); } } } From 7015cf0b1b303607857c42a34ae9e36c0b434122 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 16:08:57 +0900 Subject: [PATCH 056/124] Move touch input drum to own file for now --- .../UI/DrumTouchInputArea.cs | 12 +- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 13 +- osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs | 187 ++++++++++++++++++ 3 files changed, 197 insertions(+), 15 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index a66c52df1f..679e66d33b 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -25,10 +26,10 @@ namespace osu.Game.Rulesets.Taiko.UI // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) private const float offscreen_percent = 0.35f; - private readonly InputDrum touchInputDrum; + private readonly TouchInputDrum touchInputDrum; private readonly Circle drumBackground; - private KeyBindingContainer keyBindingContainer; + private KeyBindingContainer keyBindingContainer = null!; // Which Taiko action was pressed by the last OnMouseDown event, so that the corresponding action can be released OnMouseUp even if the cursor position moved private TaikoAction mouseAction; @@ -62,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.UI FillMode = FillMode.Fit, Alpha = 0.9f, }, - touchInputDrum = new InputDrum + touchInputDrum = new TouchInputDrum { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -84,7 +85,10 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load(TaikoInputManager taikoInputManager, OsuColour colours) { - keyBindingContainer = taikoInputManager?.KeyBindingContainer; + Debug.Assert(taikoInputManager?.KeyBindingContainer != null); + + keyBindingContainer = taikoInputManager.KeyBindingContainer; + drumBackground.Colour = colours.Gray0; } diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 97990395a2..054f98e18f 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osuTK; @@ -24,7 +23,6 @@ namespace osu.Game.Rulesets.Taiko.UI /// internal class InputDrum : Container { - public float CentreSize = 0.7f; private const float middle_split = 0.025f; public InputDrum() @@ -64,7 +62,7 @@ namespace osu.Game.Rulesets.Taiko.UI Scale = new Vector2(0.9f), Children = new[] { - new TaikoHalfDrum(false, CentreSize) + new TaikoHalfDrum(false) { Name = "Left Half", Anchor = Anchor.Centre, @@ -75,7 +73,7 @@ namespace osu.Game.Rulesets.Taiko.UI RimAction = TaikoAction.LeftRim, CentreAction = TaikoAction.LeftCentre }, - new TaikoHalfDrum(true, CentreSize) + new TaikoHalfDrum(true) { Name = "Right Half", Anchor = Anchor.Centre, @@ -110,9 +108,6 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Sprite centre; private readonly Sprite centreHit; - [Resolved] - private DrumSampleTriggerSource sampleTriggerSource { get; set; } - public TaikoHalfDrum(bool flipped) { Masking = true; @@ -173,15 +168,11 @@ namespace osu.Game.Rulesets.Taiko.UI { target = centreHit; back = centre; - - sampleTriggerSource.Play(HitType.Centre); } else if (e.Action == RimAction) { target = rimHit; back = rim; - - sampleTriggerSource.Play(HitType.Rim); } if (target != null) diff --git a/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs b/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs new file mode 100644 index 0000000000..85eec31a8a --- /dev/null +++ b/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs @@ -0,0 +1,187 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable +using System; +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.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Taiko.UI +{ + /// + /// A component of the playfield that captures input and displays input as a drum. + /// + internal class TouchInputDrum : Container + { + public float CentreSize = 0.7f; + private const float middle_split = 0.025f; + + public TouchInputDrum() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.InputDrum), _ => new Container + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Scale = new Vector2(0.9f), + Children = new Drawable[] + { + new TaikoHalfDrum(false, CentreSize) + { + Name = "Left Half", + Anchor = Anchor.Centre, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.X, + X = -middle_split / 2, + RimAction = TaikoAction.LeftRim, + CentreAction = TaikoAction.LeftCentre + }, + new TaikoHalfDrum(true, CentreSize) + { + Name = "Right Half", + Anchor = Anchor.Centre, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.X, + X = middle_split / 2, + RimAction = TaikoAction.RightRim, + CentreAction = TaikoAction.RightCentre + } + } + }) + }; + } + + /// + /// A half-drum. Contains one centre and one rim hit. + /// + private class TaikoHalfDrum : Container, IKeyBindingHandler + { + /// + /// The key to be used for the rim of the half-drum. + /// + public TaikoAction RimAction; + + /// + /// The key to be used for the centre of the half-drum. + /// + public TaikoAction CentreAction; + + private readonly Sprite rim; + private readonly Sprite rimHit; + private readonly Sprite centre; + private readonly Sprite centreHit; + + public TaikoHalfDrum(bool flipped, float centreSize) + { + 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, + Blending = BlendingParameters.Additive, + }, + centre = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(centreSize) + }, + centreHit = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(centreSize), + Alpha = 0, + Blending = BlendingParameters.Additive + } + }; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures, OsuColour colours) + { + rim.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer"); + rimHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer-hit"); + centre.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner"); + centreHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner-hit"); + + rimHit.Colour = colours.Blue; + centreHit.Colour = colours.Pink; + } + + public bool OnPressed(KeyBindingPressEvent e) + { + Drawable target = null; + Drawable back = null; + + if (e.Action == CentreAction) + { + target = centreHit; + back = centre; + } + else if (e.Action == RimAction) + { + target = rimHit; + back = rim; + } + + if (target != null) + { + const float scale_amount = 0.05f; + const float alpha_amount = 0.5f; + + const float down_time = 40; + const float up_time = 1000; + + back.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint) + .Then() + .ScaleTo(1, up_time, Easing.OutQuint); + + target.Animate( + t => t.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint), + t => t.FadeTo(Math.Min(target.Alpha + alpha_amount, 1), down_time, Easing.OutQuint) + ).Then( + t => t.ScaleTo(1, up_time, Easing.OutQuint), + t => t.FadeOut(up_time, Easing.OutQuint) + ); + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + } + } +} From b604eb62620e811567239dcd291a6a3703dc072d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 16:18:22 +0900 Subject: [PATCH 057/124] Simplify implementation --- .../TestSceneDrumTouchInputArea.cs | 29 ++-- .../UI/DrawableTaikoRuleset.cs | 7 +- .../UI/DrumTouchInputArea.cs | 135 ++++++++---------- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 2 +- osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs | 5 +- 5 files changed, 75 insertions(+), 103 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs index 979464db5d..7210419c0e 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Rulesets.Taiko.UI; @@ -13,9 +12,6 @@ namespace osu.Game.Rulesets.Taiko.Tests [TestFixture] public class TestSceneDrumTouchInputArea : OsuTestScene { - [Cached] - private TaikoInputManager taikoInputManager = new TaikoInputManager(new TaikoRuleset().RulesetInfo); - private DrumTouchInputArea drumTouchInputArea = null!; [SetUpSteps] @@ -23,19 +19,22 @@ namespace osu.Game.Rulesets.Taiko.Tests { AddStep("create drum", () => { - Children = new Drawable[] + Child = new TaikoInputManager(new TaikoRuleset().RulesetInfo) { - new InputDrum + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Height = 0.5f, - }, - drumTouchInputArea = new DrumTouchInputArea - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Height = 0.5f, + new InputDrum + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Height = 0.2f, + }, + drumTouchInputArea = new DrumTouchInputArea + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + }, }, }; }); diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index a117435ada..2fa511b8ab 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -39,8 +39,6 @@ namespace osu.Game.Rulesets.Taiko.UI private SkinnableDrawable scroller; - private DrumTouchInputArea drumTouchInputArea; - public DrawableTaikoRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) : base(ruleset, beatmap, mods) { @@ -59,8 +57,7 @@ namespace osu.Game.Rulesets.Taiko.UI Depth = float.MaxValue }); - if ((drumTouchInputArea = CreateDrumTouchInputArea()) != null) - KeyBindingInputManager.Add(drumTouchInputArea); + KeyBindingInputManager.Add(new DrumTouchInputArea()); } protected override void UpdateAfterChildren() @@ -79,8 +76,6 @@ namespace osu.Game.Rulesets.Taiko.UI return ControlPoints[result]; } - public DrumTouchInputArea CreateDrumTouchInputArea() => new DrumTouchInputArea(); - public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer { LockPlayfieldAspect = { BindTarget = LockPlayfieldAspect } diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 679e66d33b..55eb533a47 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -1,7 +1,6 @@ // 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.Collections.Generic; using System.Diagnostics; using osu.Framework.Allocation; @@ -13,152 +12,132 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; using osuTK; +using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Rulesets.Taiko.UI { /// - /// An overlay that captures and displays Taiko mouse and touch input. - /// The boundaries of this overlay defines the interactable area for touch input. - /// A secondary InputDrum is attached by this overlay, which defines the circular boundary which distinguishes "centre" from "rim" hits, and also displays input. + /// An overlay that captures and displays osu!taiko mouse and touch input. /// public class DrumTouchInputArea : Container { - // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) - private const float offscreen_percent = 0.35f; - - private readonly TouchInputDrum touchInputDrum; - private readonly Circle drumBackground; + private readonly Circle outerCircle; private KeyBindingContainer keyBindingContainer = null!; - // Which Taiko action was pressed by the last OnMouseDown event, so that the corresponding action can be released OnMouseUp even if the cursor position moved - private TaikoAction mouseAction; + private readonly Dictionary trackedTouches = new Dictionary(); + private readonly Dictionary trackedMouseButtons = new Dictionary(); - // A map of (Finger Index OnTouchDown -> Which Taiko action was pressed), so that the corresponding action can be released OnTouchUp is released even if the touch position moved - private readonly Dictionary touchActions = new Dictionary(Enum.GetNames(typeof(TouchSource)).Length); + private readonly Container mainContent; - private readonly Container visibleComponents; + private readonly Circle centreCircle; public DrumTouchInputArea() { - RelativeSizeAxes = Axes.Both; - RelativePositionAxes = Axes.Both; + RelativeSizeAxes = Axes.X; + Height = 300; + + Masking = true; + Children = new Drawable[] { - visibleComponents = new Container + mainContent = new Container { - Alpha = 0.0f, RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, + Height = 2, Children = new Drawable[] { - drumBackground = new Circle + outerCircle = new Circle { - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, FillMode = FillMode.Fit, - Alpha = 0.9f, - }, - touchInputDrum = new TouchInputDrum - { + RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, + centreCircle = new Circle + { + FillMode = FillMode.Fit, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.5f), + }, + new Box + { + FillMode = FillMode.Fit, + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = Color4.Black, + Width = 3, + }, } }, }; } - protected override void LoadComplete() - { - Padding = new MarginPadding - { - Top = TaikoPlayfield.DEFAULT_HEIGHT * 2f, // Visual elements should start right below the playfield - Bottom = -touchInputDrum.DrawHeight * offscreen_percent, // The drum should go past the bottom of the screen so that it can be wider - }; - } - [BackgroundDependencyLoader] private void load(TaikoInputManager taikoInputManager, OsuColour colours) { - Debug.Assert(taikoInputManager?.KeyBindingContainer != null); + Debug.Assert(taikoInputManager.KeyBindingContainer != null); keyBindingContainer = taikoInputManager.KeyBindingContainer; - drumBackground.Colour = colours.Gray0; + outerCircle.Colour = colours.Gray0; } protected override bool OnMouseDown(MouseDownEvent e) { - ShowTouchControls(); - mouseAction = getTaikoActionFromInput(e.ScreenSpaceMouseDownPosition); - keyBindingContainer?.TriggerPressed(mouseAction); + mainContent.Show(); + + TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceMouseDownPosition); + + trackedMouseButtons.Add(e.Button, taikoAction); + keyBindingContainer.TriggerPressed(taikoAction); return true; } protected override void OnMouseUp(MouseUpEvent e) { - keyBindingContainer?.TriggerReleased(mouseAction); + keyBindingContainer.TriggerReleased(trackedMouseButtons[e.Button]); + trackedMouseButtons.Remove(e.Button); base.OnMouseUp(e); } protected override bool OnTouchDown(TouchDownEvent e) { - ShowTouchControls(); + mainContent.Show(); + TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceTouchDownPosition); - touchActions.Add(e.Touch.Source, taikoAction); - keyBindingContainer?.TriggerPressed(touchActions[e.Touch.Source]); + + trackedTouches.Add(e.Touch.Source, taikoAction); + keyBindingContainer.TriggerPressed(taikoAction); return true; } protected override void OnTouchUp(TouchUpEvent e) { - keyBindingContainer?.TriggerReleased(touchActions[e.Touch.Source]); - touchActions.Remove(e.Touch.Source); + keyBindingContainer.TriggerReleased(trackedTouches[e.Touch.Source]); + trackedTouches.Remove(e.Touch.Source); base.OnTouchUp(e); } protected override bool OnKeyDown(KeyDownEvent e) { - HideTouchControls(); + mainContent.Hide(); return false; } - public void ShowTouchControls() - { - visibleComponents.Animate(components => components.FadeIn(500, Easing.OutQuint)); - } - - public void HideTouchControls() - { - visibleComponents.Animate(components => components.FadeOut(2000, Easing.OutQuint)); - } - private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { - bool centreHit = inputIsCenterHit(inputPosition); - bool leftSide = inputIsOnLeftSide(inputPosition); + bool centreHit = centreCircle.ScreenSpaceDrawQuad.Contains(inputPosition); + bool leftSide = ToLocalSpace(inputPosition).X < DrawWidth / 2; - return centreHit ? (leftSide ? TaikoAction.LeftCentre : TaikoAction.RightCentre) : (leftSide ? TaikoAction.LeftRim : TaikoAction.RightRim); - } + if (leftSide) + return centreHit ? TaikoAction.LeftCentre : TaikoAction.LeftRim; - private bool inputIsOnLeftSide(Vector2 inputPosition) - { - Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; - return inputPositionToDrumCentreDelta.X < 0f; - } - - private bool inputIsCenterHit(Vector2 inputPosition) - { - Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; - - float inputDrumRadius = Math.Max(touchInputDrum.Width, touchInputDrum.DrawHeight) / 2f; - float centreRadius = (inputDrumRadius * touchInputDrum.CentreSize); - return inputPositionToDrumCentreDelta.Length <= centreRadius; + return centreHit ? TaikoAction.RightCentre : TaikoAction.RightRim; } } } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index a17fc0391a..ccca5587b7 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load(OsuColour colours) { - inputDrum = new InputDrum() + inputDrum = new InputDrum { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs b/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs index 85eec31a8a..21b83e5495 100644 --- a/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Taiko.UI @@ -34,7 +33,7 @@ namespace osu.Game.Rulesets.Taiko.UI { Children = new Drawable[] { - new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.InputDrum), _ => new Container + new Container { RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, @@ -64,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.UI CentreAction = TaikoAction.RightCentre } } - }) + } }; } From e1df50c8ffa2047f6eea539952d884b6237bf839 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 22 Jul 2022 17:03:04 +0900 Subject: [PATCH 058/124] Fix timeline zoom focus point when using buttons --- .../Compose/Components/Timeline/ZoomableScrollContainer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 96f6ef6d02..83fd1dea2b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -157,7 +157,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void setZoomTarget(float newZoom, float? focusPoint = null) { zoomTarget = Math.Clamp(newZoom, MinZoom, MaxZoom); - transformZoomTo(zoomTarget, focusPoint ?? DrawWidth / 2, ZoomDuration, ZoomEasing); + focusPoint ??= zoomedContent.ToLocalSpace(ToScreenSpace(new Vector2(DrawWidth / 2, 0))).X; + + transformZoomTo(zoomTarget, focusPoint.Value, ZoomDuration, ZoomEasing); OnZoomChanged(); } From bd6ff40b43b91c8e79966cb0e751f9fc86515de8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 17:02:58 +0900 Subject: [PATCH 059/124] Combine touch and mouse handling into single path --- .../UI/DrawableTaikoRuleset.cs | 10 ++-- .../UI/DrumTouchInputArea.cs | 48 ++++++++++--------- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 2fa511b8ab..58e703b8be 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -8,18 +8,18 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Rulesets.UI; -using osu.Game.Rulesets.Taiko.Replays; using osu.Framework.Input; +using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Input.Handlers; using osu.Game.Replays; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Replays; using osu.Game.Rulesets.Timing; +using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Scoring; using osu.Game.Skinning; diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 55eb533a47..253ced9a39 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -7,13 +7,11 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; using osuTK; using osuTK.Graphics; -using osuTK.Input; namespace osu.Game.Rulesets.Taiko.UI { @@ -26,8 +24,7 @@ namespace osu.Game.Rulesets.Taiko.UI private KeyBindingContainer keyBindingContainer = null!; - private readonly Dictionary trackedTouches = new Dictionary(); - private readonly Dictionary trackedMouseButtons = new Dictionary(); + private readonly Dictionary trackedActions = new Dictionary(); private readonly Container mainContent; @@ -87,46 +84,51 @@ namespace osu.Game.Rulesets.Taiko.UI outerCircle.Colour = colours.Gray0; } + protected override bool OnKeyDown(KeyDownEvent e) + { + // Hide whenever the keyboard is used. + mainContent.Hide(); + return false; + } + protected override bool OnMouseDown(MouseDownEvent e) { - mainContent.Show(); - - TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceMouseDownPosition); - - trackedMouseButtons.Add(e.Button, taikoAction); - keyBindingContainer.TriggerPressed(taikoAction); + handleDown(e.Button, e.ScreenSpaceMousePosition); return true; } protected override void OnMouseUp(MouseUpEvent e) { - keyBindingContainer.TriggerReleased(trackedMouseButtons[e.Button]); - trackedMouseButtons.Remove(e.Button); + handleUp(e.Button); base.OnMouseUp(e); } protected override bool OnTouchDown(TouchDownEvent e) { - mainContent.Show(); - - TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceTouchDownPosition); - - trackedTouches.Add(e.Touch.Source, taikoAction); - keyBindingContainer.TriggerPressed(taikoAction); + handleDown(e.Touch.Source, e.ScreenSpaceTouchDownPosition); return true; } protected override void OnTouchUp(TouchUpEvent e) { - keyBindingContainer.TriggerReleased(trackedTouches[e.Touch.Source]); - trackedTouches.Remove(e.Touch.Source); + handleUp(e.Touch.Source); base.OnTouchUp(e); } - protected override bool OnKeyDown(KeyDownEvent e) + private void handleDown(object source, Vector2 position) { - mainContent.Hide(); - return false; + mainContent.Show(); + + TaikoAction taikoAction = getTaikoActionFromInput(position); + + trackedActions.Add(source, taikoAction); + keyBindingContainer.TriggerPressed(taikoAction); + } + + private void handleUp(object source) + { + keyBindingContainer.TriggerReleased(trackedActions[source]); + trackedActions.Remove(source); } private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) From e5ab6652fd5876324a72bfcf7d0de55199901591 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 22 Jul 2022 11:12:24 +0300 Subject: [PATCH 060/124] Fix one more case of referencing old mod select overlay in tests --- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index d35887c443..29a5ef82fe 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -633,7 +633,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("invoke on back button", () => multiplayerComponents.OnBackButton()); - AddAssert("mod overlay is hidden", () => this.ChildrenOfType().Single().State.Value == Visibility.Hidden); + AddAssert("mod overlay is hidden", () => this.ChildrenOfType().Single().UserModsSelectOverlay.State.Value == Visibility.Hidden); AddAssert("dialog overlay is hidden", () => DialogOverlay.State.Value == Visibility.Hidden); From aeeedc40b446ae79030ce8acaf862b6b55e798d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 17:17:38 +0900 Subject: [PATCH 061/124] Add first pass design --- .../UI/DrumTouchInputArea.cs | 121 ++++++++++-------- 1 file changed, 70 insertions(+), 51 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 253ced9a39..d8ae4d9210 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -20,68 +20,74 @@ namespace osu.Game.Rulesets.Taiko.UI /// public class DrumTouchInputArea : Container { - private readonly Circle outerCircle; - private KeyBindingContainer keyBindingContainer = null!; private readonly Dictionary trackedActions = new Dictionary(); - private readonly Container mainContent; + private Container mainContent = null!; - private readonly Circle centreCircle; - - public DrumTouchInputArea() - { - RelativeSizeAxes = Axes.X; - Height = 300; - - Masking = true; - - Children = new Drawable[] - { - mainContent = new Container - { - RelativeSizeAxes = Axes.Both, - Height = 2, - Children = new Drawable[] - { - outerCircle = new Circle - { - FillMode = FillMode.Fit, - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - centreCircle = new Circle - { - FillMode = FillMode.Fit, - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(0.5f), - }, - new Box - { - FillMode = FillMode.Fit, - RelativeSizeAxes = Axes.Y, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = Color4.Black, - Width = 3, - }, - } - }, - }; - } + private Circle centreCircle = null!; + private Circle outerCircle = null!; [BackgroundDependencyLoader] private void load(TaikoInputManager taikoInputManager, OsuColour colours) { Debug.Assert(taikoInputManager.KeyBindingContainer != null); - keyBindingContainer = taikoInputManager.KeyBindingContainer; - outerCircle.Colour = colours.Gray0; + // Container should handle input everywhere. + RelativeSizeAxes = Axes.Both; + + Children = new Drawable[] + { + new Container + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.X, + Height = 300, + Y = 20, + Masking = true, + FillMode = FillMode.Fit, + Children = new Drawable[] + { + mainContent = new Container + { + RelativeSizeAxes = Axes.Both, + Height = 2, + Children = new Drawable[] + { + outerCircle = new Circle + { + FillMode = FillMode.Fit, + Colour = colours.BlueDarker, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + centreCircle = new Circle + { + FillMode = FillMode.Fit, + Colour = colours.YellowDark, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.8f), + }, + new Box + { + Colour = colours.BlueDarker, + RelativeSizeAxes = Axes.Y, + Height = 0.9f, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Width = 7, + }, + } + }, + } + }, + }; } protected override bool OnKeyDown(KeyDownEvent e) @@ -121,6 +127,19 @@ namespace osu.Game.Rulesets.Taiko.UI TaikoAction taikoAction = getTaikoActionFromInput(position); + switch (taikoAction) + { + case TaikoAction.LeftCentre: + case TaikoAction.RightCentre: + centreCircle.FlashColour(Color4.White, 2000, Easing.OutQuint); + break; + + case TaikoAction.LeftRim: + case TaikoAction.RightRim: + outerCircle.FlashColour(Color4.White, 2000, Easing.OutQuint); + break; + } + trackedActions.Add(source, taikoAction); keyBindingContainer.TriggerPressed(taikoAction); } @@ -133,7 +152,7 @@ namespace osu.Game.Rulesets.Taiko.UI private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { - bool centreHit = centreCircle.ScreenSpaceDrawQuad.Contains(inputPosition); + bool centreHit = centreCircle.Contains(inputPosition); bool leftSide = ToLocalSpace(inputPosition).X < DrawWidth / 2; if (leftSide) From 6b69ff19c87c834b670cf933b430e4e7aa248732 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 22 Jul 2022 17:24:46 +0900 Subject: [PATCH 062/124] Remove unused using --- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 29a5ef82fe..bf9b99e3e2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -24,7 +24,6 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; -using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mods; From 2d2d98ab6ee3c2b6a1ee6c904782f5844cac7afd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 17:48:07 +0900 Subject: [PATCH 063/124] Add final design pass --- .../UI/DrumTouchInputArea.cs | 151 +++++++++++++----- 1 file changed, 107 insertions(+), 44 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index d8ae4d9210..5ba2ea282e 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -18,16 +18,20 @@ namespace osu.Game.Rulesets.Taiko.UI /// /// An overlay that captures and displays osu!taiko mouse and touch input. /// - public class DrumTouchInputArea : Container + public class DrumTouchInputArea : VisibilityContainer { + // visibility state affects our child. we always want to handle input. + public override bool PropagatePositionalInputSubTree => true; + public override bool PropagateNonPositionalInputSubTree => true; + private KeyBindingContainer keyBindingContainer = null!; private readonly Dictionary trackedActions = new Dictionary(); private Container mainContent = null!; - private Circle centreCircle = null!; - private Circle outerCircle = null!; + private QuarterCircle leftCentre = null!; + private QuarterCircle rightCentre = null!; [BackgroundDependencyLoader] private void load(TaikoInputManager taikoInputManager, OsuColour colours) @@ -38,6 +42,8 @@ namespace osu.Game.Rulesets.Taiko.UI // Container should handle input everywhere. RelativeSizeAxes = Axes.Both; + const float centre_region = 0.80f; + Children = new Drawable[] { new Container @@ -45,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.UI Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.X, - Height = 300, + Height = 350, Y = 20, Masking = true, FillMode = FillMode.Fit, @@ -54,35 +60,36 @@ namespace osu.Game.Rulesets.Taiko.UI mainContent = new Container { RelativeSizeAxes = Axes.Both, - Height = 2, Children = new Drawable[] { - outerCircle = new Circle + new QuarterCircle(TaikoAction.LeftRim, colours.YellowDark) { - FillMode = FillMode.Fit, - Colour = colours.BlueDarker, - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - centreCircle = new Circle - { - FillMode = FillMode.Fit, - Colour = colours.YellowDark, - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(0.8f), - }, - new Box - { - Colour = colours.BlueDarker, - RelativeSizeAxes = Axes.Y, - Height = 0.9f, Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Width = 7, + Origin = Anchor.BottomRight, + X = -2, }, + new QuarterCircle(TaikoAction.RightRim, colours.YellowDark) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomRight, + X = 2, + Rotation = 90, + }, + leftCentre = new QuarterCircle(TaikoAction.LeftCentre, colours.BlueDark) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomRight, + X = -2, + Scale = new Vector2(centre_region), + }, + rightCentre = new QuarterCircle(TaikoAction.RightCentre, colours.BlueDark) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomRight, + X = 2, + Scale = new Vector2(centre_region), + Rotation = 90, + } } }, } @@ -93,7 +100,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool OnKeyDown(KeyDownEvent e) { // Hide whenever the keyboard is used. - mainContent.Hide(); + Hide(); return false; } @@ -123,23 +130,10 @@ namespace osu.Game.Rulesets.Taiko.UI private void handleDown(object source, Vector2 position) { - mainContent.Show(); + Show(); TaikoAction taikoAction = getTaikoActionFromInput(position); - switch (taikoAction) - { - case TaikoAction.LeftCentre: - case TaikoAction.RightCentre: - centreCircle.FlashColour(Color4.White, 2000, Easing.OutQuint); - break; - - case TaikoAction.LeftRim: - case TaikoAction.RightRim: - outerCircle.FlashColour(Color4.White, 2000, Easing.OutQuint); - break; - } - trackedActions.Add(source, taikoAction); keyBindingContainer.TriggerPressed(taikoAction); } @@ -152,7 +146,7 @@ namespace osu.Game.Rulesets.Taiko.UI private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { - bool centreHit = centreCircle.Contains(inputPosition); + bool centreHit = leftCentre.Contains(inputPosition) || rightCentre.Contains(inputPosition); bool leftSide = ToLocalSpace(inputPosition).X < DrawWidth / 2; if (leftSide) @@ -160,5 +154,74 @@ namespace osu.Game.Rulesets.Taiko.UI return centreHit ? TaikoAction.RightCentre : TaikoAction.RightRim; } + + protected override void PopIn() + { + mainContent.FadeIn(500, Easing.OutQuint); + } + + protected override void PopOut() + { + mainContent.FadeOut(300); + } + + private class QuarterCircle : CompositeDrawable, IKeyBindingHandler + { + private readonly Circle overlay; + + private readonly TaikoAction handledAction; + + private readonly Circle circle; + + public override bool Contains(Vector2 screenSpacePos) => circle.Contains(screenSpacePos); + + public QuarterCircle(TaikoAction handledAction, Color4 colour) + { + this.handledAction = handledAction; + RelativeSizeAxes = Axes.Both; + + FillMode = FillMode.Fit; + + InternalChildren = new Drawable[] + { + new Container + { + Masking = true, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + circle = new Circle + { + RelativeSizeAxes = Axes.Both, + Colour = colour, + Alpha = 0.8f, + Scale = new Vector2(2), + }, + overlay = new Circle + { + Alpha = 0, + RelativeSizeAxes = Axes.Both, + Blending = BlendingParameters.Additive, + Colour = colour, + Scale = new Vector2(2), + } + } + }, + }; + } + + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Action == handledAction) + overlay.FadeTo(0.4f, 80, Easing.OutQuint); + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + if (e.Action == handledAction) + overlay.FadeOut(1000, Easing.OutQuint); + } + } } } From 4279ac866c293291f6a0121684f414fa33d00108 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 17:58:52 +0900 Subject: [PATCH 064/124] Tidy up unnecessary changes and remove unused classes --- .../Skinning/Legacy/LegacyInputDrum.cs | 2 - osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs | 186 ------------------ 2 files changed, 188 deletions(-) delete mode 100644 osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index a03d987efd..101f70b97a 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -35,8 +35,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { Child = content = new Container { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, Size = new Vector2(180, 200), Children = new Drawable[] { diff --git a/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs b/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs deleted file mode 100644 index 21b83e5495..0000000000 --- a/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable -using System; -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.Input.Bindings; -using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osuTK; - -namespace osu.Game.Rulesets.Taiko.UI -{ - /// - /// A component of the playfield that captures input and displays input as a drum. - /// - internal class TouchInputDrum : Container - { - public float CentreSize = 0.7f; - private const float middle_split = 0.025f; - - public TouchInputDrum() - { - RelativeSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load() - { - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Scale = new Vector2(0.9f), - Children = new Drawable[] - { - new TaikoHalfDrum(false, CentreSize) - { - Name = "Left Half", - Anchor = Anchor.Centre, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.X, - X = -middle_split / 2, - RimAction = TaikoAction.LeftRim, - CentreAction = TaikoAction.LeftCentre - }, - new TaikoHalfDrum(true, CentreSize) - { - Name = "Right Half", - Anchor = Anchor.Centre, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.X, - X = middle_split / 2, - RimAction = TaikoAction.RightRim, - CentreAction = TaikoAction.RightCentre - } - } - } - }; - } - - /// - /// A half-drum. Contains one centre and one rim hit. - /// - private class TaikoHalfDrum : Container, IKeyBindingHandler - { - /// - /// The key to be used for the rim of the half-drum. - /// - public TaikoAction RimAction; - - /// - /// The key to be used for the centre of the half-drum. - /// - public TaikoAction CentreAction; - - private readonly Sprite rim; - private readonly Sprite rimHit; - private readonly Sprite centre; - private readonly Sprite centreHit; - - public TaikoHalfDrum(bool flipped, float centreSize) - { - 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, - Blending = BlendingParameters.Additive, - }, - centre = new Sprite - { - Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(centreSize) - }, - centreHit = new Sprite - { - Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(centreSize), - Alpha = 0, - Blending = BlendingParameters.Additive - } - }; - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures, OsuColour colours) - { - rim.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer"); - rimHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer-hit"); - centre.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner"); - centreHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner-hit"); - - rimHit.Colour = colours.Blue; - centreHit.Colour = colours.Pink; - } - - public bool OnPressed(KeyBindingPressEvent e) - { - Drawable target = null; - Drawable back = null; - - if (e.Action == CentreAction) - { - target = centreHit; - back = centre; - } - else if (e.Action == RimAction) - { - target = rimHit; - back = rim; - } - - if (target != null) - { - const float scale_amount = 0.05f; - const float alpha_amount = 0.5f; - - const float down_time = 40; - const float up_time = 1000; - - back.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint) - .Then() - .ScaleTo(1, up_time, Easing.OutQuint); - - target.Animate( - t => t.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint), - t => t.FadeTo(Math.Min(target.Alpha + alpha_amount, 1), down_time, Easing.OutQuint) - ).Then( - t => t.ScaleTo(1, up_time, Easing.OutQuint), - t => t.FadeOut(up_time, Easing.OutQuint) - ); - } - - return false; - } - - public void OnReleased(KeyBindingReleaseEvent e) - { - } - } - } -} From ec98693ccaa7c34468ac8784d450352442415274 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 18:07:10 +0900 Subject: [PATCH 065/124] Add back standard mouse bindings support and only handle mouse when inside the visible zone --- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 ++ osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 9a5f5791ab..223e268d7f 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -50,6 +50,8 @@ namespace osu.Game.Rulesets.Taiko public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] { + new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre), + new KeyBinding(InputKey.MouseRight, TaikoAction.LeftRim), new KeyBinding(InputKey.D, TaikoAction.LeftRim), new KeyBinding(InputKey.F, TaikoAction.LeftCentre), new KeyBinding(InputKey.J, TaikoAction.RightCentre), diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 5ba2ea282e..8813cbbbc7 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -32,6 +32,8 @@ namespace osu.Game.Rulesets.Taiko.UI private QuarterCircle leftCentre = null!; private QuarterCircle rightCentre = null!; + private QuarterCircle leftRim = null!; + private QuarterCircle rightRim = null!; [BackgroundDependencyLoader] private void load(TaikoInputManager taikoInputManager, OsuColour colours) @@ -62,13 +64,13 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new QuarterCircle(TaikoAction.LeftRim, colours.YellowDark) + leftRim = new QuarterCircle(TaikoAction.LeftRim, colours.YellowDark) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomRight, X = -2, }, - new QuarterCircle(TaikoAction.RightRim, colours.YellowDark) + rightRim = new QuarterCircle(TaikoAction.RightRim, colours.YellowDark) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomRight, @@ -106,12 +108,18 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool OnMouseDown(MouseDownEvent e) { + if (validMouse(e)) + return false; + handleDown(e.Button, e.ScreenSpaceMousePosition); return true; } protected override void OnMouseUp(MouseUpEvent e) { + if (validMouse(e)) + return; + handleUp(e.Button); base.OnMouseUp(e); } @@ -144,6 +152,10 @@ namespace osu.Game.Rulesets.Taiko.UI trackedActions.Remove(source); } + private bool validMouse(MouseButtonEvent e) => + !leftRim.Contains(e.ScreenSpaceMouseDownPosition) + && !rightRim.Contains(e.ScreenSpaceMouseDownPosition); + private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { bool centreHit = leftCentre.Contains(inputPosition) || rightCentre.Contains(inputPosition); From 9e5e03af5d8e3c899a06bb7561702473a5d2c3fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 18:16:01 +0900 Subject: [PATCH 066/124] Adjust colours to match default skin for now --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 8813cbbbc7..fb22921acf 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics; using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -64,27 +65,27 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - leftRim = new QuarterCircle(TaikoAction.LeftRim, colours.YellowDark) + leftRim = new QuarterCircle(TaikoAction.LeftRim, colours.Blue) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomRight, X = -2, }, - rightRim = new QuarterCircle(TaikoAction.RightRim, colours.YellowDark) + rightRim = new QuarterCircle(TaikoAction.RightRim, colours.Blue) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomRight, X = 2, Rotation = 90, }, - leftCentre = new QuarterCircle(TaikoAction.LeftCentre, colours.BlueDark) + leftCentre = new QuarterCircle(TaikoAction.LeftCentre, colours.Pink) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomRight, X = -2, Scale = new Vector2(centre_region), }, - rightCentre = new QuarterCircle(TaikoAction.RightCentre, colours.BlueDark) + rightCentre = new QuarterCircle(TaikoAction.RightCentre, colours.Pink) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomRight, @@ -205,7 +206,7 @@ namespace osu.Game.Rulesets.Taiko.UI circle = new Circle { RelativeSizeAxes = Axes.Both, - Colour = colour, + Colour = colour.Multiply(1.4f).Darken(2.8f), Alpha = 0.8f, Scale = new Vector2(2), }, @@ -225,7 +226,7 @@ namespace osu.Game.Rulesets.Taiko.UI public bool OnPressed(KeyBindingPressEvent e) { if (e.Action == handledAction) - overlay.FadeTo(0.4f, 80, Easing.OutQuint); + overlay.FadeTo(1f, 80, Easing.OutQuint); return false; } From ee5e27638ee829df1f4c99e3061368d0387a01b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 18:19:33 +0900 Subject: [PATCH 067/124] Fix method name not matching actual implementation --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index fb22921acf..bf91ba7d49 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -109,7 +109,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool OnMouseDown(MouseDownEvent e) { - if (validMouse(e)) + if (!validMouse(e)) return false; handleDown(e.Button, e.ScreenSpaceMousePosition); @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override void OnMouseUp(MouseUpEvent e) { - if (validMouse(e)) + if (!validMouse(e)) return; handleUp(e.Button); @@ -154,8 +154,7 @@ namespace osu.Game.Rulesets.Taiko.UI } private bool validMouse(MouseButtonEvent e) => - !leftRim.Contains(e.ScreenSpaceMouseDownPosition) - && !rightRim.Contains(e.ScreenSpaceMouseDownPosition); + leftRim.Contains(e.ScreenSpaceMouseDownPosition) || rightRim.Contains(e.ScreenSpaceMouseDownPosition); private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { From 28586c704dbf6e5a2b1bc43e8f93c13e3b8f72e5 Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Fri, 22 Jul 2022 05:43:05 -0500 Subject: [PATCH 068/124] Add showModdedValue parameter to StatisticRow --- .../Screens/Select/Details/AdvancedStats.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 693f182065..80e9989c47 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Select.Details } } - public AdvancedStats() + public AdvancedStats(bool updateWithModSelection = true) { Child = new FillFlowContainer { @@ -65,11 +65,11 @@ namespace osu.Game.Screens.Select.Details AutoSizeAxes = Axes.Y, Children = new[] { - FirstValue = new StatisticRow(), // circle size/key amount - HpDrain = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsDrain }, - Accuracy = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsAccuracy }, - ApproachRate = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsAr }, - starDifficulty = new StatisticRow(10, true) { Title = BeatmapsetsStrings.ShowStatsStars }, + FirstValue = new StatisticRow(updateWithModSelection), // circle size/key amount + HpDrain = new StatisticRow(updateWithModSelection) { Title = BeatmapsetsStrings.ShowStatsDrain }, + Accuracy = new StatisticRow(updateWithModSelection) { Title = BeatmapsetsStrings.ShowStatsAccuracy }, + ApproachRate = new StatisticRow(updateWithModSelection) { Title = BeatmapsetsStrings.ShowStatsAr }, + starDifficulty = new StatisticRow(updateWithModSelection, 10, true) { Title = BeatmapsetsStrings.ShowStatsStars }, }, }; } @@ -183,6 +183,7 @@ namespace osu.Game.Screens.Select.Details private readonly OsuSpriteText name, valueText; private readonly Bar bar; public readonly Bar ModBar; + private readonly bool showModdedValue; [Resolved] private OsuColour colours { get; set; } @@ -203,6 +204,9 @@ namespace osu.Game.Screens.Select.Details if (value == this.value) return; + if (!showModdedValue) + value.adjustedValue = null; + this.value = value; bar.Length = value.baseValue / maxValue; @@ -225,13 +229,14 @@ namespace osu.Game.Screens.Select.Details set => bar.AccentColour = value; } - public StatisticRow(float maxValue = 10, bool forceDecimalPlaces = false) + public StatisticRow(bool showModdedValue, float maxValue = 10, bool forceDecimalPlaces = false) { this.maxValue = maxValue; this.forceDecimalPlaces = forceDecimalPlaces; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Padding = new MarginPadding { Vertical = 2.5f }; + this.showModdedValue = showModdedValue; Children = new Drawable[] { From d9d35bb847a8f424c1379763019dbb0dc05c52a2 Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Fri, 22 Jul 2022 05:43:56 -0500 Subject: [PATCH 069/124] Set BeatmapSetHeaderContent details to not show modded values --- osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs | 2 +- osu.Game/Overlays/BeatmapSet/Details.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index c9e97d5f2f..68f5f2be6e 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -222,7 +222,7 @@ namespace osu.Game.Overlays.BeatmapSet TextSize = 14, TextPadding = new MarginPadding { Horizontal = 35, Vertical = 10 } }, - Details = new Details(), + Details = new Details(false), }, }, } diff --git a/osu.Game/Overlays/BeatmapSet/Details.cs b/osu.Game/Overlays/BeatmapSet/Details.cs index 6db54db811..092646f0ca 100644 --- a/osu.Game/Overlays/BeatmapSet/Details.cs +++ b/osu.Game/Overlays/BeatmapSet/Details.cs @@ -59,7 +59,7 @@ namespace osu.Game.Overlays.BeatmapSet ratingBox.Alpha = BeatmapSet?.Status > 0 ? 1 : 0; } - public Details() + public Details(bool updateWithModSelection = true) { Width = BeatmapSetOverlay.RIGHT_WIDTH; AutoSizeAxes = Axes.Y; @@ -82,7 +82,7 @@ namespace osu.Game.Overlays.BeatmapSet }, new DetailBox { - Child = advanced = new AdvancedStats + Child = advanced = new AdvancedStats(updateWithModSelection) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, From 7baa1a7e85a366478772a972f8e16e30aa9037ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 20:19:13 +0900 Subject: [PATCH 070/124] Attempt to fix crashing from weird input interactions --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index bf91ba7d49..a7d9bd18c5 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -143,6 +143,10 @@ namespace osu.Game.Rulesets.Taiko.UI TaikoAction taikoAction = getTaikoActionFromInput(position); + // Not too sure how this can happen, but let's avoid throwing. + if (trackedActions.ContainsKey(source)) + return; + trackedActions.Add(source, taikoAction); keyBindingContainer.TriggerPressed(taikoAction); } From 6ce8e74e6b75b8283921190344e25584b981c0df Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 22 Jul 2022 19:06:31 +0900 Subject: [PATCH 071/124] Add panel appearance sounds --- .../Expanded/ExpandedPanelTopContent.cs | 22 +++++++++++++-- osu.Game/Screens/Ranking/ResultsScreen.cs | 10 ++++++- osu.Game/Screens/Ranking/ScorePanel.cs | 27 ++++++++++++++++--- .../Ranking/Statistics/StatisticsPanel.cs | 27 ++++++++++++++++--- 4 files changed, 77 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelTopContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelTopContent.cs index d2b2a842b8..2708090855 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelTopContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelTopContent.cs @@ -4,6 +4,8 @@ #nullable disable using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; @@ -21,13 +23,19 @@ namespace osu.Game.Screens.Ranking.Expanded { private readonly APIUser user; + private Sample appearanceSample; + + private readonly bool playAppearanceSound; + /// /// Creates a new . /// /// The to display. - public ExpandedPanelTopContent(APIUser user) + /// Whether the appearance sample should play + public ExpandedPanelTopContent(APIUser user, bool playAppearanceSound = false) { this.user = user; + this.playAppearanceSound = playAppearanceSound; Anchor = Anchor.TopCentre; Origin = Anchor.Centre; @@ -35,8 +43,10 @@ namespace osu.Game.Screens.Ranking.Expanded } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { + appearanceSample = audio.Samples.Get(@"Results/score-panel-top-appear"); + InternalChild = new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -62,5 +72,13 @@ namespace osu.Game.Screens.Ranking.Expanded } }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (playAppearanceSound) + appearanceSample?.Play(); + } } } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 2f8868c06d..c530febcae 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -7,6 +7,8 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -60,6 +62,8 @@ namespace osu.Game.Screens.Ranking private readonly bool allowRetry; private readonly bool allowWatchingReplay; + private Sample popInSample; + protected ResultsScreen(ScoreInfo score, bool allowRetry, bool allowWatchingReplay = true) { Score = score; @@ -70,10 +74,12 @@ namespace osu.Game.Screens.Ranking } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { FillFlowContainer buttons; + popInSample = audio.Samples.Get(@"UI/overlay-pop-in"); + InternalChild = new GridContainer { RelativeSizeAxes = Axes.Both, @@ -244,6 +250,8 @@ namespace osu.Game.Screens.Ranking }); bottomPanel.FadeTo(1, 250); + + popInSample?.Play(); } public override bool OnExiting(ScreenExitEvent e) diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index babdd4b149..322124313f 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -6,13 +6,16 @@ using System; using osu.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Contracted; using osu.Game.Screens.Ranking.Expanded; @@ -107,6 +110,8 @@ namespace osu.Game.Screens.Ranking private Container middleLayerContentContainer; private Drawable middleLayerContent; + private DrawableSample samplePanelFocus; + public ScorePanel(ScoreInfo score, bool isNewLocalScore = false) { Score = score; @@ -116,7 +121,7 @@ namespace osu.Game.Screens.Ranking } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { // ScorePanel doesn't include the top extruding area in its own size. // Adding a manual offset here allows the expanded version to take on an "acceptable" vertical centre when at 100% UI scale. @@ -174,7 +179,8 @@ namespace osu.Game.Screens.Ranking }, middleLayerContentContainer = new Container { RelativeSizeAxes = Axes.Both } } - } + }, + samplePanelFocus = new DrawableSample(audio.Samples.Get(@"Results/score-panel-focus")) } }; } @@ -202,12 +208,26 @@ namespace osu.Game.Screens.Ranking state = value; if (IsLoaded) + { updateState(); + if (value == PanelState.Expanded) + playAppearSample(); + } + StateChanged?.Invoke(value); } } + private void playAppearSample() + { + var channel = samplePanelFocus?.GetChannel(); + if (channel == null) return; + + channel.Frequency.Value = 0.99 + RNG.NextDouble(0.2); + channel.Play(); + } + private void updateState() { topLayerContent?.FadeOut(content_fade_duration).Expire(); @@ -221,7 +241,8 @@ namespace osu.Game.Screens.Ranking topLayerBackground.FadeColour(expanded_top_layer_colour, RESIZE_DURATION, Easing.OutQuint); middleLayerBackground.FadeColour(expanded_middle_layer_colour, RESIZE_DURATION, Easing.OutQuint); - topLayerContentContainer.Add(topLayerContent = new ExpandedPanelTopContent(Score.User) { Alpha = 0 }); + bool firstLoad = topLayerContent == null; + topLayerContentContainer.Add(topLayerContent = new ExpandedPanelTopContent(Score.User, firstLoad) { Alpha = 0 }); middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, displayWithFlair) { Alpha = 0 }); // only the first expanded display should happen with flair. diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index ab68dec92d..435162e057 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -8,6 +8,8 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -35,6 +37,10 @@ namespace osu.Game.Screens.Ranking.Statistics private readonly Container content; private readonly LoadingSpinner spinner; + private bool wasOpened; + private Sample popInSample; + private Sample popOutSample; + public StatisticsPanel() { InternalChild = new Container @@ -56,9 +62,12 @@ namespace osu.Game.Screens.Ranking.Statistics } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { Score.BindValueChanged(populateStatistics, true); + + popInSample = audio.Samples.Get(@"Results/statistics-panel-pop-in"); + popOutSample = audio.Samples.Get(@"Results/statistics-panel-pop-out"); } private CancellationTokenSource loadCancellation; @@ -216,9 +225,21 @@ namespace osu.Game.Screens.Ranking.Statistics return true; } - protected override void PopIn() => this.FadeIn(150, Easing.OutQuint); + protected override void PopIn() + { + this.FadeIn(150, Easing.OutQuint); - protected override void PopOut() => this.FadeOut(150, Easing.OutQuint); + popInSample?.Play(); + wasOpened = true; + } + + protected override void PopOut() + { + this.FadeOut(150, Easing.OutQuint); + + if (wasOpened) + popOutSample?.Play(); + } protected override void Dispose(bool isDisposing) { From 89da21b6dee6856d690397f87f9c3fa4998e327f Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 22 Jul 2022 19:48:29 +0900 Subject: [PATCH 072/124] Add total score counter sfx --- .../Expanded/ExpandedPanelMiddleContent.cs | 2 +- .../Ranking/Expanded/TotalScoreCounter.cs | 62 ++++++++++++++++++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 5b8777d2a4..0f202e5e08 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -133,7 +133,7 @@ namespace osu.Game.Screens.Ranking.Expanded FillMode = FillMode.Fit, } }, - scoreCounter = new TotalScoreCounter + scoreCounter = new TotalScoreCounter(!withFlair) { Margin = new MarginPadding { Top = 0, Bottom = 5 }, Current = { Value = 0 }, diff --git a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs index 0b06dedc44..1e3443c2df 100644 --- a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs +++ b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs @@ -3,7 +3,11 @@ #nullable disable +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Audio; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -22,11 +26,32 @@ namespace osu.Game.Screens.Ranking.Expanded protected override Easing RollingEasing => AccuracyCircle.ACCURACY_TRANSFORM_EASING; - public TotalScoreCounter() + private readonly bool playSound; + private bool isTicking; + private readonly Bindable tickPlaybackRate = new Bindable(); + private double lastSampleTime; + private DrawableSample sampleTick; + + public TotalScoreCounter(bool playSound = false) { // Todo: AutoSize X removed here due to https://github.com/ppy/osu-framework/issues/3369 AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; + this.playSound = playSound; + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + AddInternal(sampleTick = new DrawableSample(audio.Samples.Get(@"Results/score-tick-lesser"))); + lastSampleTime = Time.Current; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(startTickingPlayback); } protected override LocalisableString FormatCount(long count) => count.ToString("N0"); @@ -39,5 +64,40 @@ namespace osu.Game.Screens.Ranking.Expanded s.Font = OsuFont.Torus.With(size: 60, weight: FontWeight.Light, fixedWidth: true); s.Spacing = new Vector2(-5, 0); }); + + protected override void Update() + { + base.Update(); + + if (playSound && isTicking) playTickSample(); + } + + private void startTickingPlayback(ValueChangedEvent _) + { + const double tick_debounce_rate_start = 10f; + const double tick_debounce_rate_end = 100f; + const double tick_volume_start = 0.5f; + const double tick_volume_end = 1.0f; + double tickDuration = RollingDuration - AccuracyCircle.ACCURACY_TRANSFORM_DELAY - AccuracyCircle.RANK_CIRCLE_TRANSFORM_DELAY; + + this.TransformBindableTo(tickPlaybackRate, tick_debounce_rate_start); + this.TransformBindableTo(tickPlaybackRate, tick_debounce_rate_end, tickDuration, Easing.OutSine); + sampleTick.VolumeTo(tick_volume_start).Then().VolumeTo(tick_volume_end, tickDuration, Easing.OutSine); + + Scheduler.AddDelayed(stopTickingPlayback, tickDuration); + + isTicking = true; + } + + private void stopTickingPlayback() => isTicking = false; + + private void playTickSample() + { + if (Time.Current > lastSampleTime + tickPlaybackRate.Value) + { + sampleTick?.Play(); + lastSampleTime = Time.Current; + } + } } } From 62d4d4b05559334686737b59fb1667273e505a66 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 22 Jul 2022 19:52:42 +0900 Subject: [PATCH 073/124] Add dynamic panning fun to score panel sfx --- osu.Game/Screens/Ranking/ScorePanel.cs | 8 ++++---- osu.Game/Screens/Ranking/ScorePanelList.cs | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 322124313f..aa4b732bb6 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -96,9 +96,9 @@ namespace osu.Game.Screens.Ranking public readonly ScoreInfo Score; - private bool displayWithFlair; + public DrawableAudioMixer Mixer; - private Container content; + private bool displayWithFlair; private Container topLayerContainer; private Drawable topLayerBackground; @@ -127,7 +127,7 @@ namespace osu.Game.Screens.Ranking // Adding a manual offset here allows the expanded version to take on an "acceptable" vertical centre when at 100% UI scale. const float vertical_fudge = 20; - InternalChild = content = new Container + InternalChild = Mixer = new DrawableAudioMixer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -265,7 +265,7 @@ namespace osu.Game.Screens.Ranking break; } - content.ResizeTo(Size, RESIZE_DURATION, Easing.OutQuint); + Mixer.ResizeTo(Size, RESIZE_DURATION, Easing.OutQuint); bool topLayerExpanded = topLayerContainer.Y < 0; diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 4f9e61a4a1..d312953a38 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -344,6 +344,23 @@ namespace osu.Game.Screens.Ranking private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() .OrderByDescending(GetLayoutPosition) .ThenBy(s => s.Panel.Score.OnlineID); + + protected override void Update() + { + foreach (ScorePanelTrackingContainer trackingContainer in FlowingChildren.OfType()) + { + var panel = trackingContainer.Panel; + + if (panel.State != PanelState.Expanded) continue; + + var scrollContainer = Parent.Parent; + float balance = scrollContainer.ToLocalSpace(panel.ToScreenSpace(panel.BoundingBox.Centre)).X / scrollContainer.RelativeToAbsoluteFactor.X; + + panel.Mixer.Balance.Value = Math.Clamp(-1 + balance * 2, -1, 1); + } + + base.Update(); + } } private class Scroll : OsuScrollContainer From 9c0d79389639becc7491a9b6c3af832cd90c8605 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Jul 2022 00:04:11 +0900 Subject: [PATCH 074/124] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index c83b7872ac..7c4582adf5 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 4fa4b804ab..ec0b392481 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index dc012ab2fa..73d219cb66 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -62,7 +62,7 @@ - + From e08e4fc4260b8e5b075eef360ee4054527f436ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Jul 2022 00:04:14 +0900 Subject: [PATCH 075/124] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 7c4582adf5..97fc97153c 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ec0b392481..d95753179f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 73d219cb66..5455c94998 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 9f045209b93f4cead222a89cbc7ed041dbb3abc1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Jul 2022 02:09:08 +0900 Subject: [PATCH 076/124] Simplify score panel balance adjustment --- osu.Game/Screens/Ranking/ScorePanel.cs | 15 ++++++++++++--- osu.Game/Screens/Ranking/ScorePanelList.cs | 17 ----------------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index aa4b732bb6..9dcaafd9f9 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -96,7 +96,10 @@ namespace osu.Game.Screens.Ranking public readonly ScoreInfo Score; - public DrawableAudioMixer Mixer; + [Resolved] + private OsuGame game { get; set; } + + private DrawableAudioMixer mixer; private bool displayWithFlair; @@ -127,7 +130,7 @@ namespace osu.Game.Screens.Ranking // Adding a manual offset here allows the expanded version to take on an "acceptable" vertical centre when at 100% UI scale. const float vertical_fudge = 20; - InternalChild = Mixer = new DrawableAudioMixer + InternalChild = mixer = new DrawableAudioMixer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -219,6 +222,12 @@ namespace osu.Game.Screens.Ranking } } + protected override void Update() + { + base.Update(); + mixer.Balance.Value = (ScreenSpaceDrawQuad.Centre.X / game.ScreenSpaceDrawQuad.Width) * 2 - 1; + } + private void playAppearSample() { var channel = samplePanelFocus?.GetChannel(); @@ -265,7 +274,7 @@ namespace osu.Game.Screens.Ranking break; } - Mixer.ResizeTo(Size, RESIZE_DURATION, Easing.OutQuint); + mixer.ResizeTo(Size, RESIZE_DURATION, Easing.OutQuint); bool topLayerExpanded = topLayerContainer.Y < 0; diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index d312953a38..4f9e61a4a1 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -344,23 +344,6 @@ namespace osu.Game.Screens.Ranking private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() .OrderByDescending(GetLayoutPosition) .ThenBy(s => s.Panel.Score.OnlineID); - - protected override void Update() - { - foreach (ScorePanelTrackingContainer trackingContainer in FlowingChildren.OfType()) - { - var panel = trackingContainer.Panel; - - if (panel.State != PanelState.Expanded) continue; - - var scrollContainer = Parent.Parent; - float balance = scrollContainer.ToLocalSpace(panel.ToScreenSpace(panel.BoundingBox.Centre)).X / scrollContainer.RelativeToAbsoluteFactor.X; - - panel.Mixer.Balance.Value = Math.Clamp(-1 + balance * 2, -1, 1); - } - - base.Update(); - } } private class Scroll : OsuScrollContainer From f3ceabc53f074d610a3fad562c0f2620ee86a0ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Jul 2022 19:14:39 +0200 Subject: [PATCH 077/124] Rename `ModSelect{Overlay -> }Panel` --- osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs | 6 +++--- osu.Game/Overlays/Mods/ModColumn.cs | 10 +++++----- osu.Game/Overlays/Mods/ModPanel.cs | 2 +- osu.Game/Overlays/Mods/ModPresetPanel.cs | 2 +- .../{ModSelectOverlayPanel.cs => ModSelectPanel.cs} | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) rename osu.Game/Overlays/Mods/{ModSelectOverlayPanel.cs => ModSelectPanel.cs} (98%) diff --git a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs index c0a4cf2a25..255d01466f 100644 --- a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs +++ b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs @@ -60,7 +60,7 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Masking = true, - CornerRadius = ModSelectOverlayPanel.CORNER_RADIUS, + CornerRadius = ModSelectPanel.CORNER_RADIUS, Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0), Children = new Drawable[] { @@ -69,7 +69,7 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Y, - Width = multiplier_value_area_width + ModSelectOverlayPanel.CORNER_RADIUS + Width = multiplier_value_area_width + ModSelectPanel.CORNER_RADIUS }, new GridContainer { @@ -89,7 +89,7 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Masking = true, - CornerRadius = ModSelectOverlayPanel.CORNER_RADIUS, + CornerRadius = ModSelectPanel.CORNER_RADIUS, Children = new Drawable[] { contentBackground = new Box diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 63a51aaad0..beb4856477 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -105,20 +105,20 @@ namespace osu.Game.Overlays.Mods TopLevelContent = new Container { RelativeSizeAxes = Axes.Both, - CornerRadius = ModSelectOverlayPanel.CORNER_RADIUS, + CornerRadius = ModSelectPanel.CORNER_RADIUS, Masking = true, Children = new Drawable[] { new Container { RelativeSizeAxes = Axes.X, - Height = header_height + ModSelectOverlayPanel.CORNER_RADIUS, + Height = header_height + ModSelectPanel.CORNER_RADIUS, Children = new Drawable[] { headerBackground = new Box { RelativeSizeAxes = Axes.X, - Height = header_height + ModSelectOverlayPanel.CORNER_RADIUS + Height = header_height + ModSelectPanel.CORNER_RADIUS }, headerText = new OsuTextFlowContainer(t => { @@ -135,7 +135,7 @@ namespace osu.Game.Overlays.Mods Padding = new MarginPadding { Horizontal = 17, - Bottom = ModSelectOverlayPanel.CORNER_RADIUS + Bottom = ModSelectPanel.CORNER_RADIUS } } } @@ -148,7 +148,7 @@ namespace osu.Game.Overlays.Mods { RelativeSizeAxes = Axes.Both, Masking = true, - CornerRadius = ModSelectOverlayPanel.CORNER_RADIUS, + CornerRadius = ModSelectPanel.CORNER_RADIUS, BorderThickness = 3, Children = new Drawable[] { diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index 5de6290248..6ef6ab0595 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -11,7 +11,7 @@ using osuTK; namespace osu.Game.Overlays.Mods { - public class ModPanel : ModSelectOverlayPanel + public class ModPanel : ModSelectPanel { public Mod Mod => modState.Mod; public override BindableBool Active => modState.Active; diff --git a/osu.Game/Overlays/Mods/ModPresetPanel.cs b/osu.Game/Overlays/Mods/ModPresetPanel.cs index 39f092b91a..47e2f25538 100644 --- a/osu.Game/Overlays/Mods/ModPresetPanel.cs +++ b/osu.Game/Overlays/Mods/ModPresetPanel.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Overlays.Mods { - public class ModPresetPanel : ModSelectOverlayPanel, IHasCustomTooltip + public class ModPresetPanel : ModSelectPanel, IHasCustomTooltip { public readonly ModPreset Preset; diff --git a/osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs b/osu.Game/Overlays/Mods/ModSelectPanel.cs similarity index 98% rename from osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs rename to osu.Game/Overlays/Mods/ModSelectPanel.cs index a794884d7d..abf327a388 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs +++ b/osu.Game/Overlays/Mods/ModSelectPanel.cs @@ -24,7 +24,7 @@ using osuTK.Input; namespace osu.Game.Overlays.Mods { - public abstract class ModSelectOverlayPanel : OsuClickableContainer, IHasAccentColour + public abstract class ModSelectPanel : OsuClickableContainer, IHasAccentColour { public abstract BindableBool Active { get; } @@ -70,7 +70,7 @@ namespace osu.Game.Overlays.Mods private Sample? sampleOff; private Sample? sampleOn; - protected ModSelectOverlayPanel() + protected ModSelectPanel() { RelativeSizeAxes = Axes.X; Height = HEIGHT; From bb46ba66e03df7d0f102f3ff43ad1cb7a34b4be2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Jul 2022 02:25:37 +0900 Subject: [PATCH 078/124] Simplify `TotalScoreCounter` tick playback logic --- .../Ranking/Expanded/TotalScoreCounter.cs | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs index 1e3443c2df..33298063fc 100644 --- a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs +++ b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs @@ -26,32 +26,35 @@ namespace osu.Game.Screens.Ranking.Expanded protected override Easing RollingEasing => AccuracyCircle.ACCURACY_TRANSFORM_EASING; - private readonly bool playSound; - private bool isTicking; + private readonly bool playSamples; + private readonly Bindable tickPlaybackRate = new Bindable(); + private double lastSampleTime; + private DrawableSample sampleTick; - public TotalScoreCounter(bool playSound = false) + public TotalScoreCounter(bool playSamples = false) { // Todo: AutoSize X removed here due to https://github.com/ppy/osu-framework/issues/3369 AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; - this.playSound = playSound; + + this.playSamples = playSamples; } [BackgroundDependencyLoader] private void load(AudioManager audio) { AddInternal(sampleTick = new DrawableSample(audio.Samples.Get(@"Results/score-tick-lesser"))); - lastSampleTime = Time.Current; } protected override void LoadComplete() { base.LoadComplete(); - Current.BindValueChanged(startTickingPlayback); + if (playSamples) + Current.BindValueChanged(_ => startTicking()); } protected override LocalisableString FormatCount(long count) => count.ToString("N0"); @@ -65,39 +68,36 @@ namespace osu.Game.Screens.Ranking.Expanded s.Spacing = new Vector2(-5, 0); }); - protected override void Update() + public override long DisplayedCount { - base.Update(); + get => base.DisplayedCount; + set + { + if (base.DisplayedCount == value) + return; - if (playSound && isTicking) playTickSample(); + base.DisplayedCount = value; + + if (playSamples && Time.Current > lastSampleTime + tickPlaybackRate.Value) + { + sampleTick?.Play(); + lastSampleTime = Time.Current; + } + } } - private void startTickingPlayback(ValueChangedEvent _) + private void startTicking() { const double tick_debounce_rate_start = 10f; const double tick_debounce_rate_end = 100f; const double tick_volume_start = 0.5f; const double tick_volume_end = 1.0f; - double tickDuration = RollingDuration - AccuracyCircle.ACCURACY_TRANSFORM_DELAY - AccuracyCircle.RANK_CIRCLE_TRANSFORM_DELAY; + + double tickDuration = RollingDuration; this.TransformBindableTo(tickPlaybackRate, tick_debounce_rate_start); this.TransformBindableTo(tickPlaybackRate, tick_debounce_rate_end, tickDuration, Easing.OutSine); sampleTick.VolumeTo(tick_volume_start).Then().VolumeTo(tick_volume_end, tickDuration, Easing.OutSine); - - Scheduler.AddDelayed(stopTickingPlayback, tickDuration); - - isTicking = true; - } - - private void stopTickingPlayback() => isTicking = false; - - private void playTickSample() - { - if (Time.Current > lastSampleTime + tickPlaybackRate.Value) - { - sampleTick?.Play(); - lastSampleTime = Time.Current; - } } } } From 475679ca66da4f34051d14c52b0f01b9e5704920 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Jul 2022 02:27:47 +0900 Subject: [PATCH 079/124] Fix DI failure --- osu.Game/Screens/Ranking/ScorePanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 9dcaafd9f9..0bcfa0da1f 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -97,7 +97,7 @@ namespace osu.Game.Screens.Ranking public readonly ScoreInfo Score; [Resolved] - private OsuGame game { get; set; } + private OsuGameBase game { get; set; } private DrawableAudioMixer mixer; From db632c0d6e5f40674d7e79136520b2ffd39145fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Jul 2022 02:38:00 +0900 Subject: [PATCH 080/124] Inline rolling duration --- osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs index 33298063fc..c7286a1838 100644 --- a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs +++ b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs @@ -93,11 +93,9 @@ namespace osu.Game.Screens.Ranking.Expanded const double tick_volume_start = 0.5f; const double tick_volume_end = 1.0f; - double tickDuration = RollingDuration; - this.TransformBindableTo(tickPlaybackRate, tick_debounce_rate_start); - this.TransformBindableTo(tickPlaybackRate, tick_debounce_rate_end, tickDuration, Easing.OutSine); - sampleTick.VolumeTo(tick_volume_start).Then().VolumeTo(tick_volume_end, tickDuration, Easing.OutSine); + this.TransformBindableTo(tickPlaybackRate, tick_debounce_rate_end, RollingDuration, Easing.OutSine); + sampleTick.VolumeTo(tick_volume_start).Then().VolumeTo(tick_volume_end, RollingDuration, Easing.OutSine); } } } From d451bc8fda5784c5574f6fe8e68f66f16d16ee49 Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Fri, 22 Jul 2022 22:24:50 -0500 Subject: [PATCH 081/124] Revert commits This reverts commit 28586c704dbf6e5a2b1bc43e8f93c13e3b8f72e5. This reverts commit d9d35bb847a8f424c1379763019dbb0dc05c52a2. --- .../BeatmapSet/BeatmapSetHeaderContent.cs | 2 +- osu.Game/Overlays/BeatmapSet/Details.cs | 4 ++-- .../Screens/Select/Details/AdvancedStats.cs | 19 +++++++------------ 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index 68f5f2be6e..c9e97d5f2f 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -222,7 +222,7 @@ namespace osu.Game.Overlays.BeatmapSet TextSize = 14, TextPadding = new MarginPadding { Horizontal = 35, Vertical = 10 } }, - Details = new Details(false), + Details = new Details(), }, }, } diff --git a/osu.Game/Overlays/BeatmapSet/Details.cs b/osu.Game/Overlays/BeatmapSet/Details.cs index 092646f0ca..6db54db811 100644 --- a/osu.Game/Overlays/BeatmapSet/Details.cs +++ b/osu.Game/Overlays/BeatmapSet/Details.cs @@ -59,7 +59,7 @@ namespace osu.Game.Overlays.BeatmapSet ratingBox.Alpha = BeatmapSet?.Status > 0 ? 1 : 0; } - public Details(bool updateWithModSelection = true) + public Details() { Width = BeatmapSetOverlay.RIGHT_WIDTH; AutoSizeAxes = Axes.Y; @@ -82,7 +82,7 @@ namespace osu.Game.Overlays.BeatmapSet }, new DetailBox { - Child = advanced = new AdvancedStats(updateWithModSelection) + Child = advanced = new AdvancedStats { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 80e9989c47..693f182065 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Select.Details } } - public AdvancedStats(bool updateWithModSelection = true) + public AdvancedStats() { Child = new FillFlowContainer { @@ -65,11 +65,11 @@ namespace osu.Game.Screens.Select.Details AutoSizeAxes = Axes.Y, Children = new[] { - FirstValue = new StatisticRow(updateWithModSelection), // circle size/key amount - HpDrain = new StatisticRow(updateWithModSelection) { Title = BeatmapsetsStrings.ShowStatsDrain }, - Accuracy = new StatisticRow(updateWithModSelection) { Title = BeatmapsetsStrings.ShowStatsAccuracy }, - ApproachRate = new StatisticRow(updateWithModSelection) { Title = BeatmapsetsStrings.ShowStatsAr }, - starDifficulty = new StatisticRow(updateWithModSelection, 10, true) { Title = BeatmapsetsStrings.ShowStatsStars }, + FirstValue = new StatisticRow(), // circle size/key amount + HpDrain = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsDrain }, + Accuracy = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsAccuracy }, + ApproachRate = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsAr }, + starDifficulty = new StatisticRow(10, true) { Title = BeatmapsetsStrings.ShowStatsStars }, }, }; } @@ -183,7 +183,6 @@ namespace osu.Game.Screens.Select.Details private readonly OsuSpriteText name, valueText; private readonly Bar bar; public readonly Bar ModBar; - private readonly bool showModdedValue; [Resolved] private OsuColour colours { get; set; } @@ -204,9 +203,6 @@ namespace osu.Game.Screens.Select.Details if (value == this.value) return; - if (!showModdedValue) - value.adjustedValue = null; - this.value = value; bar.Length = value.baseValue / maxValue; @@ -229,14 +225,13 @@ namespace osu.Game.Screens.Select.Details set => bar.AccentColour = value; } - public StatisticRow(bool showModdedValue, float maxValue = 10, bool forceDecimalPlaces = false) + public StatisticRow(float maxValue = 10, bool forceDecimalPlaces = false) { this.maxValue = maxValue; this.forceDecimalPlaces = forceDecimalPlaces; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Padding = new MarginPadding { Vertical = 2.5f }; - this.showModdedValue = showModdedValue; Children = new Drawable[] { From 06462c13dd7023441ad9e2288673417c1e349c31 Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Fri, 22 Jul 2022 23:15:24 -0500 Subject: [PATCH 082/124] Overwrite IBindable> cache in BeatmapSetOverlay Implement fix as suggested --- osu.Game/Overlays/BeatmapSetOverlay.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 823d0023cf..a50a604b92 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -3,7 +3,10 @@ #nullable disable +using System; +using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -12,6 +15,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Overlays.Comments; +using osu.Game.Rulesets.Mods; using osuTK; using osuTK.Graphics; @@ -25,6 +29,10 @@ namespace osu.Game.Overlays private readonly Bindable beatmapSet = new Bindable(); + [Cached] + [Cached(typeof(IBindable>))] + protected readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); + public BeatmapSetOverlay() : base(OverlayColourScheme.Blue) { From 661c79baf691c59bc0af3501dd3fe28d62a04028 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 23 Jul 2022 10:15:52 +0300 Subject: [PATCH 083/124] Add explanatory comment --- osu.Game/Overlays/BeatmapSetOverlay.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index a50a604b92..207dc91ca5 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -16,6 +16,7 @@ using osu.Game.Overlays.BeatmapSet; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Overlays.Comments; using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Select.Details; using osuTK; using osuTK.Graphics; @@ -29,6 +30,10 @@ namespace osu.Game.Overlays private readonly Bindable beatmapSet = new Bindable(); + /// + /// Isolates the beatmap set overlay from the game-wide selected mods bindable + /// to avoid affecting the beatmap details section (i.e. ). + /// [Cached] [Cached(typeof(IBindable>))] protected readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); From c937c0548eb1782041d52445896a098b8d0d300e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 23 Jul 2022 10:01:06 +0300 Subject: [PATCH 084/124] Add test coverage --- .../Online/TestSceneBeatmapSetOverlay.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index 416e8aebcc..bb4823fb1d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -16,6 +16,10 @@ using osu.Framework.Testing; using osu.Game.Beatmaps.Drawables; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet.Scores; +using osu.Game.Resources.Localisation.Web; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens.Select.Details; using APIUser = osu.Game.Online.API.Requests.Responses.APIUser; namespace osu.Game.Tests.Visual.Online @@ -34,6 +38,9 @@ namespace osu.Game.Tests.Visual.Online [Resolved] private IRulesetStore rulesets { get; set; } + [SetUp] + public void SetUp() => Schedule(() => SelectedMods.Value = Array.Empty()); + [Test] public void TestLoading() { @@ -205,6 +212,21 @@ namespace osu.Game.Tests.Visual.Online }); } + [Test] + public void TestSelectedModsDontAffectStatistics() + { + AddStep("show map", () => overlay.ShowBeatmapSet(getBeatmapSet())); + AddAssert("AR displayed as 0", () => overlay.ChildrenOfType().Single(s => s.Title == BeatmapsetsStrings.ShowStatsAr).Value == (0, null)); + AddStep("set AR10 diff adjust", () => SelectedMods.Value = new[] + { + new OsuModDifficultyAdjust + { + ApproachRate = { Value = 10 } + } + }); + AddAssert("AR still displayed as 0", () => overlay.ChildrenOfType().Single(s => s.Title == BeatmapsetsStrings.ShowStatsAr).Value == (0, null)); + } + [Test] public void TestHide() { From 840ad8fad2e8abe166d20a86a5ca4c5d7df7269c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 23 Jul 2022 11:10:59 +0300 Subject: [PATCH 085/124] Fix background beatmap processor resetting star ratings in tests --- osu.Game/Tests/Visual/OsuGameTestScene.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Tests/Visual/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs index b9f6183869..69a945db34 100644 --- a/osu.Game/Tests/Visual/OsuGameTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs @@ -171,6 +171,11 @@ namespace osu.Game.Tests.Visual API.Login("Rhythm Champion", "osu!"); Dependencies.Get().SetValue(Static.MutedAudioNotificationShownOnce, true); + + // set applied version to latest so that the BackgroundBeatmapProcessor doesn't consider + // beatmap star ratings as outdated and reset them throughout the test. + foreach (var ruleset in RulesetStore.AvailableRulesets) + ruleset.LastAppliedDifficultyVersion = ruleset.CreateInstance().CreateDifficultyCalculator(Beatmap.Default).Version; } protected override void Update() From f1791e79e3e6cb607867304a1b560937f5876ba2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Jul 2022 19:21:12 +0900 Subject: [PATCH 086/124] Add error logging for background processing failures --- osu.Game/BackgroundBeatmapProcessor.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs index 6ecd8ca5c1..14fdb2e1ef 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -127,8 +127,15 @@ namespace osu.Game if (set != null) { - Logger.Log($"Background processing {set} ({++i} / {beatmapSetIds.Count})"); - beatmapUpdater.Process(set); + try + { + Logger.Log($"Background processing {set} ({++i} / {beatmapSetIds.Count})"); + beatmapUpdater.Process(set); + } + catch (Exception e) + { + Logger.Log($"Background processing failed on {set}: {e}"); + } } }); } From 7c477e6f2298ffc2fbd8d77efb6badbfd348d068 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 24 Jul 2022 04:19:26 +0300 Subject: [PATCH 087/124] Fix beatmap overlay leaderboard not handling null PP scores properly --- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index c46c5cde43..08f750827b 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions; @@ -178,10 +177,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } if (showPerformancePoints) - { - Debug.Assert(score.PP != null); - content.Add(new StatisticText(score.PP.Value, format: @"N0")); - } + content.Add(new StatisticText(score.PP, format: @"N0")); content.Add(new ScoreboardTime(score.Date, text_size) { @@ -222,19 +218,19 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private class StatisticText : OsuSpriteText, IHasTooltip { - private readonly double count; + private readonly double? count; private readonly double? maxCount; private readonly bool showTooltip; public LocalisableString TooltipText => maxCount == null || !showTooltip ? string.Empty : $"{count}/{maxCount}"; - public StatisticText(double count, double? maxCount = null, string format = null, bool showTooltip = true) + public StatisticText(double? count, double? maxCount = null, string format = null, bool showTooltip = true) { this.count = count; this.maxCount = maxCount; this.showTooltip = showTooltip; - Text = count.ToLocalisableString(format); + Text = count?.ToLocalisableString(format) ?? default; Font = OsuFont.GetFont(size: text_size); } From 0c16ef3e2ed0215ac03c51a1fd7743273f695fbb Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 24 Jul 2022 08:33:35 +0300 Subject: [PATCH 088/124] Add failing test case --- .../TestSceneLabelledSliderBar.cs | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledSliderBar.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledSliderBar.cs index 8ccfe2ee9c..e5f3aea2f7 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledSliderBar.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledSliderBar.cs @@ -8,8 +8,10 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; @@ -17,11 +19,26 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestSceneLabelledSliderBar : OsuTestScene { - [TestCase(false)] - [TestCase(true)] - public void TestSliderBar(bool hasDescription) => createSliderBar(hasDescription); + [Test] + public void TestBasic() => createSliderBar(); - private void createSliderBar(bool hasDescription = false) + [Test] + public void TestDescription() + { + createSliderBar(); + AddStep("set description", () => this.ChildrenOfType>().ForEach(l => l.Description = "this text describes the component")); + } + + [Test] + public void TestSize() + { + createSliderBar(); + AddStep("set zero width", () => this.ChildrenOfType>().ForEach(l => l.ResizeWidthTo(0, 200, Easing.OutQuint))); + AddStep("set negative width", () => this.ChildrenOfType>().ForEach(l => l.ResizeWidthTo(-1, 200, Easing.OutQuint))); + AddStep("revert back", () => this.ChildrenOfType>().ForEach(l => l.ResizeWidthTo(1, 200, Easing.OutQuint))); + } + + private void createSliderBar() { AddStep("create component", () => { @@ -38,6 +55,8 @@ namespace osu.Game.Tests.Visual.UserInterface { new LabelledSliderBar { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Current = new BindableDouble(5) { MinValue = 0, @@ -45,7 +64,6 @@ namespace osu.Game.Tests.Visual.UserInterface Precision = 1, }, Label = "a sample component", - Description = hasDescription ? "this text describes the component" : string.Empty, }, }, }; @@ -54,10 +72,14 @@ namespace osu.Game.Tests.Visual.UserInterface { flow.Add(new OverlayColourContainer(colour) { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Child = new LabelledSliderBar { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Current = new BindableDouble(5) { MinValue = 0, @@ -65,7 +87,6 @@ namespace osu.Game.Tests.Visual.UserInterface Precision = 1, }, Label = "a sample component", - Description = hasDescription ? "this text describes the component" : string.Empty, } }); } From 4332e6cae90980396482edbe6309259bc6096c42 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 24 Jul 2022 08:34:05 +0300 Subject: [PATCH 089/124] Fix `OsuSliderBar` throwing on negative draw width --- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index c48627bd21..2a8b41fd20 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -228,10 +228,8 @@ namespace osu.Game.Graphics.UserInterface protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); - LeftBox.Scale = new Vector2(Math.Clamp( - RangePadding + Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, DrawWidth), 1); - RightBox.Scale = new Vector2(Math.Clamp( - DrawWidth - Nub.DrawPosition.X - RangePadding - Nub.DrawWidth / 2, 0, DrawWidth), 1); + LeftBox.Scale = new Vector2(Math.Clamp(RangePadding + Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, Math.Max(0, DrawWidth)), 1); + RightBox.Scale = new Vector2(Math.Clamp(DrawWidth - Nub.DrawPosition.X - RangePadding - Nub.DrawWidth / 2, 0, Math.Max(0, DrawWidth)), 1); } protected override void UpdateValue(float value) From b2f893411766567ad774de4e6327db1688d861cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Jul 2022 20:01:22 +0200 Subject: [PATCH 090/124] Extract base mod select column presentation logic --- osu.Game/Overlays/Mods/ModColumn.cs | 162 ++------------------ osu.Game/Overlays/Mods/ModSelectColumn.cs | 177 ++++++++++++++++++++++ 2 files changed, 187 insertions(+), 152 deletions(-) create mode 100644 osu.Game/Overlays/Mods/ModSelectColumn.cs diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index beb4856477..1c40c8c6e5 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -12,14 +12,10 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Configuration; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Overlays.Mods.Input; @@ -29,10 +25,8 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Mods { - public class ModColumn : CompositeDrawable + public class ModColumn : ModSelectColumn { - public readonly Container TopLevelContent; - public readonly ModType ModType; private IReadOnlyList availableMods = Array.Empty(); @@ -62,149 +56,29 @@ namespace osu.Game.Overlays.Mods } } - /// - /// Determines whether this column should accept user input. - /// - public Bindable Active = new BindableBool(true); - - protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && Active.Value; - protected virtual ModPanel CreateModPanel(ModState mod) => new ModPanel(mod); private readonly bool allowIncompatibleSelection; - private readonly TextFlowContainer headerText; - private readonly Box headerBackground; - private readonly Container contentContainer; - private readonly Box contentBackground; - private readonly FillFlowContainer panelFlow; private readonly ToggleAllCheckbox? toggleAllCheckbox; - private Colour4 accentColour; - private Bindable hotkeyStyle = null!; private IModHotkeyHandler hotkeyHandler = null!; private Task? latestLoadTask; internal bool ItemsLoaded => latestLoadTask == null; - private const float header_height = 42; - public ModColumn(ModType modType, bool allowIncompatibleSelection) { ModType = modType; this.allowIncompatibleSelection = allowIncompatibleSelection; - Width = 320; - RelativeSizeAxes = Axes.Y; - Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0); - - Container controlContainer; - InternalChildren = new Drawable[] - { - TopLevelContent = new Container - { - RelativeSizeAxes = Axes.Both, - CornerRadius = ModSelectPanel.CORNER_RADIUS, - Masking = true, - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.X, - Height = header_height + ModSelectPanel.CORNER_RADIUS, - Children = new Drawable[] - { - headerBackground = new Box - { - RelativeSizeAxes = Axes.X, - Height = header_height + ModSelectPanel.CORNER_RADIUS - }, - headerText = new OsuTextFlowContainer(t => - { - t.Font = OsuFont.TorusAlternate.With(size: 17); - t.Shadow = false; - t.Colour = Colour4.Black; - }) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), - Padding = new MarginPadding - { - Horizontal = 17, - Bottom = ModSelectPanel.CORNER_RADIUS - } - } - } - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = header_height }, - Child = contentContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = ModSelectPanel.CORNER_RADIUS, - BorderThickness = 3, - Children = new Drawable[] - { - contentBackground = new Box - { - RelativeSizeAxes = Axes.Both - }, - new GridContainer - { - RelativeSizeAxes = Axes.Both, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension() - }, - Content = new[] - { - new Drawable[] - { - controlContainer = new Container - { - RelativeSizeAxes = Axes.X, - Padding = new MarginPadding { Horizontal = 14 } - } - }, - new Drawable[] - { - new OsuScrollContainer(Direction.Vertical) - { - RelativeSizeAxes = Axes.Both, - ClampExtension = 100, - ScrollbarOverlapsContent = false, - Child = panelFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0, 7), - Padding = new MarginPadding(7) - } - } - } - } - } - } - } - } - } - } - }; - - createHeaderText(); + HeaderText = ModType.Humanize(LetterCasing.Title); if (allowIncompatibleSelection) { - controlContainer.Height = 35; - controlContainer.Add(toggleAllCheckbox = new ToggleAllCheckbox(this) + ControlContainer.Height = 35; + ControlContainer.Add(toggleAllCheckbox = new ToggleAllCheckbox(this) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -212,7 +86,7 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.X, Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0) }); - panelFlow.Padding = new MarginPadding + ItemsFlow.Padding = new MarginPadding { Top = 0, Bottom = 7, @@ -221,33 +95,17 @@ namespace osu.Game.Overlays.Mods } } - private void createHeaderText() - { - IEnumerable headerTextWords = ModType.Humanize(LetterCasing.Title).Split(' '); - - if (headerTextWords.Count() > 1) - { - headerText.AddText($"{headerTextWords.First()} ", t => t.Font = t.Font.With(weight: FontWeight.SemiBold)); - headerTextWords = headerTextWords.Skip(1); - } - - headerText.AddText(string.Join(' ', headerTextWords)); - } - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider, OsuColour colours, OsuConfigManager configManager) + private void load(OsuColour colours, OsuConfigManager configManager) { - headerBackground.Colour = accentColour = colours.ForModType(ModType); + AccentColour = colours.ForModType(ModType); if (toggleAllCheckbox != null) { - toggleAllCheckbox.AccentColour = accentColour; - toggleAllCheckbox.AccentHoverColour = accentColour.Lighten(0.3f); + toggleAllCheckbox.AccentColour = AccentColour; + toggleAllCheckbox.AccentHoverColour = AccentColour.Lighten(0.3f); } - contentContainer.BorderColour = ColourInfo.GradientVertical(colourProvider.Background4, colourProvider.Background3); - contentBackground.Colour = colourProvider.Background4; - hotkeyStyle = configManager.GetBindable(OsuSetting.ModSelectHotkeyStyle); } @@ -278,7 +136,7 @@ namespace osu.Game.Overlays.Mods latestLoadTask = loadTask = LoadComponentsAsync(panels, loaded => { - panelFlow.ChildrenEnumerable = loaded; + ItemsFlow.ChildrenEnumerable = loaded; updateState(); }, (cancellationTokenSource = new CancellationTokenSource()).Token); loadTask.ContinueWith(_ => diff --git a/osu.Game/Overlays/Mods/ModSelectColumn.cs b/osu.Game/Overlays/Mods/ModSelectColumn.cs new file mode 100644 index 0000000000..d211f9eb5e --- /dev/null +++ b/osu.Game/Overlays/Mods/ModSelectColumn.cs @@ -0,0 +1,177 @@ +// 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.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Mods +{ + public abstract class ModSelectColumn : CompositeDrawable, IHasAccentColour + { + public readonly Container TopLevelContent; + + public LocalisableString HeaderText + { + set => createHeaderText(value); + } + + public Color4 AccentColour + { + get => headerBackground.Colour; + set => headerBackground.Colour = value; + } + + /// + /// Determines whether this column should accept user input. + /// + public Bindable Active = new BindableBool(true); + + protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && Active.Value; + + protected readonly Container ControlContainer; + protected readonly FillFlowContainer ItemsFlow; + + private readonly TextFlowContainer headerText; + private readonly Box headerBackground; + private readonly Container contentContainer; + private readonly Box contentBackground; + + private const float header_height = 42; + + protected ModSelectColumn() + { + Width = 320; + RelativeSizeAxes = Axes.Y; + Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0); + + InternalChildren = new Drawable[] + { + TopLevelContent = new Container + { + RelativeSizeAxes = Axes.Both, + CornerRadius = ModSelectPanel.CORNER_RADIUS, + Masking = true, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + Height = header_height + ModSelectPanel.CORNER_RADIUS, + Children = new Drawable[] + { + headerBackground = new Box + { + RelativeSizeAxes = Axes.X, + Height = header_height + ModSelectPanel.CORNER_RADIUS + }, + headerText = new OsuTextFlowContainer(t => + { + t.Font = OsuFont.TorusAlternate.With(size: 17); + t.Shadow = false; + t.Colour = Colour4.Black; + }) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Padding = new MarginPadding + { + Horizontal = 17, + Bottom = ModSelectPanel.CORNER_RADIUS + } + } + } + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = header_height }, + Child = contentContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = ModSelectPanel.CORNER_RADIUS, + BorderThickness = 3, + Children = new Drawable[] + { + contentBackground = new Box + { + RelativeSizeAxes = Axes.Both + }, + new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension() + }, + Content = new[] + { + new Drawable[] + { + ControlContainer = new Container + { + RelativeSizeAxes = Axes.X, + Padding = new MarginPadding { Horizontal = 14 } + } + }, + new Drawable[] + { + new OsuScrollContainer(Direction.Vertical) + { + RelativeSizeAxes = Axes.Both, + ClampExtension = 100, + ScrollbarOverlapsContent = false, + Child = ItemsFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0, 7), + Padding = new MarginPadding(7) + } + } + } + } + } + } + } + } + } + } + }; + } + + private void createHeaderText(LocalisableString text) + { + headerText.Clear(); + + int wordIndex = 0; + + headerText.AddText(text, t => + { + if (wordIndex == 0) + t.Font = t.Font.With(weight: FontWeight.SemiBold); + wordIndex += 1; + }); + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + contentContainer.BorderColour = ColourInfo.GradientVertical(colourProvider.Background4, colourProvider.Background3); + contentBackground.Colour = colourProvider.Background4; + } + } +} From 6a67d76d7ca085c7f4dec1336fe7be5a66e2ebac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Jul 2022 21:05:02 +0200 Subject: [PATCH 091/124] Add basic mod preset column implementation --- .../UserInterface/TestSceneModPresetColumn.cs | 77 +++++++++++++++++++ .../Localisation/ModPresetColumnStrings.cs | 19 +++++ osu.Game/Overlays/Mods/ModPresetColumn.cs | 77 +++++++++++++++++++ 3 files changed, 173 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs create mode 100644 osu.Game/Localisation/ModPresetColumnStrings.cs create mode 100644 osu.Game/Overlays/Mods/ModPresetColumn.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs new file mode 100644 index 0000000000..f6209e1b42 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs @@ -0,0 +1,77 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; +using osu.Game.Overlays.Mods; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneModPresetColumn : OsuTestScene + { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + + [Test] + public void TestBasicAppearance() + { + ModPresetColumn modPresetColumn = null!; + + AddStep("create content", () => Child = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(30), + Child = modPresetColumn = new ModPresetColumn + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Presets = createTestPresets().ToArray() + } + }); + AddStep("change presets", () => modPresetColumn.Presets = createTestPresets().Skip(1).ToArray()); + } + + private static IEnumerable createTestPresets() => new[] + { + new ModPreset + { + Name = "First preset", + Description = "Please ignore", + Mods = new Mod[] + { + new OsuModHardRock(), + new OsuModDoubleTime() + } + }, + new ModPreset + { + Name = "AR0", + Description = "For good readers", + Mods = new Mod[] + { + new OsuModDifficultyAdjust + { + ApproachRate = { Value = 0 } + } + } + }, + new ModPreset + { + Name = "This preset is going to have an extraordinarily long name", + Description = "This is done so that the capability to truncate overlong texts may be demonstrated", + Mods = new Mod[] + { + new OsuModFlashlight(), + new OsuModSpinIn() + } + } + }; + } +} diff --git a/osu.Game/Localisation/ModPresetColumnStrings.cs b/osu.Game/Localisation/ModPresetColumnStrings.cs new file mode 100644 index 0000000000..b19a70a248 --- /dev/null +++ b/osu.Game/Localisation/ModPresetColumnStrings.cs @@ -0,0 +1,19 @@ +// 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.Localisation; + +namespace osu.Game.Localisation +{ + public static class ModPresetColumnStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.ModPresetColumn"; + + /// + /// "Personal Presets" + /// + public static LocalisableString PersonalPresets => new TranslatableString(getKey(@"personal_presets"), @"Personal Presets"); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/Mods/ModPresetColumn.cs b/osu.Game/Overlays/Mods/ModPresetColumn.cs new file mode 100644 index 0000000000..b32015e6ea --- /dev/null +++ b/osu.Game/Overlays/Mods/ModPresetColumn.cs @@ -0,0 +1,77 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Localisation; +using osu.Game.Rulesets.Mods; +using osuTK; + +namespace osu.Game.Overlays.Mods +{ + public class ModPresetColumn : ModSelectColumn + { + private IReadOnlyList presets = Array.Empty(); + + /// + /// Sets the collection of available mod presets. + /// + public IReadOnlyList Presets + { + get => presets; + set + { + presets = value; + + if (IsLoaded) + asyncLoadPanels(); + } + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.Orange1; + HeaderText = ModPresetColumnStrings.PersonalPresets; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + asyncLoadPanels(); + } + + private CancellationTokenSource? cancellationTokenSource; + + private Task? latestLoadTask; + internal bool ItemsLoaded => latestLoadTask == null; + + private void asyncLoadPanels() + { + cancellationTokenSource?.Cancel(); + + var panels = presets.Select(preset => new ModPresetPanel(preset) + { + Shear = Vector2.Zero + }); + + Task? loadTask; + + latestLoadTask = loadTask = LoadComponentsAsync(panels, loaded => + { + ItemsFlow.ChildrenEnumerable = loaded; + }, (cancellationTokenSource = new CancellationTokenSource()).Token); + loadTask.ContinueWith(_ => + { + if (loadTask == latestLoadTask) + latestLoadTask = null; + }); + } + } +} From 8af9cfbe40c446f8ce1bfd0761f6e68ce9bead0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 24 Jul 2022 23:29:18 +0200 Subject: [PATCH 092/124] Add readonly modifier --- osu.Game/Overlays/Mods/ModSelectColumn.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectColumn.cs b/osu.Game/Overlays/Mods/ModSelectColumn.cs index d211f9eb5e..0224631577 100644 --- a/osu.Game/Overlays/Mods/ModSelectColumn.cs +++ b/osu.Game/Overlays/Mods/ModSelectColumn.cs @@ -33,7 +33,7 @@ namespace osu.Game.Overlays.Mods /// /// Determines whether this column should accept user input. /// - public Bindable Active = new BindableBool(true); + public readonly Bindable Active = new BindableBool(true); protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && Active.Value; From 446485f804b4d7df948ea4e82ca23697d6ff2e80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 24 Jul 2022 23:30:52 +0200 Subject: [PATCH 093/124] Move localisation string to common location --- .../Localisation/ModPresetColumnStrings.cs | 19 ------------------- .../Localisation/ModSelectOverlayStrings.cs | 5 +++++ osu.Game/Overlays/Mods/ModPresetColumn.cs | 2 +- 3 files changed, 6 insertions(+), 20 deletions(-) delete mode 100644 osu.Game/Localisation/ModPresetColumnStrings.cs diff --git a/osu.Game/Localisation/ModPresetColumnStrings.cs b/osu.Game/Localisation/ModPresetColumnStrings.cs deleted file mode 100644 index b19a70a248..0000000000 --- a/osu.Game/Localisation/ModPresetColumnStrings.cs +++ /dev/null @@ -1,19 +0,0 @@ -// 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.Localisation; - -namespace osu.Game.Localisation -{ - public static class ModPresetColumnStrings - { - private const string prefix = @"osu.Game.Resources.Localisation.ModPresetColumn"; - - /// - /// "Personal Presets" - /// - public static LocalisableString PersonalPresets => new TranslatableString(getKey(@"personal_presets"), @"Personal Presets"); - - private static string getKey(string key) => $@"{prefix}:{key}"; - } -} \ No newline at end of file diff --git a/osu.Game/Localisation/ModSelectOverlayStrings.cs b/osu.Game/Localisation/ModSelectOverlayStrings.cs index e9af7147e3..3696b1f2cd 100644 --- a/osu.Game/Localisation/ModSelectOverlayStrings.cs +++ b/osu.Game/Localisation/ModSelectOverlayStrings.cs @@ -24,6 +24,11 @@ namespace osu.Game.Localisation /// public static LocalisableString ModCustomisation => new TranslatableString(getKey(@"mod_customisation"), @"Mod Customisation"); + /// + /// "Personal Presets" + /// + public static LocalisableString PersonalPresets => new TranslatableString(getKey(@"personal_presets"), @"Personal Presets"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Overlays/Mods/ModPresetColumn.cs b/osu.Game/Overlays/Mods/ModPresetColumn.cs index b32015e6ea..1eea8383f8 100644 --- a/osu.Game/Overlays/Mods/ModPresetColumn.cs +++ b/osu.Game/Overlays/Mods/ModPresetColumn.cs @@ -37,7 +37,7 @@ namespace osu.Game.Overlays.Mods private void load(OsuColour colours) { AccentColour = colours.Orange1; - HeaderText = ModPresetColumnStrings.PersonalPresets; + HeaderText = ModSelectOverlayStrings.PersonalPresets; } protected override void LoadComplete() From feef16b09b409b9446dd1b3dadea206b246691b7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 04:18:30 +0300 Subject: [PATCH 094/124] Add potentially failing test case --- .../Visual/Gameplay/TestSceneSpectatorHost.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs index c55e98c1a8..9ad8ac086c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Screens; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Spectator; @@ -43,6 +44,21 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("spectator client sent correct ruleset", () => spectatorClient.WatchedUserStates[dummy_user_id].RulesetID == Ruleset.Value.OnlineID); } + [Test] + public void TestRestart() + { + AddAssert("spectator client sees playing state", () => spectatorClient.WatchedUserStates[dummy_user_id].State == SpectatedUserState.Playing); + + AddStep("exit player", () => Player.Exit()); + AddStep("reload player", LoadPlayer); + AddUntilStep("wait for player load", () => Player.IsLoaded && Player.Alpha == 1); + + AddAssert("spectator client sees playing state", () => spectatorClient.WatchedUserStates[dummy_user_id].State == SpectatedUserState.Playing); + + AddWaitStep("wait", 5); + AddUntilStep("spectator client still sees playing state", () => spectatorClient.WatchedUserStates[dummy_user_id].State == SpectatedUserState.Playing); + } + public override void TearDownSteps() { base.TearDownSteps(); From f5a58876694efbb14e5c08be509abed2a6b62abe Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 04:21:52 +0300 Subject: [PATCH 095/124] Fix players potentially not displaying in spectator after restart --- osu.Game/Online/Spectator/SpectatorClient.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 68c8b57019..d12817d4d4 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -206,6 +206,11 @@ namespace osu.Game.Online.Spectator if (!IsPlaying) return; + // Disposal could be processed late, leading to EndPlaying potentially being called after a future BeginPlaying call. + // Account for this by ensuring the current score matches the score in the provided GameplayState. + if (currentScore != state.Score) + return; + if (pendingFrames.Count > 0) purgePendingFrames(); From e0266b0d81fabda2b946321e85fe09ca42c87613 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 04:34:42 +0300 Subject: [PATCH 096/124] Reword comment slightly --- osu.Game/Online/Spectator/SpectatorClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index d12817d4d4..b5e1c8a45f 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -206,8 +206,8 @@ namespace osu.Game.Online.Spectator if (!IsPlaying) return; - // Disposal could be processed late, leading to EndPlaying potentially being called after a future BeginPlaying call. - // Account for this by ensuring the current score matches the score in the provided GameplayState. + // Disposal can take some time, leading to EndPlaying potentially being called after a future play session. + // Account for this by ensuring the score of the current play matches the one in the provided state. if (currentScore != state.Score) return; From fa9daa68996e2ff9688458e36157689639dfdf0a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 05:21:27 +0300 Subject: [PATCH 097/124] Fix `TestSceneReplayRecorder` not using score provided by gameplay state --- .../Gameplay/TestSceneReplayRecorder.cs | 153 +++++++++--------- osu.Game/Screens/Play/GameplayState.cs | 1 + 2 files changed, 78 insertions(+), 76 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index 54b2e66f2f..b3401c916b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -3,10 +3,10 @@ #nullable disable +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -40,8 +40,7 @@ namespace osu.Game.Tests.Visual.Gameplay private TestReplayRecorder recorder; - [Cached] - private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset()); + private GameplayState gameplayState; [SetUpSteps] public void SetUpSteps() @@ -52,81 +51,15 @@ namespace osu.Game.Tests.Visual.Gameplay { replay = new Replay(); - Add(new GridContainer + gameplayState = TestGameplayState.Create(new OsuRuleset()); + gameplayState.Score.Replay = replay; + + Child = new DependencyProvidingContainer { RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] - { - recordingManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) - { - Recorder = recorder = new TestReplayRecorder(new Score - { - Replay = replay, - ScoreInfo = - { - BeatmapInfo = gameplayState.Beatmap.BeatmapInfo, - Ruleset = new OsuRuleset().RulesetInfo, - } - }) - { - ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), - }, - Child = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - Colour = Color4.Brown, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Text = "Recording", - Scale = new Vector2(3), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new TestInputConsumer() - } - }, - } - }, - new Drawable[] - { - playbackManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) - { - ReplayInputHandler = new TestFramedReplayInputHandler(replay) - { - GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos), - }, - Child = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - Colour = Color4.DarkBlue, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Text = "Playback", - Scale = new Vector2(3), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new TestInputConsumer() - } - }, - } - } - } - }); + CachedDependencies = new (Type, object)[] { (typeof(GameplayState), gameplayState) }, + Child = createContent(), + }; }); } @@ -203,6 +136,74 @@ namespace osu.Game.Tests.Visual.Gameplay recorder = null; } + private Drawable createContent() => new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + recordingManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + { + Recorder = recorder = new TestReplayRecorder(gameplayState.Score) + { + ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), + }, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Brown, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Text = "Recording", + Scale = new Vector2(3), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new TestInputConsumer() + } + }, + } + }, + new Drawable[] + { + playbackManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + { + ReplayInputHandler = new TestFramedReplayInputHandler(replay) + { + GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos), + }, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = Color4.DarkBlue, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Text = "Playback", + Scale = new Vector2(3), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new TestInputConsumer() + } + }, + } + } + } + }; + public class TestFramedReplayInputHandler : FramedReplayInputHandler { public TestFramedReplayInputHandler(Replay replay) diff --git a/osu.Game/Screens/Play/GameplayState.cs b/osu.Game/Screens/Play/GameplayState.cs index 9fb62106f3..c2162d4df2 100644 --- a/osu.Game/Screens/Play/GameplayState.cs +++ b/osu.Game/Screens/Play/GameplayState.cs @@ -70,6 +70,7 @@ namespace osu.Game.Screens.Play { ScoreInfo = { + BeatmapInfo = beatmap.BeatmapInfo, Ruleset = ruleset.RulesetInfo } }; From 3d97b748131d2c581ce9e4bb1954e20c3a74cea7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 25 Jul 2022 13:03:47 +0900 Subject: [PATCH 098/124] Log beatmap difficulty retrieval failures during score calculation --- osu.Game/Scoring/ScoreManager.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 9aed8904e6..7cfc55580b 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Extensions; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Beatmaps; @@ -172,6 +173,10 @@ namespace osu.Game.Scoring // We can compute the max combo locally after the async beatmap difficulty computation. var difficulty = await difficultyCache.GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false); + + if (difficulty == null) + Logger.Log($"Couldn't get beatmap difficulty for beatmap {score.BeatmapInfo.OnlineID}"); + return difficulty?.MaxCombo; } From 0226b358eec2d3dd0fba037d178bb21011d1b8bf Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 25 Jul 2022 13:20:33 +0900 Subject: [PATCH 099/124] Disable timeline test for now --- osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs index 6c5cca1874..09d753ba41 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; namespace osu.Game.Tests.Visual.Editing { + [Ignore("Timeline initialisation is kinda broken.")] // Initial work to rectify this was done in https://github.com/ppy/osu/pull/19297, but needs more massaging to work. public class TestSceneTimelineZoom : TimelineTestScene { public override Drawable CreateTestComponent() => Empty(); From 54eb2b98a96b1d412cc790ba6a00516f01ca5dca Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 07:30:52 +0300 Subject: [PATCH 100/124] Display exclamation triangle on scores with unprocessed PP --- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 08f750827b..e9cb02406e 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -23,6 +23,7 @@ using osuTK.Graphics; using osu.Framework.Localisation; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.BeatmapSet.Scores @@ -177,7 +178,12 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } if (showPerformancePoints) - content.Add(new StatisticText(score.PP, format: @"N0")); + { + if (score.PP != null) + content.Add(new StatisticText(score.PP, format: @"N0")); + else + content.Add(new ProcessingPPIcon()); + } content.Add(new ScoreboardTime(score.Date, text_size) { @@ -241,5 +247,18 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Colour = colours.GreenLight; } } + + private class ProcessingPPIcon : SpriteIcon, IHasTooltip + { + public LocalisableString TooltipText => ScoresStrings.StatusProcessing; + + public ProcessingPPIcon() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Size = new Vector2(text_size); + Icon = FontAwesome.Solid.ExclamationTriangle; + } + } } } From 6c95c49da32924ac0321a46c733454a11c3746ee Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 07:31:52 +0300 Subject: [PATCH 101/124] Mark test score with null PP for visual testing --- osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 864b2b6878..19acc8d7c0 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -210,7 +210,7 @@ namespace osu.Game.Tests.Visual.Online new APIMod { Acronym = new OsuModHidden().Acronym }, }, Rank = ScoreRank.B, - PP = 180, + PP = null, MaxCombo = 1234, TotalScore = 12345678, Accuracy = 0.9854, From 91d1c9686c0d970d131dc743a7d6922c55ff0495 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 09:07:51 +0300 Subject: [PATCH 102/124] Separate unprocessed PP placeholder to own class --- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 17 ++---------- ...UnprocessedPerformancePointsPlaceholder.cs | 27 +++++++++++++++++++ 2 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 osu.Game/Scoring/Drawables/UnprocessedPerformancePointsPlaceholder.cs diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index e9cb02406e..5463c7a50f 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -23,8 +23,8 @@ using osuTK.Graphics; using osu.Framework.Localisation; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Sprites; using osu.Game.Resources.Localisation.Web; +using osu.Game.Scoring.Drawables; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -182,7 +182,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (score.PP != null) content.Add(new StatisticText(score.PP, format: @"N0")); else - content.Add(new ProcessingPPIcon()); + content.Add(new UnprocessedPerformancePointsPlaceholder { Size = new Vector2(text_size) }); } content.Add(new ScoreboardTime(score.Date, text_size) @@ -247,18 +247,5 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Colour = colours.GreenLight; } } - - private class ProcessingPPIcon : SpriteIcon, IHasTooltip - { - public LocalisableString TooltipText => ScoresStrings.StatusProcessing; - - public ProcessingPPIcon() - { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - Size = new Vector2(text_size); - Icon = FontAwesome.Solid.ExclamationTriangle; - } - } } } diff --git a/osu.Game/Scoring/Drawables/UnprocessedPerformancePointsPlaceholder.cs b/osu.Game/Scoring/Drawables/UnprocessedPerformancePointsPlaceholder.cs new file mode 100644 index 0000000000..6087ca9eb9 --- /dev/null +++ b/osu.Game/Scoring/Drawables/UnprocessedPerformancePointsPlaceholder.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable +using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Scoring.Drawables +{ + /// + /// A placeholder used in PP columns for scores with unprocessed PP value. + /// + public class UnprocessedPerformancePointsPlaceholder : SpriteIcon, IHasTooltip + { + public LocalisableString TooltipText => ScoresStrings.StatusProcessing; + + public UnprocessedPerformancePointsPlaceholder() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Icon = FontAwesome.Solid.ExclamationTriangle; + } + } +} From f54cee027065eb30ff0c00e1957e075f0be59e45 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 09:10:35 +0300 Subject: [PATCH 103/124] Display placeholder for leaderboard top scores --- .../Scores/TopScoreStatisticsSection.cs | 57 +++++++++++++------ 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 3b5ab811ae..653bfd6d2c 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -12,14 +12,17 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using osu.Game.Scoring; +using osu.Game.Scoring.Drawables; using osuTK; namespace osu.Game.Overlays.BeatmapSet.Scores @@ -121,7 +124,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores maxComboColumn.Text = value.MaxCombo.ToLocalisableString(@"0\x"); ppColumn.Alpha = value.BeatmapInfo.Status.GrantsPerformancePoints() ? 1 : 0; - ppColumn.Text = value.PP?.ToLocalisableString(@"N0") ?? default; + + if (value.PP is double pp) + ppColumn.Text = pp.ToLocalisableString(@"N0"); + else + ppColumn.Drawable = new UnprocessedPerformancePointsPlaceholder { Size = new Vector2(smallFont.Size) }; statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(createStatisticsColumn); modsColumn.Mods = value.Mods; @@ -197,30 +204,48 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } } - private class TextColumn : InfoColumn + private class TextColumn : InfoColumn, IHasCurrentValue { - private readonly SpriteText text; - - public TextColumn(LocalisableString title, FontUsage font, float? minWidth = null) - : this(title, new OsuSpriteText { Font = font }, minWidth) - { - } - - private TextColumn(LocalisableString title, SpriteText text, float? minWidth = null) - : base(title, text, minWidth) - { - this.text = text; - } + private readonly OsuTextFlowContainer text; public LocalisableString Text { set => text.Text = value; } + public Drawable Drawable + { + set + { + text.Clear(); + text.AddArbitraryDrawable(value); + } + } + + private Bindable current; + public Bindable Current { - get => text.Current; - set => text.Current = value; + get => current; + set + { + text.Clear(); + text.AddText(value.Value, t => t.Current = current = value); + } + } + + public TextColumn(LocalisableString title, FontUsage font, float? minWidth = null) + : this(title, new OsuTextFlowContainer(t => t.Font = font) + { + AutoSizeAxes = Axes.Both + }, minWidth) + { + } + + private TextColumn(LocalisableString title, OsuTextFlowContainer text, float? minWidth = null) + : base(title, text, minWidth) + { + this.text = text; } } From bbbc0a863ff9b5f33b6720423569f58b0a6fc85d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jul 2022 15:21:24 +0900 Subject: [PATCH 104/124] Add test coverage of `WorkingBeatmap` retrieval from `BeatmapManager` --- .../Beatmaps/WorkingBeatmapManagerTest.cs | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs diff --git a/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs b/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs new file mode 100644 index 0000000000..0348e47d4a --- /dev/null +++ b/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs @@ -0,0 +1,100 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Extensions; +using osu.Framework.Platform; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Rulesets; +using osu.Game.Tests.Resources; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Beatmaps +{ + [HeadlessTest] + public class WorkingBeatmapManagerTest : OsuTestScene + { + private BeatmapManager beatmaps = null!; + + private BeatmapSetInfo importedSet = null!; + + [BackgroundDependencyLoader] + private void load(GameHost host, AudioManager audio, RulesetStore rulesets) + { + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + } + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("import beatmap", () => + { + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); + importedSet = beatmaps.GetAllUsableBeatmapSets().First(); + }); + } + + [Test] + public void TestGetWorkingBeatmap() => AddStep("run test", () => + { + Assert.That(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()), Is.Not.Null); + }); + + [Test] + public void TestCachedRetrievalNoFiles() => AddStep("run test", () => + { + var beatmap = importedSet.Beatmaps.First(); + + Assert.That(beatmap.BeatmapSet?.Files, Is.Empty); + + var first = beatmaps.GetWorkingBeatmap(beatmap); + var second = beatmaps.GetWorkingBeatmap(beatmap); + + Assert.That(first, Is.SameAs(second)); + Assert.That(first.BeatmapInfo.BeatmapSet?.Files, Has.Count.GreaterThan(0)); + }); + + [Test] + public void TestCachedRetrievalWithFiles() => AddStep("run test", () => + { + var beatmap = Realm.Run(r => r.Find(importedSet.Beatmaps.First().ID).Detach()); + + Assert.That(beatmap.BeatmapSet?.Files, Has.Count.GreaterThan(0)); + + var first = beatmaps.GetWorkingBeatmap(beatmap); + var second = beatmaps.GetWorkingBeatmap(beatmap); + + Assert.That(first, Is.SameAs(second)); + Assert.That(first.BeatmapInfo.BeatmapSet?.Files, Has.Count.GreaterThan(0)); + }); + + [Test] + public void TestForcedRefetchRetrievalNoFiles() => AddStep("run test", () => + { + var beatmap = importedSet.Beatmaps.First(); + + Assert.That(beatmap.BeatmapSet?.Files, Is.Empty); + + var first = beatmaps.GetWorkingBeatmap(beatmap); + var second = beatmaps.GetWorkingBeatmap(beatmap, true); + Assert.That(first, Is.Not.SameAs(second)); + }); + + [Test] + public void TestForcedRefetchRetrievalWithFiles() => AddStep("run test", () => + { + var beatmap = Realm.Run(r => r.Find(importedSet.Beatmaps.First().ID).Detach()); + + Assert.That(beatmap.BeatmapSet?.Files, Has.Count.GreaterThan(0)); + + var first = beatmaps.GetWorkingBeatmap(beatmap); + var second = beatmaps.GetWorkingBeatmap(beatmap, true); + Assert.That(first, Is.Not.SameAs(second)); + }); + } +} From 2ec90e37bb9e005dea4f99691758793a549ecd94 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jul 2022 14:59:11 +0900 Subject: [PATCH 105/124] Fix calls to `GetWorkingBeatmap` invalidating cache too often With recent changes, the pathway between refetching (on request) and refetching (on requirement due to unpopulated files) was combined. Unfortunately this pathway also added a forced invalidation, which should not have been applied to the second case. Closes https://github.com/ppy/osu/issues/19365. --- osu.Game/Beatmaps/BeatmapManager.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 30456afd2f..7717c9cc87 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -439,12 +439,15 @@ namespace osu.Game.Beatmaps { if (beatmapInfo != null) { - // Detached sets don't come with files. - // If we seem to be missing files, now is a good time to re-fetch. - if (refetch || beatmapInfo.IsManaged || beatmapInfo.BeatmapSet?.Files.Count == 0) - { + if (refetch) workingBeatmapCache.Invalidate(beatmapInfo); + // Detached beatmapsets don't come with files as an optimisation (see `RealmObjectExtensions.beatmap_set_mapper`). + // If we seem to be missing files, now is a good time to re-fetch. + bool missingFiles = beatmapInfo.BeatmapSet?.Files.Count == 0; + + if (refetch || beatmapInfo.IsManaged || missingFiles) + { Guid id = beatmapInfo.ID; beatmapInfo = Realm.Run(r => r.Find(id)?.Detach()) ?? beatmapInfo; } From e402e919ab4b2049e56338bdae7bff5a41348d82 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 09:28:54 +0300 Subject: [PATCH 106/124] Display placeholder for user profile scores --- .../Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs | 4 ++++ .../Profile/Sections/Ranks/DrawableProfileWeightedScore.cs | 5 ++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index 5d8f8c8326..b8446c153d 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -19,6 +19,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; using osu.Game.Rulesets; using osu.Game.Rulesets.UI; +using osu.Game.Scoring.Drawables; using osu.Game.Utils; using osuTK; @@ -246,6 +247,9 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks }; } + if (Score.Beatmap?.Status.GrantsPerformancePoints() == true) + return new UnprocessedPerformancePointsPlaceholder { Size = new Vector2(16), Colour = colourProvider.Highlight1 }; + return new OsuSpriteText { Font = OsuFont.GetFont(weight: FontWeight.Bold), diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs index 94d95dc27e..8c46f10ba2 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs @@ -42,12 +42,11 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks CreateDrawableAccuracy(), new Container { - AutoSizeAxes = Axes.Y, - Width = 50, + Size = new Vector2(50, 14), Child = new OsuSpriteText { Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true), - Text = $"{Score.PP * weight:0}pp", + Text = Score.PP.HasValue ? $"{Score.PP * weight:0}pp" : string.Empty, }, } } From 6bdd1f43a294082ac7a586a05447c095b91b72ed Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 09:28:59 +0300 Subject: [PATCH 107/124] Add visual test coverage --- .../Visual/Online/TestSceneScoresContainer.cs | 15 ++++++++++++++- .../Online/TestSceneUserProfileScores.cs | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 19acc8d7c0..cfa9f77634 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -141,6 +141,19 @@ namespace osu.Game.Tests.Visual.Online AddUntilStep("best score not displayed", () => scoresContainer.ChildrenOfType().Count() == 1); } + [Test] + public void TestUnprocessedPP() + { + AddStep("Load scores with unprocessed PP", () => + { + var allScores = createScores(); + allScores.Scores[0].PP = null; + allScores.UserScore = createUserBest(); + allScores.UserScore.Score.PP = null; + scoresContainer.Scores = allScores; + }); + } + private int onlineID = 1; private APIScoresCollection createScores() @@ -210,7 +223,7 @@ namespace osu.Game.Tests.Visual.Online new APIMod { Acronym = new OsuModHidden().Acronym }, }, Rank = ScoreRank.B, - PP = null, + PP = 180, MaxCombo = 1234, TotalScore = 12345678, Accuracy = 0.9854, diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs index 0eb6ec3c04..4bbb72c862 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs @@ -7,6 +7,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; @@ -99,6 +100,23 @@ namespace osu.Game.Tests.Visual.Online Accuracy = 0.55879 }; + var unprocessedPPScore = new SoloScoreInfo + { + Rank = ScoreRank.B, + Beatmap = new APIBeatmap + { + BeatmapSet = new APIBeatmapSet + { + Title = "C18H27NO3(extend)", + Artist = "Team Grimoire", + }, + DifficultyName = "[4K] Cataclysmic Hypernova", + Status = BeatmapOnlineStatus.Ranked, + }, + EndedAt = DateTimeOffset.Now, + Accuracy = 0.55879 + }; + Add(new FillFlowContainer { Anchor = Anchor.Centre, @@ -112,6 +130,7 @@ namespace osu.Game.Tests.Visual.Online new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(firstScore)), new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(secondScore)), new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(noPPScore)), + new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(unprocessedPPScore)), new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(firstScore, 0.97)), new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(secondScore, 0.85)), new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(thirdScore, 0.66)), From 6bf2645b1ae4639bda41bcc825741d1c5a3ae34a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jul 2022 15:44:54 +0900 Subject: [PATCH 108/124] Fix `StarRatingDisplay` not handling negative numbers as "pending" --- .../Visual/UserInterface/TestSceneStarRatingDisplay.cs | 2 +- osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneStarRatingDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneStarRatingDisplay.cs index 1f65b6ec7f..72929a4555 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneStarRatingDisplay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneStarRatingDisplay.cs @@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.UserInterface AutoSizeAxes = Axes.Both, Spacing = new Vector2(2f), Direction = FillDirection.Horizontal, - ChildrenEnumerable = Enumerable.Range(0, 15).Select(i => new FillFlowContainer + ChildrenEnumerable = Enumerable.Range(-1, 15).Select(i => new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs b/osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs index 44bccd69d0..9585f1bdb5 100644 --- a/osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs +++ b/osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs @@ -151,7 +151,7 @@ namespace osu.Game.Beatmaps.Drawables displayedStars.BindValueChanged(s => { - starsText.Text = s.NewValue.ToLocalisableString("0.00"); + starsText.Text = s.NewValue < 0 ? "-" : s.NewValue.ToLocalisableString("0.00"); background.Colour = colours.ForStarDifficulty(s.NewValue); From 4d90e6bbac73e242fb61eeae70b4c6818f4f14c7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 10:03:06 +0300 Subject: [PATCH 109/124] Flip method to read better --- .../Sections/Ranks/DrawableProfileScore.cs | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index b8446c153d..fda2db7acc 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -219,42 +219,42 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks private Drawable createDrawablePerformance() { - if (Score.PP.HasValue) + if (!Score.PP.HasValue) { - return new FillFlowContainer + if (Score.Beatmap?.Status.GrantsPerformancePoints() == true) + return new UnprocessedPerformancePointsPlaceholder { Size = new Vector2(16), Colour = colourProvider.Highlight1 }; + + return new OsuSpriteText { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new[] - { - new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Font = OsuFont.GetFont(weight: FontWeight.Bold), - Text = $"{Score.PP:0}", - Colour = colourProvider.Highlight1 - }, - new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), - Text = "pp", - Colour = colourProvider.Light3 - } - } + Font = OsuFont.GetFont(weight: FontWeight.Bold), + Text = "-", + Colour = colourProvider.Highlight1 }; } - if (Score.Beatmap?.Status.GrantsPerformancePoints() == true) - return new UnprocessedPerformancePointsPlaceholder { Size = new Vector2(16), Colour = colourProvider.Highlight1 }; - - return new OsuSpriteText + return new FillFlowContainer { - Font = OsuFont.GetFont(weight: FontWeight.Bold), - Text = "-", - Colour = colourProvider.Highlight1 + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new[] + { + new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Font = OsuFont.GetFont(weight: FontWeight.Bold), + Text = $"{Score.PP:0}", + Colour = colourProvider.Highlight1 + }, + new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Text = "pp", + Colour = colourProvider.Light3 + } + } }; } From 2dd99ef1fdc835fd14297371a159d94af8f7c9d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jul 2022 16:26:47 +0900 Subject: [PATCH 110/124] Refactor `FPSCounter` to not use scheduled tasks While on the surface this looks harmless (ignoring allocations), `Scheduler` doesn't clear cancelled tasks until they reach their execution time. This can cause an increase in time spent processing the scheduler itself. I don't think a per-frame updating component should use scheduled tasks in this way in the first place, so I've just rewritten the logic to avoid that overhead altogether. --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 55 +++++++++---------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 6fa53b32d8..539ac7ed1f 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Platform; -using osu.Framework.Threading; using osu.Framework.Timing; using osu.Framework.Utils; using osu.Game.Configuration; @@ -44,8 +43,6 @@ namespace osu.Game.Graphics.UserInterface private bool isDisplayed; - private ScheduledDelegate? fadeOutDelegate; - private double aimDrawFPS; private double aimUpdateFPS; @@ -54,6 +51,11 @@ namespace osu.Game.Graphics.UserInterface private ThrottledFrameClock updateClock = null!; private ThrottledFrameClock inputClock = null!; + /// + /// The last time value where the display was required (due to a significant change or hovering). + /// + private double lastDisplayRequiredTime; + [Resolved] private OsuColour colours { get; set; } = null!; @@ -131,13 +133,13 @@ namespace osu.Game.Graphics.UserInterface { base.LoadComplete(); - displayTemporarily(); + requestDisplay(); showFpsDisplay.BindValueChanged(showFps => { State.Value = showFps.NewValue ? Visibility.Visible : Visibility.Hidden; if (showFps.NewValue) - displayTemporarily(); + requestDisplay(); }, true); State.BindValueChanged(state => showFpsDisplay.Value = state.NewValue == Visibility.Visible); @@ -150,38 +152,17 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnHover(HoverEvent e) { background.FadeTo(1, 200); - displayTemporarily(); + requestDisplay(); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { background.FadeTo(idle_background_alpha, 200); - displayTemporarily(); + requestDisplay(); base.OnHoverLost(e); } - private void displayTemporarily() - { - if (!isDisplayed) - { - mainContent.FadeTo(1, 300, Easing.OutQuint); - isDisplayed = true; - } - - fadeOutDelegate?.Cancel(); - fadeOutDelegate = null; - - if (!IsHovered) - { - fadeOutDelegate = Scheduler.AddDelayed(() => - { - mainContent.FadeTo(0, 300, Easing.OutQuint); - isDisplayed = false; - }, 2000); - } - } - protected override void Update() { base.Update(); @@ -221,7 +202,23 @@ namespace osu.Game.Graphics.UserInterface || 1000 / displayedFrameTime < aimUpdateFPS * 0.8; if (hasSignificantChanges) - displayTemporarily(); + requestDisplay(); + else if (isDisplayed && Time.Current - lastDisplayRequiredTime > 2000) + { + mainContent.FadeTo(0, 300, Easing.OutQuint); + isDisplayed = false; + } + } + + private void requestDisplay() + { + lastDisplayRequiredTime = Time.Current; + + if (!isDisplayed) + { + mainContent.FadeTo(1, 300, Easing.OutQuint); + isDisplayed = true; + } } private void updateFpsDisplay() From 93175eaf6efda8c782557c8624b4cc23f2075c66 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 11:39:23 +0300 Subject: [PATCH 111/124] Re-enable timeline zoom test and remove flaky attribute --- .../Visual/Editing/TestSceneTimelineZoom.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs index 09d753ba41..11ac102814 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs @@ -8,22 +8,11 @@ using osu.Framework.Graphics; namespace osu.Game.Tests.Visual.Editing { - [Ignore("Timeline initialisation is kinda broken.")] // Initial work to rectify this was done in https://github.com/ppy/osu/pull/19297, but needs more massaging to work. public class TestSceneTimelineZoom : TimelineTestScene { public override Drawable CreateTestComponent() => Empty(); [Test] - [FlakyTest] - /* - * Fail rate around 0.3% - * - * TearDown : osu.Framework.Testing.Drawables.Steps.AssertButton+TracedException : range halved - * --TearDown - * at osu.Framework.Threading.ScheduledDelegate.RunTaskInternal() - * at osu.Framework.Threading.Scheduler.Update() - * at osu.Framework.Graphics.Drawable.UpdateSubTree() - */ public void TestVisibleRangeUpdatesOnZoomChange() { double initialVisibleRange = 0; From 123930306bc0146f52273f66e26f5c2c5e48f6e2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 11:54:10 +0300 Subject: [PATCH 112/124] Refactor `ZoomableScrollContainer` to allow setting up zoom range and initial zoom after load --- .../TestSceneZoomableScrollContainer.cs | 17 +--- .../Timeline/ZoomableScrollContainer.cs | 99 ++++++++++--------- 2 files changed, 56 insertions(+), 60 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs index 9dc403814b..ce418f33f0 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Editing RelativeSizeAxes = Axes.Both, Colour = OsuColour.Gray(30) }, - scrollContainer = new ZoomableScrollContainer + scrollContainer = new ZoomableScrollContainer(1, 60, 1) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -80,21 +80,6 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("Inner container width matches scroll container", () => innerBox.DrawWidth == scrollContainer.DrawWidth); } - [Test] - public void TestZoomRangeUpdate() - { - AddStep("set zoom to 2", () => scrollContainer.Zoom = 2); - AddStep("set min zoom to 5", () => scrollContainer.MinZoom = 5); - AddAssert("zoom = 5", () => scrollContainer.Zoom == 5); - - AddStep("set max zoom to 10", () => scrollContainer.MaxZoom = 10); - AddAssert("zoom = 5", () => scrollContainer.Zoom == 5); - - AddStep("set min zoom to 20", () => scrollContainer.MinZoom = 20); - AddStep("set max zoom to 40", () => scrollContainer.MaxZoom = 40); - AddAssert("zoom = 20", () => scrollContainer.Zoom == 20); - } - [Test] public void TestZoom0() { diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 83fd1dea2b..fb2297e88c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -32,19 +32,27 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly Container zoomedContent; protected override Container Content => zoomedContent; - private float currentZoom = 1; /// - /// The current zoom level of . - /// It may differ from during transitions. + /// The current zoom level of . + /// It may differ from during transitions. /// - public float CurrentZoom => currentZoom; + public float CurrentZoom { get; private set; } = 1; + + private bool isZoomSetUp; [Resolved(canBeNull: true)] private IFrameBasedClock editorClock { get; set; } private readonly LayoutValue zoomedContentWidthCache = new LayoutValue(Invalidation.DrawSize); + private float minZoom; + private float maxZoom; + + /// + /// Creates a with no zoom range. + /// Functionality will be disabled until zoom is set up via . + /// public ZoomableScrollContainer() : base(Direction.Horizontal) { @@ -53,46 +61,36 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline AddLayout(zoomedContentWidthCache); } - private float minZoom = 1; - /// - /// The minimum zoom level allowed. + /// Creates a with a defined zoom range. /// - public float MinZoom + public ZoomableScrollContainer(float minimum, float maximum, float initial) + : this() { - get => minZoom; - set - { - if (value < 1) - throw new ArgumentException($"{nameof(MinZoom)} must be >= 1.", nameof(value)); - - minZoom = value; - - // ensure zoom range is in valid state before updating zoom. - if (MinZoom < MaxZoom) - updateZoom(); - } + SetupZoom(initial, minimum, maximum); } - private float maxZoom = 60; - /// - /// The maximum zoom level allowed. + /// Sets up the minimum and maximum range of this zoomable scroll container, along with the initial zoom value. /// - public float MaxZoom + /// The initial zoom value, applied immediately. + /// The minimum zoom value. + /// The maximum zoom value. + public void SetupZoom(float initial, float minimum, float maximum) { - get => maxZoom; - set - { - if (value < 1) - throw new ArgumentException($"{nameof(MaxZoom)} must be >= 1.", nameof(value)); + if (minimum < 1) + throw new ArgumentException($"{nameof(minimum)} ({minimum}) must be >= 1.", nameof(maximum)); - maxZoom = value; + if (maximum < 1) + throw new ArgumentException($"{nameof(maximum)} ({maximum}) must be >= 1.", nameof(maximum)); - // ensure zoom range is in valid state before updating zoom. - if (MaxZoom > MinZoom) - updateZoom(); - } + if (minimum > maximum) + throw new ArgumentException($"{nameof(minimum)} ({minimum}) must be less than {nameof(maximum)} ({maximum})"); + + minZoom = minimum; + maxZoom = maximum; + CurrentZoom = zoomTarget = initial; + isZoomSetUp = true; } /// @@ -104,14 +102,17 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline set => updateZoom(value); } - private void updateZoom(float? value = null) + private void updateZoom(float value) { - float newZoom = Math.Clamp(value ?? Zoom, MinZoom, MaxZoom); + if (!isZoomSetUp) + return; + + float newZoom = Math.Clamp(value, minZoom, maxZoom); if (IsLoaded) setZoomTarget(newZoom, ToSpaceOfOtherDrawable(new Vector2(DrawWidth / 2, 0), zoomedContent).X); else - currentZoom = zoomTarget = newZoom; + CurrentZoom = zoomTarget = newZoom; } protected override void Update() @@ -141,22 +142,32 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateZoomedContentWidth() { - zoomedContent.Width = DrawWidth * currentZoom; + zoomedContent.Width = DrawWidth * CurrentZoom; zoomedContentWidthCache.Validate(); } public void AdjustZoomRelatively(float change, float? focusPoint = null) { + if (!isZoomSetUp) + return; + const float zoom_change_sensitivity = 0.02f; - setZoomTarget(zoomTarget + change * (MaxZoom - minZoom) * zoom_change_sensitivity, focusPoint); + setZoomTarget(zoomTarget + change * (maxZoom - minZoom) * zoom_change_sensitivity, focusPoint); + } + + protected void SetZoomImmediately(float value, float min, float max) + { + maxZoom = max; + minZoom = min; + CurrentZoom = zoomTarget = value; } private float zoomTarget = 1; private void setZoomTarget(float newZoom, float? focusPoint = null) { - zoomTarget = Math.Clamp(newZoom, MinZoom, MaxZoom); + zoomTarget = Math.Clamp(newZoom, minZoom, maxZoom); focusPoint ??= zoomedContent.ToLocalSpace(ToScreenSpace(new Vector2(DrawWidth / 2, 0))).X; transformZoomTo(zoomTarget, focusPoint.Value, ZoomDuration, ZoomEasing); @@ -192,7 +203,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly float scrollOffset; /// - /// Transforms to a new value. + /// Transforms to a new value. /// /// The focus point in absolute coordinates local to the content. /// The size of the content. @@ -204,7 +215,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline this.scrollOffset = scrollOffset; } - public override string TargetMember => nameof(currentZoom); + public override string TargetMember => nameof(CurrentZoom); private float valueAt(double time) { @@ -222,7 +233,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline float expectedWidth = d.DrawWidth * newZoom; float targetOffset = expectedWidth * (focusPoint / contentSize) - focusOffset; - d.currentZoom = newZoom; + d.CurrentZoom = newZoom; d.updateZoomedContentWidth(); // Temporarily here to make sure ScrollTo gets the correct DrawSize for scrollable area. @@ -231,7 +242,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline d.ScrollTo(targetOffset, false); } - protected override void ReadIntoStartValue(ZoomableScrollContainer d) => StartValue = d.currentZoom; + protected override void ReadIntoStartValue(ZoomableScrollContainer d) => StartValue = d.CurrentZoom; } } } From 07c6b4486491848b358f1a2e8c8de56523cc62e3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 11:54:47 +0300 Subject: [PATCH 113/124] Fix `Timeline` attempting to setup zoom with unloaded track --- .../Compose/Components/Timeline/Timeline.cs | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index bbe011a2e0..54f2d13707 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -146,13 +146,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline waveform.Waveform = b.NewValue.Waveform; track = b.NewValue.Track; - // todo: i don't think this is safe, the track may not be loaded yet. - if (track.Length > 0) - { - MaxZoom = getZoomLevelForVisibleMilliseconds(500); - MinZoom = getZoomLevelForVisibleMilliseconds(10000); - defaultTimelineZoom = getZoomLevelForVisibleMilliseconds(6000); - } + setupTimelineZoom(); }, true); Zoom = (float)(defaultTimelineZoom * editorBeatmap.BeatmapInfo.TimelineZoom); @@ -205,6 +199,20 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline scrollToTrackTime(); } + private void setupTimelineZoom() + { + if (!track.IsLoaded) + { + Scheduler.AddOnce(setupTimelineZoom); + return; + } + + defaultTimelineZoom = getZoomLevelForVisibleMilliseconds(6000); + + float initialZoom = (float)(defaultTimelineZoom * editorBeatmap.BeatmapInfo.TimelineZoom); + SetupZoom(initialZoom, getZoomLevelForVisibleMilliseconds(10000), getZoomLevelForVisibleMilliseconds(500)); + } + protected override bool OnScroll(ScrollEvent e) { // if this is not a precision scroll event, let the editor handle the seek itself (for snapping support) From bc2b629ee7c89ec71dfbac7d11bd36adfdc4bb8a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 11:43:34 +0300 Subject: [PATCH 114/124] Let tests wait until track load before testing zoom --- osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs index 11ac102814..630d048867 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs @@ -17,6 +17,8 @@ namespace osu.Game.Tests.Visual.Editing { double initialVisibleRange = 0; + AddUntilStep("wait for load", () => MusicController.TrackLoaded); + AddStep("reset zoom", () => TimelineArea.Timeline.Zoom = 100); AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange); @@ -34,6 +36,8 @@ namespace osu.Game.Tests.Visual.Editing { double initialVisibleRange = 0; + AddUntilStep("wait for load", () => MusicController.TrackLoaded); + AddStep("reset timeline size", () => TimelineArea.Timeline.Width = 1); AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange); From 48bcf57066f1b5f67e0849a3cac9a9c8e4feb8bc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 12:06:54 +0300 Subject: [PATCH 115/124] Mark `SetupZoom` and parameterless `ZoomableScrollContainer` ctor as protected --- .../Compose/Components/Timeline/ZoomableScrollContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index fb2297e88c..725f94e7db 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// Creates a with no zoom range. /// Functionality will be disabled until zoom is set up via . /// - public ZoomableScrollContainer() + protected ZoomableScrollContainer() : base(Direction.Horizontal) { base.Content.Add(zoomedContent = new Container { RelativeSizeAxes = Axes.Y }); @@ -76,7 +76,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// The initial zoom value, applied immediately. /// The minimum zoom value. /// The maximum zoom value. - public void SetupZoom(float initial, float minimum, float maximum) + protected void SetupZoom(float initial, float minimum, float maximum) { if (minimum < 1) throw new ArgumentException($"{nameof(minimum)} ({minimum}) must be >= 1.", nameof(maximum)); From d04df19c7e2ba81107cac71df5435d5cd016f504 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 13:03:06 +0300 Subject: [PATCH 116/124] Remove `APIScore` and replace its final usage --- .../Gameplay/TestSceneReplayDownloadButton.cs | 28 ++- .../Online/API/Requests/Responses/APIScore.cs | 162 ------------------ osu.Game/Online/Solo/SubmittableScore.cs | 2 - 3 files changed, 10 insertions(+), 182 deletions(-) delete mode 100644 osu.Game/Online/API/Requests/Responses/APIScore.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index c259d5f0a8..1384d4ef6e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -7,7 +7,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Online; -using osu.Game.Online.API.Requests.Responses; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; @@ -15,7 +14,6 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking; @@ -30,9 +28,6 @@ namespace osu.Game.Tests.Visual.Gameplay { private const long online_score_id = 2553163309; - [Resolved] - private RulesetStore rulesets { get; set; } - private TestReplayDownloadButton downloadButton; [Resolved] @@ -211,21 +206,18 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType().First().Enabled.Value); } - private ScoreInfo getScoreInfo(bool replayAvailable, bool hasOnlineId = true) + private ScoreInfo getScoreInfo(bool replayAvailable, bool hasOnlineId = true) => new ScoreInfo { - return new APIScore + OnlineID = hasOnlineId ? online_score_id : 0, + Ruleset = new OsuRuleset().RulesetInfo, + BeatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(), + Hash = replayAvailable ? "hash" : string.Empty, + User = new APIUser { - OnlineID = hasOnlineId ? online_score_id : 0, - RulesetID = 0, - Beatmap = CreateAPIBeatmapSet(new OsuRuleset().RulesetInfo).Beatmaps.First(), - HasReplay = replayAvailable, - User = new APIUser - { - Id = 39828, - Username = @"WubWoofWolf", - } - }.CreateScoreInfo(rulesets, beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First()); - } + Id = 39828, + Username = @"WubWoofWolf", + } + }; private class TestReplayDownloadButton : ReplayDownloadButton { diff --git a/osu.Game/Online/API/Requests/Responses/APIScore.cs b/osu.Game/Online/API/Requests/Responses/APIScore.cs deleted file mode 100644 index f236607761..0000000000 --- a/osu.Game/Online/API/Requests/Responses/APIScore.cs +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System; -using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using osu.Game.Beatmaps; -using osu.Game.Database; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; -using osu.Game.Scoring; -using osu.Game.Scoring.Legacy; -using osu.Game.Users; - -namespace osu.Game.Online.API.Requests.Responses -{ - public class APIScore : IScoreInfo - { - [JsonProperty(@"score")] - public long TotalScore { get; set; } - - [JsonProperty(@"max_combo")] - public int MaxCombo { get; set; } - - [JsonProperty(@"user")] - public APIUser User { get; set; } - - [JsonProperty(@"id")] - public long OnlineID { get; set; } - - [JsonProperty(@"replay")] - public bool HasReplay { get; set; } - - [JsonProperty(@"created_at")] - public DateTimeOffset Date { get; set; } - - [JsonProperty(@"beatmap")] - [CanBeNull] - public APIBeatmap Beatmap { get; set; } - - [JsonProperty("accuracy")] - public double Accuracy { get; set; } - - [JsonProperty(@"pp")] - public double? PP { get; set; } - - [JsonProperty(@"beatmapset")] - [CanBeNull] - public APIBeatmapSet BeatmapSet - { - set - { - // in the deserialisation case we need to ferry this data across. - // the order of properties returned by the API guarantees that the beatmap is populated by this point. - if (!(Beatmap is APIBeatmap apiBeatmap)) - throw new InvalidOperationException("Beatmap set metadata arrived before beatmap metadata in response"); - - apiBeatmap.BeatmapSet = value; - } - } - - [JsonProperty("statistics")] - public Dictionary Statistics { get; set; } - - [JsonProperty(@"mode_int")] - public int RulesetID { get; set; } - - [JsonProperty(@"mods")] - private string[] mods { set => Mods = value.Select(acronym => new APIMod { Acronym = acronym }); } - - [NotNull] - public IEnumerable Mods { get; set; } = Array.Empty(); - - [JsonProperty("rank")] - [JsonConverter(typeof(StringEnumConverter))] - public ScoreRank Rank { get; set; } - - /// - /// Create a from an API score instance. - /// - /// A ruleset store, used to populate a ruleset instance in the returned score. - /// An optional beatmap, copied into the returned score (for cases where the API does not populate the beatmap). - /// - public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null) - { - var ruleset = rulesets.GetRuleset(RulesetID) ?? throw new InvalidOperationException($"Ruleset with ID of {RulesetID} not found locally"); - - var rulesetInstance = ruleset.CreateInstance(); - - var modInstances = Mods.Select(apiMod => rulesetInstance.CreateModFromAcronym(apiMod.Acronym)).Where(m => m != null).ToArray(); - - // all API scores provided by this class are considered to be legacy. - modInstances = modInstances.Append(rulesetInstance.CreateMod()).ToArray(); - - var scoreInfo = new ScoreInfo - { - TotalScore = TotalScore, - MaxCombo = MaxCombo, - BeatmapInfo = beatmap ?? new BeatmapInfo(), - User = User, - Accuracy = Accuracy, - OnlineID = OnlineID, - Date = Date, - PP = PP, - Hash = HasReplay ? "online" : string.Empty, // todo: temporary? - Rank = Rank, - Ruleset = ruleset, - Mods = modInstances, - }; - - if (Statistics != null) - { - foreach (var kvp in Statistics) - { - switch (kvp.Key) - { - case @"count_geki": - scoreInfo.SetCountGeki(kvp.Value); - break; - - case @"count_300": - scoreInfo.SetCount300(kvp.Value); - break; - - case @"count_katu": - scoreInfo.SetCountKatu(kvp.Value); - break; - - case @"count_100": - scoreInfo.SetCount100(kvp.Value); - break; - - case @"count_50": - scoreInfo.SetCount50(kvp.Value); - break; - - case @"count_miss": - scoreInfo.SetCountMiss(kvp.Value); - break; - } - } - } - - return scoreInfo; - } - - public IRulesetInfo Ruleset => new RulesetInfo { OnlineID = RulesetID }; - IEnumerable IHasNamedFiles.Files => throw new NotImplementedException(); - - #region Implementation of IScoreInfo - - IBeatmapInfo IScoreInfo.Beatmap => Beatmap; - IUser IScoreInfo.User => User; - - #endregion - } -} diff --git a/osu.Game/Online/Solo/SubmittableScore.cs b/osu.Game/Online/Solo/SubmittableScore.cs index 31f0cb1dfb..73782060b3 100644 --- a/osu.Game/Online/Solo/SubmittableScore.cs +++ b/osu.Game/Online/Solo/SubmittableScore.cs @@ -9,7 +9,6 @@ using JetBrains.Annotations; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using osu.Game.Online.API; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -17,7 +16,6 @@ namespace osu.Game.Online.Solo { /// /// A class specifically for sending scores to the API during score submission. - /// This is used instead of due to marginally different serialisation naming requirements. /// [Serializable] public class SubmittableScore From 1b6ebcfd87772a104773ead922fa0817891609f6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 13:43:19 +0300 Subject: [PATCH 117/124] Remove `SubmittableScore` and replace with `SoloScoreInfo` extension method --- .../Online/TestAPIModJsonSerialization.cs | 13 ++-- ... => TestSoloScoreInfoJsonSerialization.cs} | 8 +-- .../API/Requests/Responses/SoloScoreInfo.cs | 17 +++++ osu.Game/Online/Rooms/SubmitScoreRequest.cs | 6 +- osu.Game/Online/Solo/SubmittableScore.cs | 70 ------------------- 5 files changed, 30 insertions(+), 84 deletions(-) rename osu.Game.Tests/Online/{TestSubmittableScoreJsonSerialization.cs => TestSoloScoreInfoJsonSerialization.cs} (79%) delete mode 100644 osu.Game/Online/Solo/SubmittableScore.cs diff --git a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs index 185f85513b..67dbcf0ccf 100644 --- a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs @@ -12,7 +12,6 @@ using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Online.Solo; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; @@ -110,30 +109,30 @@ namespace osu.Game.Tests.Online } [Test] - public void TestDeserialiseSubmittableScoreWithEmptyMods() + public void TestDeserialiseSoloScoreWithEmptyMods() { - var score = new SubmittableScore(new ScoreInfo + var score = SoloScoreInfo.ForSubmission(new ScoreInfo { User = new APIUser(), Ruleset = new OsuRuleset().RulesetInfo, }); - var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score)); + var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score)); Assert.That(deserialised?.Mods.Length, Is.Zero); } [Test] - public void TestDeserialiseSubmittableScoreWithCustomModSetting() + public void TestDeserialiseSoloScoreWithCustomModSetting() { - var score = new SubmittableScore(new ScoreInfo + var score = SoloScoreInfo.ForSubmission(new ScoreInfo { Mods = new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2 } } }, User = new APIUser(), Ruleset = new OsuRuleset().RulesetInfo, }); - var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score)); + var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score)); Assert.That((deserialised?.Mods[0])?.Settings["speed_change"], Is.EqualTo(2)); } diff --git a/osu.Game.Tests/Online/TestSubmittableScoreJsonSerialization.cs b/osu.Game.Tests/Online/TestSoloScoreInfoJsonSerialization.cs similarity index 79% rename from osu.Game.Tests/Online/TestSubmittableScoreJsonSerialization.cs rename to osu.Game.Tests/Online/TestSoloScoreInfoJsonSerialization.cs index f0f6727393..8ff0b67b5b 100644 --- a/osu.Game.Tests/Online/TestSubmittableScoreJsonSerialization.cs +++ b/osu.Game.Tests/Online/TestSoloScoreInfoJsonSerialization.cs @@ -6,7 +6,7 @@ using Newtonsoft.Json; using NUnit.Framework; using osu.Game.IO.Serialization; -using osu.Game.Online.Solo; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Tests.Resources; namespace osu.Game.Tests.Online @@ -15,12 +15,12 @@ namespace osu.Game.Tests.Online /// Basic testing to ensure our attribute-based naming is correctly working. /// [TestFixture] - public class TestSubmittableScoreJsonSerialization + public class TestSoloScoreInfoJsonSerialization { [Test] public void TestScoreSerialisationViaExtensionMethod() { - var score = new SubmittableScore(TestResources.CreateTestScoreInfo()); + var score = SoloScoreInfo.ForSubmission(TestResources.CreateTestScoreInfo()); string serialised = score.Serialize(); @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Online [Test] public void TestScoreSerialisationWithoutSettings() { - var score = new SubmittableScore(TestResources.CreateTestScoreInfo()); + var score = SoloScoreInfo.ForSubmission(TestResources.CreateTestScoreInfo()); string serialised = JsonConvert.SerializeObject(score); diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index bfc8b4102a..6558578023 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -151,6 +151,23 @@ namespace osu.Game.Online.API.Requests.Responses PP = PP, }; + /// + /// Creates a from a local score for score submission. + /// + /// The local score. + public static SoloScoreInfo ForSubmission(ScoreInfo score) => new SoloScoreInfo + { + Rank = score.Rank, + TotalScore = (int)score.TotalScore, + Accuracy = score.Accuracy, + PP = score.PP, + MaxCombo = score.MaxCombo, + RulesetID = score.RulesetID, + Passed = score.Passed, + Mods = score.APIMods, + Statistics = score.Statistics, + }; + public long OnlineID => ID ?? -1; } } diff --git a/osu.Game/Online/Rooms/SubmitScoreRequest.cs b/osu.Game/Online/Rooms/SubmitScoreRequest.cs index d681938da5..48a7780a03 100644 --- a/osu.Game/Online/Rooms/SubmitScoreRequest.cs +++ b/osu.Game/Online/Rooms/SubmitScoreRequest.cs @@ -7,20 +7,20 @@ using System.Net.Http; using Newtonsoft.Json; using osu.Framework.IO.Network; using osu.Game.Online.API; -using osu.Game.Online.Solo; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Scoring; namespace osu.Game.Online.Rooms { public abstract class SubmitScoreRequest : APIRequest { - public readonly SubmittableScore Score; + public readonly SoloScoreInfo Score; protected readonly long ScoreId; protected SubmitScoreRequest(ScoreInfo scoreInfo, long scoreId) { - Score = new SubmittableScore(scoreInfo); + Score = SoloScoreInfo.ForSubmission(scoreInfo); ScoreId = scoreId; } diff --git a/osu.Game/Online/Solo/SubmittableScore.cs b/osu.Game/Online/Solo/SubmittableScore.cs deleted file mode 100644 index 73782060b3..0000000000 --- a/osu.Game/Online/Solo/SubmittableScore.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System; -using System.Collections.Generic; -using JetBrains.Annotations; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using osu.Game.Online.API; -using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; - -namespace osu.Game.Online.Solo -{ - /// - /// A class specifically for sending scores to the API during score submission. - /// - [Serializable] - public class SubmittableScore - { - [JsonProperty("rank")] - [JsonConverter(typeof(StringEnumConverter))] - public ScoreRank Rank { get; set; } - - [JsonProperty("total_score")] - public long TotalScore { get; set; } - - [JsonProperty("accuracy")] - public double Accuracy { get; set; } - - [JsonProperty(@"pp")] - public double? PP { get; set; } - - [JsonProperty("max_combo")] - public int MaxCombo { get; set; } - - [JsonProperty("ruleset_id")] - public int RulesetID { get; set; } - - [JsonProperty("passed")] - public bool Passed { get; set; } - - // Used for API serialisation/deserialisation. - [JsonProperty("mods")] - public APIMod[] Mods { get; set; } - - [JsonProperty("statistics")] - public Dictionary Statistics { get; set; } - - [UsedImplicitly] - public SubmittableScore() - { - } - - public SubmittableScore(ScoreInfo score) - { - Rank = score.Rank; - TotalScore = score.TotalScore; - Accuracy = score.Accuracy; - PP = score.PP; - MaxCombo = score.MaxCombo; - RulesetID = score.RulesetID; - Passed = score.Passed; - Mods = score.APIMods; - Statistics = score.Statistics; - } - } -} From e5355f314d4b5a851c0646f2da7d2916c2972106 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 15:19:32 +0300 Subject: [PATCH 118/124] Use longer hash string --- osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 1384d4ef6e..9d70d1ef33 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -211,7 +211,7 @@ namespace osu.Game.Tests.Visual.Gameplay OnlineID = hasOnlineId ? online_score_id : 0, Ruleset = new OsuRuleset().RulesetInfo, BeatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(), - Hash = replayAvailable ? "hash" : string.Empty, + Hash = replayAvailable ? "online" : string.Empty, User = new APIUser { Id = 39828, From ee0c67e114826da6d497bec8b78f6ddd5efd5630 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jul 2022 14:07:33 +0900 Subject: [PATCH 119/124] Add ability to make cursor show even during touch input I completely disagree with this from a UX perspective, but it's come up so often that I figure we should just let users bone themselves. --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ osu.Game/Graphics/Cursor/MenuCursorContainer.cs | 15 +++++++++++++-- osu.Game/Localisation/SkinSettingsStrings.cs | 5 +++++ .../Settings/Sections/Gameplay/InputSettings.cs | 5 +++++ 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index e816fd50f3..fb585e9cbd 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -91,6 +91,7 @@ namespace osu.Game.Configuration // Input SetDefault(OsuSetting.MenuCursorSize, 1.0f, 0.5f, 2f, 0.01f); SetDefault(OsuSetting.GameplayCursorSize, 1.0f, 0.1f, 2f, 0.01f); + SetDefault(OsuSetting.GameplayCursorDuringTouch, false); SetDefault(OsuSetting.AutoCursorSize, false); SetDefault(OsuSetting.MouseDisableButtons, false); @@ -292,6 +293,7 @@ namespace osu.Game.Configuration MenuCursorSize, GameplayCursorSize, AutoCursorSize, + GameplayCursorDuringTouch, DimLevel, BlurLevel, LightenDuringBreaks, diff --git a/osu.Game/Graphics/Cursor/MenuCursorContainer.cs b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs index 746f1b5d5e..e03dc4df6f 100644 --- a/osu.Game/Graphics/Cursor/MenuCursorContainer.cs +++ b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs @@ -3,16 +3,20 @@ #nullable disable +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Input; using osu.Framework.Input.StateChanges; +using osu.Game.Configuration; namespace osu.Game.Graphics.Cursor { /// - /// A container which provides a which can be overridden by hovered s. + /// A container which provides a . + /// It also handles cases where a more localised cursor is provided by another component (via ). /// public class MenuCursorContainer : Container, IProvideCursor { @@ -36,12 +40,19 @@ namespace osu.Game.Graphics.Cursor }); } + private Bindable showDuringTouch; + private InputManager inputManager; + [Resolved] + private OsuConfigManager config { get; set; } + protected override void LoadComplete() { base.LoadComplete(); inputManager = GetContainingInputManager(); + + showDuringTouch = config.GetBindable(OsuSetting.GameplayCursorDuringTouch); } private IProvideCursor currentTarget; @@ -51,7 +62,7 @@ namespace osu.Game.Graphics.Cursor base.Update(); var lastMouseSource = inputManager.CurrentState.Mouse.LastSource; - bool hasValidInput = lastMouseSource != null && !(lastMouseSource is ISourcedFromTouch); + bool hasValidInput = lastMouseSource != null && (showDuringTouch.Value || lastMouseSource is not ISourcedFromTouch); if (!hasValidInput || !CanShowCursor) { diff --git a/osu.Game/Localisation/SkinSettingsStrings.cs b/osu.Game/Localisation/SkinSettingsStrings.cs index 81035c5a5e..4b6b0ce1d6 100644 --- a/osu.Game/Localisation/SkinSettingsStrings.cs +++ b/osu.Game/Localisation/SkinSettingsStrings.cs @@ -34,6 +34,11 @@ namespace osu.Game.Localisation /// public static LocalisableString AutoCursorSize => new TranslatableString(getKey(@"auto_cursor_size"), @"Adjust gameplay cursor size based on current beatmap"); + /// + /// "Show gameplay cursor during touch input" + /// + public static LocalisableString GameplayCursorDuringTouch => new TranslatableString(getKey(@"gameplay_cursor_during_touch"), @"Show gameplay cursor during touch input"); + /// /// "Beatmap skins" /// diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/InputSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/InputSettings.cs index b5315d5268..ac59a6c0ed 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/InputSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/InputSettings.cs @@ -32,6 +32,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay LabelText = SkinSettingsStrings.AutoCursorSize, Current = config.GetBindable(OsuSetting.AutoCursorSize) }, + new SettingsCheckbox + { + LabelText = SkinSettingsStrings.GameplayCursorDuringTouch, + Current = config.GetBindable(OsuSetting.GameplayCursorDuringTouch) + }, }; if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) From ef10145d6f9f77117e6e148cf82f76f6b89ffdb4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jul 2022 14:11:52 +0900 Subject: [PATCH 120/124] Rename `MenuCursorContainer` and clean up code --- .../Visual/Gameplay/TestScenePause.cs | 2 +- .../Visual/UserInterface/TestSceneCursors.cs | 62 +++++++++--------- osu.Game.Tournament/TournamentGameBase.cs | 4 +- ...sorContainer.cs => GlobalCursorDisplay.cs} | 64 +++++++++---------- osu.Game/Graphics/Cursor/IProvideCursor.cs | 4 +- osu.Game/OsuGame.cs | 4 +- osu.Game/OsuGameBase.cs | 6 +- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- osu.Game/Screens/Utility/LatencyArea.cs | 8 +-- .../Visual/OsuManualInputManagerTestScene.cs | 6 +- 10 files changed, 80 insertions(+), 82 deletions(-) rename osu.Game/Graphics/Cursor/{MenuCursorContainer.cs => GlobalCursorDisplay.cs} (52%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 5bd0a29308..71cc1f7b23 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay public TestScenePause() { - base.Content.Add(content = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }); + base.Content.Add(content = new GlobalCursorDisplay { RelativeSizeAxes = Axes.Both }); } [SetUpSteps] diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs index 0fa7c5303c..bcbe146456 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs @@ -21,12 +21,12 @@ namespace osu.Game.Tests.Visual.UserInterface [TestFixture] public class TestSceneCursors : OsuManualInputManagerTestScene { - private readonly MenuCursorContainer menuCursorContainer; + private readonly GlobalCursorDisplay globalCursorDisplay; private readonly CustomCursorBox[] cursorBoxes = new CustomCursorBox[6]; public TestSceneCursors() { - Child = menuCursorContainer = new MenuCursorContainer + Child = globalCursorDisplay = new GlobalCursorDisplay { RelativeSizeAxes = Axes.Both, Children = new[] @@ -96,11 +96,11 @@ namespace osu.Game.Tests.Visual.UserInterface private void testUserCursor() { AddStep("Move to green area", () => InputManager.MoveMouseTo(cursorBoxes[0])); - AddAssert("Check green cursor visible", () => checkVisible(cursorBoxes[0].Cursor)); - AddAssert("Check green cursor at mouse", () => checkAtMouse(cursorBoxes[0].Cursor)); + AddAssert("Check green cursor visible", () => checkVisible(cursorBoxes[0].MenuCursor)); + AddAssert("Check green cursor at mouse", () => checkAtMouse(cursorBoxes[0].MenuCursor)); AddStep("Move out", moveOut); - AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].Cursor)); - AddAssert("Check global cursor visible", () => checkVisible(menuCursorContainer.Cursor)); + AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].MenuCursor)); + AddAssert("Check global cursor visible", () => checkVisible(globalCursorDisplay.MenuCursor)); } /// @@ -111,13 +111,13 @@ namespace osu.Game.Tests.Visual.UserInterface private void testLocalCursor() { AddStep("Move to purple area", () => InputManager.MoveMouseTo(cursorBoxes[3])); - AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor)); - AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].Cursor)); - AddAssert("Check global cursor visible", () => checkVisible(menuCursorContainer.Cursor)); - AddAssert("Check global cursor at mouse", () => checkAtMouse(menuCursorContainer.Cursor)); + AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].MenuCursor)); + AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].MenuCursor)); + AddAssert("Check global cursor visible", () => checkVisible(globalCursorDisplay.MenuCursor)); + AddAssert("Check global cursor at mouse", () => checkAtMouse(globalCursorDisplay.MenuCursor)); AddStep("Move out", moveOut); - AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor)); - AddAssert("Check global cursor visible", () => checkVisible(menuCursorContainer.Cursor)); + AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].MenuCursor)); + AddAssert("Check global cursor visible", () => checkVisible(globalCursorDisplay.MenuCursor)); } /// @@ -128,12 +128,12 @@ namespace osu.Game.Tests.Visual.UserInterface private void testUserCursorOverride() { AddStep("Move to blue-green boundary", () => InputManager.MoveMouseTo(cursorBoxes[1].ScreenSpaceDrawQuad.BottomRight - new Vector2(10))); - AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].Cursor)); - AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].Cursor)); - AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].Cursor)); + AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].MenuCursor)); + AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].MenuCursor)); + AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].MenuCursor)); AddStep("Move out", moveOut); - AddAssert("Check blue cursor not visible", () => !checkVisible(cursorBoxes[1].Cursor)); - AddAssert("Check green cursor not visible", () => !checkVisible(cursorBoxes[0].Cursor)); + AddAssert("Check blue cursor not visible", () => !checkVisible(cursorBoxes[1].MenuCursor)); + AddAssert("Check green cursor not visible", () => !checkVisible(cursorBoxes[0].MenuCursor)); } /// @@ -143,13 +143,13 @@ namespace osu.Game.Tests.Visual.UserInterface private void testMultipleLocalCursors() { AddStep("Move to yellow-purple boundary", () => InputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.BottomRight - new Vector2(10))); - AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor)); - AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].Cursor)); - AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor)); - AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].Cursor)); + AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].MenuCursor)); + AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].MenuCursor)); + AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].MenuCursor)); + AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].MenuCursor)); AddStep("Move out", moveOut); - AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor)); - AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor)); + AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].MenuCursor)); + AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].MenuCursor)); } /// @@ -159,13 +159,13 @@ namespace osu.Game.Tests.Visual.UserInterface private void testUserOverrideWithLocal() { AddStep("Move to yellow-blue boundary", () => InputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.TopRight - new Vector2(10))); - AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].Cursor)); - AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].Cursor)); - AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor)); - AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].Cursor)); + AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].MenuCursor)); + AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].MenuCursor)); + AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].MenuCursor)); + AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].MenuCursor)); AddStep("Move out", moveOut); - AddAssert("Check blue cursor invisible", () => !checkVisible(cursorBoxes[1].Cursor)); - AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor)); + AddAssert("Check blue cursor invisible", () => !checkVisible(cursorBoxes[1].MenuCursor)); + AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].MenuCursor)); } /// @@ -191,7 +191,7 @@ namespace osu.Game.Tests.Visual.UserInterface { public bool SmoothTransition; - public CursorContainer Cursor { get; } + public CursorContainer MenuCursor { get; } public bool ProvidingUserCursor { get; } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || (SmoothTransition && !ProvidingUserCursor); @@ -218,7 +218,7 @@ namespace osu.Game.Tests.Visual.UserInterface Origin = Anchor.Centre, Text = providesUserCursor ? "User cursor" : "Local cursor" }, - Cursor = new TestCursorContainer + MenuCursor = new TestCursorContainer { State = { Value = providesUserCursor ? Visibility.Hidden : Visibility.Visible }, } diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 063b62bf08..1861e39c60 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -70,10 +70,10 @@ namespace osu.Game.Tournament protected override void LoadComplete() { - MenuCursorContainer.Cursor.AlwaysPresent = true; // required for tooltip display + GlobalCursorDisplay.MenuCursor.AlwaysPresent = true; // required for tooltip display // we don't want to show the menu cursor as it would appear on stream output. - MenuCursorContainer.Cursor.Alpha = 0; + GlobalCursorDisplay.MenuCursor.Alpha = 0; base.LoadComplete(); diff --git a/osu.Game/Graphics/Cursor/MenuCursorContainer.cs b/osu.Game/Graphics/Cursor/GlobalCursorDisplay.cs similarity index 52% rename from osu.Game/Graphics/Cursor/MenuCursorContainer.cs rename to osu.Game/Graphics/Cursor/GlobalCursorDisplay.cs index e03dc4df6f..6613e18cbe 100644 --- a/osu.Game/Graphics/Cursor/MenuCursorContainer.cs +++ b/osu.Game/Graphics/Cursor/GlobalCursorDisplay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -15,48 +13,48 @@ using osu.Game.Configuration; namespace osu.Game.Graphics.Cursor { /// - /// A container which provides a . - /// It also handles cases where a more localised cursor is provided by another component (via ). + /// A container which provides the main . + /// Also handles cases where a more localised cursor is provided by another component (via ). /// - public class MenuCursorContainer : Container, IProvideCursor + public class GlobalCursorDisplay : Container, IProvideCursor { - protected override Container Content => content; - private readonly Container content; - /// - /// Whether any cursors can be displayed. + /// Control whether any cursor should be displayed. /// - internal bool CanShowCursor = true; + internal bool ShowCursor = true; + + public CursorContainer MenuCursor { get; } - public CursorContainer Cursor { get; } public bool ProvidingUserCursor => true; - public MenuCursorContainer() + protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both }; + + private Bindable showDuringTouch = null!; + + private InputManager inputManager = null!; + + private IProvideCursor? currentOverrideProvider; + + [Resolved] + private OsuConfigManager config { get; set; } = null!; + + public GlobalCursorDisplay() { AddRangeInternal(new Drawable[] { - Cursor = new MenuCursor { State = { Value = Visibility.Hidden } }, - content = new Container { RelativeSizeAxes = Axes.Both } + MenuCursor = new MenuCursor { State = { Value = Visibility.Hidden } }, + Content = new Container { RelativeSizeAxes = Axes.Both } }); } - private Bindable showDuringTouch; - - private InputManager inputManager; - - [Resolved] - private OsuConfigManager config { get; set; } - protected override void LoadComplete() { base.LoadComplete(); - inputManager = GetContainingInputManager(); + inputManager = GetContainingInputManager(); showDuringTouch = config.GetBindable(OsuSetting.GameplayCursorDuringTouch); } - private IProvideCursor currentTarget; - protected override void Update() { base.Update(); @@ -64,31 +62,31 @@ namespace osu.Game.Graphics.Cursor var lastMouseSource = inputManager.CurrentState.Mouse.LastSource; bool hasValidInput = lastMouseSource != null && (showDuringTouch.Value || lastMouseSource is not ISourcedFromTouch); - if (!hasValidInput || !CanShowCursor) + if (!hasValidInput || !ShowCursor) { - currentTarget?.Cursor?.Hide(); - currentTarget = null; + currentOverrideProvider?.MenuCursor?.Hide(); + currentOverrideProvider = null; return; } - IProvideCursor newTarget = this; + IProvideCursor newOverrideProvider = this; foreach (var d in inputManager.HoveredDrawables) { if (d is IProvideCursor p && p.ProvidingUserCursor) { - newTarget = p; + newOverrideProvider = p; break; } } - if (currentTarget == newTarget) + if (currentOverrideProvider == newOverrideProvider) return; - currentTarget?.Cursor?.Hide(); - newTarget.Cursor?.Show(); + currentOverrideProvider?.MenuCursor?.Hide(); + newOverrideProvider.MenuCursor?.Show(); - currentTarget = newTarget; + currentOverrideProvider = newOverrideProvider; } } } diff --git a/osu.Game/Graphics/Cursor/IProvideCursor.cs b/osu.Game/Graphics/Cursor/IProvideCursor.cs index 9f01e5da6d..f7f7b75bc8 100644 --- a/osu.Game/Graphics/Cursor/IProvideCursor.cs +++ b/osu.Game/Graphics/Cursor/IProvideCursor.cs @@ -17,10 +17,10 @@ namespace osu.Game.Graphics.Cursor /// The cursor provided by this . /// May be null if no cursor should be visible. /// - CursorContainer Cursor { get; } + CursorContainer MenuCursor { get; } /// - /// Whether should be displayed as the singular user cursor. This will temporarily hide any other user cursor. + /// Whether should be displayed as the singular user cursor. This will temporarily hide any other user cursor. /// This value is checked every frame and may be used to control whether multiple cursors are displayed (e.g. watching replays). /// bool ProvidingUserCursor { get; } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 4f08511783..1ee53e2848 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -716,7 +716,7 @@ namespace osu.Game // The next time this is updated is in UpdateAfterChildren, which occurs too late and results // in the cursor being shown for a few frames during the intro. // This prevents the cursor from showing until we have a screen with CursorVisible = true - MenuCursorContainer.CanShowCursor = menuScreen?.CursorVisible ?? false; + GlobalCursorDisplay.ShowCursor = menuScreen?.CursorVisible ?? false; // todo: all archive managers should be able to be looped here. SkinManager.PostNotification = n => Notifications.Post(n); @@ -1231,7 +1231,7 @@ namespace osu.Game ScreenOffsetContainer.X = horizontalOffset; overlayContent.X = horizontalOffset * 1.2f; - MenuCursorContainer.CanShowCursor = (ScreenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false; + GlobalCursorDisplay.ShowCursor = (ScreenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false; } private void screenChanged(IScreen current, IScreen newScreen) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 3b81b5c8cd..7b6cda17a2 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -138,7 +138,7 @@ namespace osu.Game protected RealmKeyBindingStore KeyBindingStore { get; private set; } - protected MenuCursorContainer MenuCursorContainer { get; private set; } + protected GlobalCursorDisplay GlobalCursorDisplay { get; private set; } protected MusicController MusicController { get; private set; } @@ -340,10 +340,10 @@ namespace osu.Game RelativeSizeAxes = Axes.Both, Child = CreateScalingContainer().WithChildren(new Drawable[] { - (MenuCursorContainer = new MenuCursorContainer + (GlobalCursorDisplay = new GlobalCursorDisplay { RelativeSizeAxes = Axes.Both - }).WithChild(content = new OsuTooltipContainer(MenuCursorContainer.Cursor) + }).WithChild(content = new OsuTooltipContainer(GlobalCursorDisplay.MenuCursor) { RelativeSizeAxes = Axes.Both }), diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 95cb02c477..f7f62d2af0 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -380,7 +380,7 @@ namespace osu.Game.Rulesets.UI // only show the cursor when within the playfield, by default. public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Playfield.ReceivePositionalInputAt(screenSpacePos); - CursorContainer IProvideCursor.Cursor => Playfield.Cursor; + CursorContainer IProvideCursor.MenuCursor => Playfield.Cursor; public override GameplayCursorContainer Cursor => Playfield.Cursor; diff --git a/osu.Game/Screens/Utility/LatencyArea.cs b/osu.Game/Screens/Utility/LatencyArea.cs index b7d45ba642..c8e0bf93a2 100644 --- a/osu.Game/Screens/Utility/LatencyArea.cs +++ b/osu.Game/Screens/Utility/LatencyArea.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Utility public readonly Bindable VisualMode = new Bindable(); - public CursorContainer? Cursor { get; private set; } + public CursorContainer? MenuCursor { get; private set; } public bool ProvidingUserCursor => IsActiveArea.Value; @@ -91,7 +91,7 @@ namespace osu.Game.Screens.Utility { RelativeSizeAxes = Axes.Both, }, - Cursor = new LatencyCursorContainer + MenuCursor = new LatencyCursorContainer { RelativeSizeAxes = Axes.Both, }, @@ -105,7 +105,7 @@ namespace osu.Game.Screens.Utility { RelativeSizeAxes = Axes.Both, }, - Cursor = new LatencyCursorContainer + MenuCursor = new LatencyCursorContainer { RelativeSizeAxes = Axes.Both, }, @@ -119,7 +119,7 @@ namespace osu.Game.Screens.Utility { RelativeSizeAxes = Axes.Both, }, - Cursor = new LatencyCursorContainer + MenuCursor = new LatencyCursorContainer { RelativeSizeAxes = Axes.Both, }, diff --git a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs index eccd2efa96..9082ca9c58 100644 --- a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs +++ b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs @@ -38,11 +38,11 @@ namespace osu.Game.Tests.Visual protected OsuManualInputManagerTestScene() { - MenuCursorContainer cursorContainer; + GlobalCursorDisplay cursorDisplay; - CompositeDrawable mainContent = cursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }; + CompositeDrawable mainContent = cursorDisplay = new GlobalCursorDisplay { RelativeSizeAxes = Axes.Both }; - cursorContainer.Child = content = new OsuTooltipContainer(cursorContainer.Cursor) + cursorDisplay.Child = content = new OsuTooltipContainer(cursorDisplay.MenuCursor) { RelativeSizeAxes = Axes.Both }; From 1b2158ff048da3e37c13f908eac533e4cefe2aca Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 26 Jul 2022 14:15:59 +0900 Subject: [PATCH 121/124] Remove unused method --- .../Compose/Components/Timeline/ZoomableScrollContainer.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 725f94e7db..7d51284f46 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -156,13 +156,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline setZoomTarget(zoomTarget + change * (maxZoom - minZoom) * zoom_change_sensitivity, focusPoint); } - protected void SetZoomImmediately(float value, float min, float max) - { - maxZoom = max; - minZoom = min; - CurrentZoom = zoomTarget = value; - } - private float zoomTarget = 1; private void setZoomTarget(float newZoom, float? focusPoint = null) From 59d3cc52c445694f2874ff91d4f8bcb9538b6430 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jul 2022 14:56:41 +0900 Subject: [PATCH 122/124] Avoid leaving left-over files after test run completes --- .../Database/BeatmapImporterTests.cs | 1 - osu.Game.Tests/Database/RealmLiveTests.cs | 30 ++++++++----------- osu.Game.Tests/Database/RealmTest.cs | 23 +++++--------- .../NonVisual/DataLoadTest.cs | 4 +-- 4 files changed, 23 insertions(+), 35 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 9ee88c0670..2e49aa7bdc 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -142,7 +142,6 @@ namespace osu.Game.Tests.Database { Task.Run(async () => { - // ReSharper disable once AccessToDisposedClosure var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz")); Assert.NotNull(beatmapSet); diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 3615cebe6a..d853e75db0 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -32,31 +32,29 @@ namespace osu.Game.Tests.Database [Test] public void TestAccessAfterStorageMigrate() { - RunTestWithRealm((realm, storage) => + using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target")) { - var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()); - - Live? liveBeatmap = null; - - realm.Run(r => + RunTestWithRealm((realm, storage) => { - r.Write(_ => r.Add(beatmap)); + var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()); - liveBeatmap = beatmap.ToLive(realm); - }); + Live? liveBeatmap = null; + + realm.Run(r => + { + r.Write(_ => r.Add(beatmap)); + + liveBeatmap = beatmap.ToLive(realm); + }); - using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target")) - { migratedStorage.DeleteDirectory(string.Empty); using (realm.BlockAllOperations("testing")) - { storage.Migrate(migratedStorage); - } Assert.IsFalse(liveBeatmap?.PerformRead(l => l.Hidden)); - } - }); + }); + } } [Test] @@ -341,14 +339,12 @@ namespace osu.Game.Tests.Database liveBeatmap.PerformRead(resolved => { // retrieval causes an implicit refresh. even changes that aren't related to the retrieval are fired at this point. - // ReSharper disable once AccessToDisposedClosure Assert.AreEqual(2, outerRealm.All().Count()); Assert.AreEqual(1, changesTriggered); // can access properties without a crash. Assert.IsFalse(resolved.Hidden); - // ReSharper disable once AccessToDisposedClosure outerRealm.Write(r => { // can use with the main context. diff --git a/osu.Game.Tests/Database/RealmTest.cs b/osu.Game.Tests/Database/RealmTest.cs index d6b3c1ff44..1b1878942b 100644 --- a/osu.Game.Tests/Database/RealmTest.cs +++ b/osu.Game.Tests/Database/RealmTest.cs @@ -4,11 +4,11 @@ using System; using System.Runtime.CompilerServices; using System.Threading.Tasks; +using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Extensions; using osu.Framework.Logging; using osu.Framework.Platform; -using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.IO; @@ -20,22 +20,15 @@ namespace osu.Game.Tests.Database [TestFixture] public abstract class RealmTest { - private static readonly TemporaryNativeStorage storage; - - static RealmTest() - { - storage = new TemporaryNativeStorage("realm-test"); - storage.DeleteDirectory(string.Empty); - } - - protected void RunTestWithRealm(Action testAction, [CallerMemberName] string caller = "") + protected void RunTestWithRealm([InstantHandle] Action testAction, [CallerMemberName] string caller = "") { using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller)) { host.Run(new RealmTestGame(() => { - // ReSharper disable once AccessToDisposedClosure - var testStorage = new OsuStorage(host, storage.GetStorageForDirectory(caller)); + var defaultStorage = host.Storage; + + var testStorage = new OsuStorage(host, defaultStorage); using (var realm = new RealmAccess(testStorage, OsuGameBase.CLIENT_DATABASE_FILENAME)) { @@ -58,7 +51,7 @@ namespace osu.Game.Tests.Database { host.Run(new RealmTestGame(async () => { - var testStorage = storage.GetStorageForDirectory(caller); + var testStorage = host.Storage; using (var realm = new RealmAccess(testStorage, OsuGameBase.CLIENT_DATABASE_FILENAME)) { @@ -116,7 +109,7 @@ namespace osu.Game.Tests.Database private class RealmTestGame : Framework.Game { - public RealmTestGame(Func work) + public RealmTestGame([InstantHandle] Func work) { // ReSharper disable once AsyncVoidLambda Scheduler.Add(async () => @@ -126,7 +119,7 @@ namespace osu.Game.Tests.Database }); } - public RealmTestGame(Action work) + public RealmTestGame([InstantHandle] Action work) { Scheduler.Add(() => { diff --git a/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs b/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs index ad776622be..df77b31191 100644 --- a/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs @@ -6,6 +6,7 @@ using System; using System.IO; using System.Threading.Tasks; +using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -27,7 +28,6 @@ namespace osu.Game.Tournament.Tests.NonVisual { var osu = new TestTournament(runOnLoadComplete: () => { - // ReSharper disable once AccessToDisposedClosure var storage = host.Storage.GetStorageForDirectory(Path.Combine("tournaments", "default")); using (var stream = storage.CreateFileSafely("bracket.json")) @@ -85,7 +85,7 @@ namespace osu.Game.Tournament.Tests.NonVisual public new Task BracketLoadTask => base.BracketLoadTask; - public TestTournament(bool resetRuleset = false, Action runOnLoadComplete = null) + public TestTournament(bool resetRuleset = false, [InstantHandle] Action runOnLoadComplete = null) { this.resetRuleset = resetRuleset; this.runOnLoadComplete = runOnLoadComplete; From ac553d22ea076a161743a0fefdc67099b37b0d4b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jul 2022 15:05:01 +0900 Subject: [PATCH 123/124] Fix left over resource file which isn't actually imported --- osu.Game.Tests/Database/BeatmapImporterTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 2e49aa7bdc..af285f82b1 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -310,6 +310,7 @@ namespace osu.Game.Tests.Database } finally { + File.Delete(temp); Directory.Delete(extractedFolder, true); } }); From 99bdf417175c8fa856fca0e9dba78babbf7005c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jul 2022 15:12:22 +0900 Subject: [PATCH 124/124] Avoid potential realm fetch after disposal in `StatisticsPanel` As seen at https://github.com/ppy/osu/runs/7513799859?check_suite_focus=true. --- .../Screens/Ranking/Statistics/StatisticsPanel.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 435162e057..79d7b99e51 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -11,6 +11,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; @@ -90,18 +91,16 @@ namespace osu.Game.Screens.Ranking.Statistics spinner.Show(); var localCancellationSource = loadCancellation = new CancellationTokenSource(); - IBeatmap playableBeatmap = null; + + var workingBeatmap = beatmapManager.GetWorkingBeatmap(newScore.BeatmapInfo); // Todo: The placement of this is temporary. Eventually we'll both generate the playable beatmap _and_ run through it in a background task to generate the hit events. - Task.Run(() => - { - playableBeatmap = beatmapManager.GetWorkingBeatmap(newScore.BeatmapInfo).GetPlayableBeatmap(newScore.Ruleset, newScore.Mods); - }, loadCancellation.Token).ContinueWith(_ => Schedule(() => + Task.Run(() => workingBeatmap.GetPlayableBeatmap(newScore.Ruleset, newScore.Mods), loadCancellation.Token).ContinueWith(task => Schedule(() => { bool hitEventsAvailable = newScore.HitEvents.Count != 0; Container container; - var statisticRows = newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap); + var statisticRows = newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, task.GetResultSafely()); if (!hitEventsAvailable && statisticRows.SelectMany(r => r.Columns).All(c => c.RequiresHitEvents)) {