diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index 4b6e1f089f..903cd178b7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses.Value > 2)); seekTo(referenceBeatmap.Breaks[0].StartTime); - AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting); + AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting.Value); AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType().Single().AccuracyDisplay.Current.Value == 1); AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000)); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index af79650d29..a586d798f5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -267,7 +267,7 @@ namespace osu.Game.Tests.Visual.Gameplay hudOverlay = new HUDOverlay(null, Array.Empty()); // Add any key just to display the key counter visually. - hudOverlay.KeyCounter.Add(hudOverlay.KeyCounter.CreateKeyCounter(new KeyCounterKeyboardTrigger(Key.Space))); + hudOverlay.KeyCounter.AddTrigger(new KeyCounterKeyboardTrigger(Key.Space)); scoreProcessor.Combo.Value = 1; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs index 9eeee800d9..5405274cd0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Add random", () => { Key key = (Key)((int)Key.A + RNG.Next(26)); - kc.Add(kc.CreateKeyCounter(new KeyCounterKeyboardTrigger(key))); + kc.AddTrigger(new KeyCounterKeyboardTrigger(key)); }); Key testKey = ((KeyCounterKeyboardTrigger)kc.Children.First().Trigger).Key; @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 1); addPressKeyStep(); AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 2); - AddStep("Disable counting", () => testCounter.IsCounting = false); + AddStep("Disable counting", () => testCounter.IsCounting.Value = false); addPressKeyStep(); AddAssert($"Check {testKey} count has not changed", () => testCounter.CountPresses.Value == 2); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs index 432ff2fc7e..e4f257582d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Gameplay }; // Add any key just to display the key counter visually. - hudOverlay.KeyCounter.Add(hudOverlay.KeyCounter.CreateKeyCounter(new KeyCounterKeyboardTrigger(Key.Space))); + hudOverlay.KeyCounter.AddTrigger(new KeyCounterKeyboardTrigger(Key.Space)); scoreProcessor.Combo.Value = 1; return new Container diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index 24de29fa03..9848894f84 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -88,7 +88,7 @@ namespace osu.Game.Tests.Visual.Gameplay hudOverlay = new HUDOverlay(null, Array.Empty()); // Add any key just to display the key counter visually. - hudOverlay.KeyCounter.Add(hudOverlay.KeyCounter.CreateKeyCounter(new KeyCounterKeyboardTrigger(Key.Space))); + hudOverlay.KeyCounter.AddTrigger(new KeyCounterKeyboardTrigger(Key.Space)); action?.Invoke(hudOverlay); diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 6a38fa4824..32b2a19e21 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -162,11 +162,11 @@ namespace osu.Game.Rulesets.UI KeyBindingContainer.Add(receptor); keyCounter.SetReceptor(receptor); - keyCounter.AddRange(KeyBindingContainer.DefaultKeyBindings - .Select(b => b.GetAction()) - .Distinct() - .OrderBy(action => action) - .Select(action => keyCounter.CreateKeyCounter(new KeyCounterActionTrigger(action)))); + keyCounter.AddTriggerRange(KeyBindingContainer.DefaultKeyBindings + .Select(b => b.GetAction()) + .Distinct() + .OrderBy(action => action) + .Select(action => new KeyCounterActionTrigger(action))); } private partial class ActionReceptor : KeyCounterDisplay.Receptor, IKeyBindingHandler diff --git a/osu.Game/Screens/Play/DefaultKeyCounterDisplay.cs b/osu.Game/Screens/Play/DefaultKeyCounterDisplay.cs index fbf1b87395..367eb483a0 100644 --- a/osu.Game/Screens/Play/DefaultKeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/DefaultKeyCounterDisplay.cs @@ -13,7 +13,9 @@ namespace osu.Game.Screens.Play private const int duration = 100; private const double key_fade_time = 80; - protected override Container Content => KeyFlow; + private readonly FillFlowContainer keyFlow = new FillFlowContainer(); + + protected override Container Content => keyFlow; public new IReadOnlyList Children { @@ -22,12 +24,13 @@ namespace osu.Game.Screens.Play } public DefaultKeyCounterDisplay() + : base(typeof(DefaultKeyCounter)) { - KeyFlow.Direction = FillDirection.Horizontal; - KeyFlow.AutoSizeAxes = Axes.Both; - KeyFlow.Alpha = 0; + keyFlow.Direction = FillDirection.Horizontal; + keyFlow.AutoSizeAxes = Axes.Both; + keyFlow.Alpha = 0; - InternalChild = KeyFlow; + InternalChild = keyFlow; } protected override void Update() @@ -36,12 +39,22 @@ namespace osu.Game.Screens.Play // Don't use autosize as it will shrink to zero when KeyFlow is hidden. // In turn this can cause the display to be masked off screen and never become visible again. - Size = KeyFlow.Size; + Size = keyFlow.Size; + } + + public override void AddTrigger(KeyCounter.InputTrigger trigger) + { + DefaultKeyCounter key = new DefaultKeyCounter(trigger); + Add(key); + key.FadeTime = key_fade_time; + key.KeyDownTextColor = KeyDownTextColor; + key.KeyUpTextColor = KeyUpTextColor; } public override void Add(KeyCounter key) { base.Add(key); + DefaultKeyCounter defaultKey = (DefaultKeyCounter)key; defaultKey.FadeTime = key_fade_time; @@ -51,7 +64,7 @@ namespace osu.Game.Screens.Play protected override void UpdateVisibility() => // Isolate changing visibility of the key counters from fading this component. - KeyFlow.FadeTo(AlwaysVisible.Value || ConfigVisibility.Value ? 1 : 0, duration); + keyFlow.FadeTo(AlwaysVisible.Value || ConfigVisibility.Value ? 1 : 0, duration); private Color4 keyDownTextColor = Color4.DarkGray; diff --git a/osu.Game/Screens/Play/KeyCounter.cs b/osu.Game/Screens/Play/KeyCounter.cs index 4bad6920e3..26bb6f1a22 100644 --- a/osu.Game/Screens/Play/KeyCounter.cs +++ b/osu.Game/Screens/Play/KeyCounter.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Play MinValue = 0 }; - public bool IsCounting { get; set; } = true; + public Bindable IsCounting { get; } = new BindableBool(true); public IBindable CountPresses => countPresses; @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Play private void increment() { - if (!IsCounting) + if (!IsCounting.Value) return; countPresses.Value++; @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Play private void decrement() { - if (!IsCounting) + if (!IsCounting.Value) return; countPresses.Value--; diff --git a/osu.Game/Screens/Play/KeyCounterDisplay.cs b/osu.Game/Screens/Play/KeyCounterDisplay.cs index d2b50ff73d..d1fbfe166d 100644 --- a/osu.Game/Screens/Play/KeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/KeyCounterDisplay.cs @@ -2,9 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; 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.Input.Events; @@ -15,22 +17,58 @@ namespace osu.Game.Screens.Play { public abstract partial class KeyCounterDisplay : Container { - protected readonly Bindable ConfigVisibility = new Bindable(); - - protected readonly FillFlowContainer KeyFlow = new FillFlowContainer(); - - protected override Container Content => KeyFlow; - /// /// Whether the key counter should be visible regardless of the configuration value. /// This is true by default, but can be changed. /// - public readonly Bindable AlwaysVisible = new Bindable(true); + public Bindable AlwaysVisible { get; } = new Bindable(true); + + public Bindable IsCounting { get; } = new BindableBool(true); + + public IReadOnlyList Triggers + { + get => Children.Select(c => c.Trigger).ToArray(); + set + { + Clear(); + value.ForEach(AddTrigger); + } + } + + protected readonly Bindable ConfigVisibility = new Bindable(); + + protected abstract void UpdateVisibility(); + + private Receptor? receptor; + + private readonly Type[] acceptedTypes; + + protected KeyCounterDisplay(params Type[] acceptedTypes) + { + this.acceptedTypes = acceptedTypes; + } + + public void SetReceptor(Receptor receptor) + { + if (this.receptor != null) + throw new InvalidOperationException("Cannot set a new receptor when one is already active"); + + this.receptor = receptor; + } + + public abstract void AddTrigger(KeyCounter.InputTrigger trigger); + + public void AddTriggerRange(IEnumerable triggers) => triggers.ForEach(AddTrigger); + + private bool checkType(KeyCounter key) => acceptedTypes.Length == 0 || acceptedTypes.Any(t => t.IsInstanceOfType(key)); public override void Add(KeyCounter key) { + if (!checkType(key)) + throw new InvalidOperationException($"{key.GetType()} is not a supported counter type. (hint: you may want to use {nameof(AddTrigger)} instead.)"); + base.Add(key); - key.IsCounting = IsCounting; + key.IsCounting.BindTo(IsCounting); } [BackgroundDependencyLoader] @@ -47,38 +85,10 @@ namespace osu.Game.Screens.Play ConfigVisibility.BindValueChanged(_ => UpdateVisibility(), true); } - private bool isCounting = true; - - public bool IsCounting - { - get => isCounting; - set - { - if (value == isCounting) return; - - isCounting = value; - foreach (var child in Children) - child.IsCounting = value; - } - } - - protected abstract void UpdateVisibility(); - public override bool HandleNonPositionalInput => receptor == null; + public override bool HandlePositionalInput => receptor == null; - private Receptor? receptor; - - public void SetReceptor(Receptor receptor) - { - if (this.receptor != null) - throw new InvalidOperationException("Cannot set a new receptor when one is already active"); - - this.receptor = receptor; - } - - public virtual KeyCounter CreateKeyCounter(KeyCounter.InputTrigger trigger) => new DefaultKeyCounter(trigger); - public partial class Receptor : Drawable { protected readonly KeyCounterDisplay Target; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 6dc4854e80..b141848a21 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -440,8 +440,11 @@ namespace osu.Game.Screens.Play }, KeyCounter = { + IsCounting = + { + Value = false + }, AlwaysVisible = { BindTarget = DrawableRuleset.HasReplayLoaded }, - IsCounting = false }, Anchor = Anchor.Centre, Origin = Anchor.Centre @@ -481,7 +484,7 @@ namespace osu.Game.Screens.Play { updateGameplayState(); updatePauseOnFocusLostState(); - HUDOverlay.KeyCounter.IsCounting = !isBreakTime.NewValue; + HUDOverlay.KeyCounter.IsCounting.Value = !isBreakTime.NewValue; } private void updateGameplayState()