diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 4fa18f53a7..e0a1533c4b 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.UI /// Displays an interactive ruleset gameplay instance. /// /// The type of HitObject contained by this DrawableRuleset. - public abstract partial class DrawableRuleset : DrawableRuleset, IProvideCursor, ICanAttachHUDPieces + public abstract partial class DrawableRuleset : DrawableRuleset, IProvideCursor, IKeybindingEventsEmitter where TObject : HitObject { public override event Action NewResult; @@ -327,8 +327,8 @@ namespace osu.Game.Rulesets.UI /// The representing . public abstract DrawableHitObject CreateDrawableRepresentation(TObject h); - public void Attach(IAttachableSkinComponent skinComponent) => - (KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(skinComponent); + public void Attach(IKeybindingListener skinComponent) => + (KeyBindingInputManager as IKeybindingEventsEmitter)?.Attach(skinComponent); /// /// Creates a key conversion input manager. An exception will be thrown if a valid is not returned. diff --git a/osu.Game/Rulesets/UI/IKeybindingListener.cs b/osu.Game/Rulesets/UI/IKeybindingListener.cs new file mode 100644 index 0000000000..f38ce8643e --- /dev/null +++ b/osu.Game/Rulesets/UI/IKeybindingListener.cs @@ -0,0 +1,50 @@ +// 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.Collections.Generic; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; + +namespace osu.Game.Rulesets.UI +{ + /// + /// Listens to events emitted by an . + /// Alternative to for classes that need to not depend on type parameters. + /// + public interface IKeybindingListener + { + /// + /// This class or a member of this class can already handle keybindings. + /// Signals to the that and + /// don't necessarily need to be called. + /// + /// + /// This is usually true for s and s that need to + /// pass s events to children that can already handle them. + /// + public bool CanHandleKeybindings { get; } + + /// + /// Prepares this class to receive events. + /// + /// The list of possible actions that can occur. + /// The type actions, commonly enums. + public void Setup(IEnumerable actions) where T : struct; + + /// + /// Called when an action is pressed. + /// + /// The event containing information about the pressed action. + /// The type of binding, commonly enums. + public void OnPressed(KeyBindingPressEvent action) where T : struct; + + /// + /// Called when an action is released. + /// + /// The event containing information about the released action. + /// The type of binding, commonly enums. + public void OnReleased(KeyBindingReleaseEvent action) where T : struct; + } +} diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index d3bc381f72..44c1f00cf7 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -19,13 +19,11 @@ using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.Input.Handlers; using osu.Game.Rulesets.Scoring; -using osu.Game.Screens.Play.HUD; -using osu.Game.Screens.Play.HUD.ClicksPerSecond; using static osu.Game.Input.Handlers.ReplayInputHandler; namespace osu.Game.Rulesets.UI { - public abstract partial class RulesetInputManager : PassThroughInputManager, ICanAttachHUDPieces, IHasReplayHandler, IHasRecordingHandler + public abstract partial class RulesetInputManager : PassThroughInputManager, IKeybindingEventsEmitter, IHasReplayHandler, IHasRecordingHandler where T : struct { protected override bool AllowRightClickFromLongTouch => false; @@ -66,6 +64,7 @@ namespace osu.Game.Rulesets.UI InternalChild = KeyBindingContainer = CreateKeyBindingContainer(ruleset, variant, unique) .WithChild(content = new Container { RelativeSizeAxes = Axes.Both }); + KeyBindingContainer.Add(actionListener = new ActionListener()); } [BackgroundDependencyLoader(true)] @@ -160,63 +159,47 @@ namespace osu.Game.Rulesets.UI #region Component attachement - public void Attach(IAttachableSkinComponent skinComponent) + private readonly ActionListener actionListener; + + public void Attach(IKeybindingListener skinComponent) { - switch (skinComponent) - { - case KeyCounterController keyCounterDisplay: - attachKeyCounter(keyCounterDisplay); - break; - - case ClicksPerSecondCalculator clicksPerSecondCalculator: - attachClicksPerSecond(clicksPerSecondCalculator); - break; - } - } - - #endregion - - #region Key Counter Attachment - - private void attachKeyCounter(KeyCounterController keyCounter) - { - KeyBindingContainer.Add(keyCounter); - - keyCounter.AddRange(KeyBindingContainer.DefaultKeyBindings + skinComponent.Setup(KeyBindingContainer.DefaultKeyBindings .Select(b => b.GetAction()) .Distinct() - .OrderBy(action => action) - .Select(action => new KeyCounterActionTrigger(action))); - } + .OrderBy(a => a)); - #endregion + if (skinComponent.CanHandleKeybindings && skinComponent is Drawable component) + { + try + { + KeyBindingContainer.Add(component); + return; + } + catch (Exception) + { + return; + } + } - #region Keys per second Counter Attachment - - private void attachClicksPerSecond(ClicksPerSecondCalculator calculator) - { - var listener = new ActionListener(calculator); - - KeyBindingContainer.Add(listener); + actionListener.OnPressedEvent += skinComponent.OnPressed; + actionListener.OnReleasedEvent += skinComponent.OnReleased; } private partial class ActionListener : Component, IKeyBindingHandler { - private readonly ClicksPerSecondCalculator calculator; + public event Action> OnPressedEvent; - public ActionListener(ClicksPerSecondCalculator calculator) - { - this.calculator = calculator; - } + public event Action> OnReleasedEvent; public bool OnPressed(KeyBindingPressEvent e) { - calculator.AddInputTimestamp(); + OnPressedEvent?.Invoke(e); return false; } public void OnReleased(KeyBindingReleaseEvent e) { + OnReleasedEvent?.Invoke(e); } } @@ -257,16 +240,11 @@ namespace osu.Game.Rulesets.UI } /// - /// Supports attaching various HUD pieces. - /// Keys will be populated automatically and a receptor will be injected inside. + /// Sends events to a /// - public interface ICanAttachHUDPieces - { - void Attach(IAttachableSkinComponent component); - } - - public interface IAttachableSkinComponent + public interface IKeybindingEventsEmitter { + void Attach(IKeybindingListener component); } public class RulesetInputManagerInputState : InputState diff --git a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs index 3e55e11f1c..e0e93cb66e 100644 --- a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs @@ -4,11 +4,12 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Input.Events; using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Play.HUD.ClicksPerSecond { - public partial class ClicksPerSecondCalculator : Component, IAttachableSkinComponent + public partial class ClicksPerSecondCalculator : Component, IKeybindingListener { private readonly List timestamps = new List(); @@ -53,5 +54,21 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond Value = count; } + + #region IKeybindingListener + + bool IKeybindingListener.CanHandleKeybindings => false; + + void IKeybindingListener.Setup(IEnumerable actions) + { + } + + void IKeybindingListener.OnPressed(KeyBindingPressEvent action) => AddInputTimestamp(); + + void IKeybindingListener.OnReleased(KeyBindingReleaseEvent action) + { + } + + #endregion } } diff --git a/osu.Game/Screens/Play/HUD/KeyCounterController.cs b/osu.Game/Screens/Play/HUD/KeyCounterController.cs index 0fa02afbb4..2e678e55fc 100644 --- a/osu.Game/Screens/Play/HUD/KeyCounterController.cs +++ b/osu.Game/Screens/Play/HUD/KeyCounterController.cs @@ -3,14 +3,16 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Play.HUD { - public partial class KeyCounterController : CompositeComponent, IAttachableSkinComponent + public partial class KeyCounterController : CompositeComponent, IKeybindingListener { public readonly Bindable IsCounting = new BindableBool(true); @@ -36,5 +38,22 @@ namespace osu.Game.Screens.Play.HUD public override bool HandleNonPositionalInput => true; public override bool HandlePositionalInput => true; + + #region IKeybindingListener + + bool IKeybindingListener.CanHandleKeybindings => true; + + void IKeybindingListener.Setup(IEnumerable actions) + => AddRange(actions.Select(a => new KeyCounterActionTrigger(a))); + + void IKeybindingListener.OnPressed(KeyBindingPressEvent action) + { + } + + void IKeybindingListener.OnReleased(KeyBindingReleaseEvent action) + { + } + + #endregion } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 21636ac04c..b74b5d835a 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -10,6 +10,7 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.EnumExtensions; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; @@ -102,6 +103,8 @@ namespace osu.Game.Screens.Play private readonly List hideTargets; + private readonly IEnumerable actionInjectionCandidates; + public HUDOverlay(DrawableRuleset drawableRuleset, IReadOnlyList mods, bool alwaysShowLeaderboard = true) { Drawable rulesetComponents; @@ -163,6 +166,8 @@ namespace osu.Game.Screens.Play hideTargets = new List { mainComponents, rulesetComponents, topRightElements }; + actionInjectionCandidates = new IKeybindingListener[] { clicksPerSecondCalculator, KeyCounter }; + if (!alwaysShowLeaderboard) hideTargets.Add(LeaderboardFlow); } @@ -319,11 +324,8 @@ namespace osu.Game.Screens.Play protected virtual void BindDrawableRuleset(DrawableRuleset drawableRuleset) { - if (drawableRuleset is ICanAttachHUDPieces attachTarget) - { - attachTarget.Attach(KeyCounter); - attachTarget.Attach(clicksPerSecondCalculator); - } + if (drawableRuleset is IKeybindingEventsEmitter attachTarget) + actionInjectionCandidates.ForEach(attachTarget.Attach); replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); }