1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-28 20:32:55 +08:00

refactor: tidy up attachement flow

TODO: find better naming and improve XMLDocs
This commit is contained in:
tsrk 2023-06-18 22:57:21 +02:00
parent 141f9efad5
commit f83a4f4952
No known key found for this signature in database
GPG Key ID: EBD46BB3049B56D6
6 changed files with 126 additions and 60 deletions

View File

@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.UI
/// Displays an interactive ruleset gameplay instance. /// Displays an interactive ruleset gameplay instance.
/// </summary> /// </summary>
/// <typeparam name="TObject">The type of HitObject contained by this DrawableRuleset.</typeparam> /// <typeparam name="TObject">The type of HitObject contained by this DrawableRuleset.</typeparam>
public abstract partial class DrawableRuleset<TObject> : DrawableRuleset, IProvideCursor, ICanAttachHUDPieces public abstract partial class DrawableRuleset<TObject> : DrawableRuleset, IProvideCursor, IKeybindingEventsEmitter
where TObject : HitObject where TObject : HitObject
{ {
public override event Action<JudgementResult> NewResult; public override event Action<JudgementResult> NewResult;
@ -327,8 +327,8 @@ namespace osu.Game.Rulesets.UI
/// <returns>The representing <see cref="DrawableHitObject{TObject}"/>.</returns> /// <returns>The representing <see cref="DrawableHitObject{TObject}"/>.</returns>
public abstract DrawableHitObject<TObject> CreateDrawableRepresentation(TObject h); public abstract DrawableHitObject<TObject> CreateDrawableRepresentation(TObject h);
public void Attach(IAttachableSkinComponent skinComponent) => public void Attach(IKeybindingListener skinComponent) =>
(KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(skinComponent); (KeyBindingInputManager as IKeybindingEventsEmitter)?.Attach(skinComponent);
/// <summary> /// <summary>
/// Creates a key conversion input manager. An exception will be thrown if a valid <see cref="RulesetInputManager{T}"/> is not returned. /// Creates a key conversion input manager. An exception will be thrown if a valid <see cref="RulesetInputManager{T}"/> is not returned.

View File

@ -0,0 +1,50 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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
{
/// <summary>
/// Listens to <see cref="IKeyBinding"/> events emitted by an <see cref="IKeybindingEventsEmitter"/>.
/// Alternative to <see cref="IKeyBindingHandler{T}"/> for classes that need to not depend on type parameters.
/// </summary>
public interface IKeybindingListener
{
/// <summary>
/// This class or a member of this class can already handle keybindings.
/// Signals to the <see cref="IKeybindingEventsEmitter"/> that <see cref="OnPressed{T}"/> and <see cref="OnReleased{T}"/>
/// don't necessarily need to be called.
/// </summary>
/// <remarks>
/// This is usually true for <see cref="CompositeDrawable"/>s and <see cref="CompositeComponent"/>s that need to
/// pass <see cref="IKeyBinding"/>s events to children that can already handle them.
/// </remarks>
public bool CanHandleKeybindings { get; }
/// <summary>
/// Prepares this class to receive events.
/// </summary>
/// <param name="actions">The list of possible actions that can occur.</param>
/// <typeparam name="T">The type actions, commonly enums.</typeparam>
public void Setup<T>(IEnumerable<T> actions) where T : struct;
/// <summary>
/// Called when an action is pressed.
/// </summary>
/// <param name="action">The event containing information about the pressed action.</param>
/// <typeparam name="T">The type of binding, commonly enums.</typeparam>
public void OnPressed<T>(KeyBindingPressEvent<T> action) where T : struct;
/// <summary>
/// Called when an action is released.
/// </summary>
/// <param name="action">The event containing information about the released action.</param>
/// <typeparam name="T">The type of binding, commonly enums.</typeparam>
public void OnReleased<T>(KeyBindingReleaseEvent<T> action) where T : struct;
}
}

View File

@ -19,13 +19,11 @@ using osu.Game.Input;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osu.Game.Input.Handlers; using osu.Game.Input.Handlers;
using osu.Game.Rulesets.Scoring; 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; using static osu.Game.Input.Handlers.ReplayInputHandler;
namespace osu.Game.Rulesets.UI namespace osu.Game.Rulesets.UI
{ {
public abstract partial class RulesetInputManager<T> : PassThroughInputManager, ICanAttachHUDPieces, IHasReplayHandler, IHasRecordingHandler public abstract partial class RulesetInputManager<T> : PassThroughInputManager, IKeybindingEventsEmitter, IHasReplayHandler, IHasRecordingHandler
where T : struct where T : struct
{ {
protected override bool AllowRightClickFromLongTouch => false; protected override bool AllowRightClickFromLongTouch => false;
@ -66,6 +64,7 @@ namespace osu.Game.Rulesets.UI
InternalChild = KeyBindingContainer = InternalChild = KeyBindingContainer =
CreateKeyBindingContainer(ruleset, variant, unique) CreateKeyBindingContainer(ruleset, variant, unique)
.WithChild(content = new Container { RelativeSizeAxes = Axes.Both }); .WithChild(content = new Container { RelativeSizeAxes = Axes.Both });
KeyBindingContainer.Add(actionListener = new ActionListener());
} }
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
@ -160,63 +159,47 @@ namespace osu.Game.Rulesets.UI
#region Component attachement #region Component attachement
public void Attach(IAttachableSkinComponent skinComponent) private readonly ActionListener actionListener;
public void Attach(IKeybindingListener skinComponent)
{ {
switch (skinComponent) skinComponent.Setup(KeyBindingContainer.DefaultKeyBindings
{
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
.Select(b => b.GetAction<T>()) .Select(b => b.GetAction<T>())
.Distinct() .Distinct()
.OrderBy(action => action) .OrderBy(a => a));
.Select(action => new KeyCounterActionTrigger<T>(action)));
if (skinComponent.CanHandleKeybindings && skinComponent is Drawable component)
{
try
{
KeyBindingContainer.Add(component);
return;
}
catch (Exception)
{
return;
}
} }
#endregion actionListener.OnPressedEvent += skinComponent.OnPressed;
actionListener.OnReleasedEvent += skinComponent.OnReleased;
#region Keys per second Counter Attachment
private void attachClicksPerSecond(ClicksPerSecondCalculator calculator)
{
var listener = new ActionListener(calculator);
KeyBindingContainer.Add(listener);
} }
private partial class ActionListener : Component, IKeyBindingHandler<T> private partial class ActionListener : Component, IKeyBindingHandler<T>
{ {
private readonly ClicksPerSecondCalculator calculator; public event Action<KeyBindingPressEvent<T>> OnPressedEvent;
public ActionListener(ClicksPerSecondCalculator calculator) public event Action<KeyBindingReleaseEvent<T>> OnReleasedEvent;
{
this.calculator = calculator;
}
public bool OnPressed(KeyBindingPressEvent<T> e) public bool OnPressed(KeyBindingPressEvent<T> e)
{ {
calculator.AddInputTimestamp(); OnPressedEvent?.Invoke(e);
return false; return false;
} }
public void OnReleased(KeyBindingReleaseEvent<T> e) public void OnReleased(KeyBindingReleaseEvent<T> e)
{ {
OnReleasedEvent?.Invoke(e);
} }
} }
@ -257,16 +240,11 @@ namespace osu.Game.Rulesets.UI
} }
/// <summary> /// <summary>
/// Supports attaching various HUD pieces. /// Sends <see cref="IKeyBinding"/> events to a <see cref="IKeybindingListener"/>
/// Keys will be populated automatically and a receptor will be injected inside.
/// </summary> /// </summary>
public interface ICanAttachHUDPieces public interface IKeybindingEventsEmitter
{
void Attach(IAttachableSkinComponent component);
}
public interface IAttachableSkinComponent
{ {
void Attach(IKeybindingListener component);
} }
public class RulesetInputManagerInputState<T> : InputState public class RulesetInputManagerInputState<T> : InputState

View File

@ -4,11 +4,12 @@
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
namespace osu.Game.Screens.Play.HUD.ClicksPerSecond namespace osu.Game.Screens.Play.HUD.ClicksPerSecond
{ {
public partial class ClicksPerSecondCalculator : Component, IAttachableSkinComponent public partial class ClicksPerSecondCalculator : Component, IKeybindingListener
{ {
private readonly List<double> timestamps = new List<double>(); private readonly List<double> timestamps = new List<double>();
@ -53,5 +54,21 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond
Value = count; Value = count;
} }
#region IKeybindingListener
bool IKeybindingListener.CanHandleKeybindings => false;
void IKeybindingListener.Setup<T>(IEnumerable<T> actions)
{
}
void IKeybindingListener.OnPressed<T>(KeyBindingPressEvent<T> action) => AddInputTimestamp();
void IKeybindingListener.OnReleased<T>(KeyBindingReleaseEvent<T> action)
{
}
#endregion
} }
} }

View File

@ -3,14 +3,16 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
namespace osu.Game.Screens.Play.HUD namespace osu.Game.Screens.Play.HUD
{ {
public partial class KeyCounterController : CompositeComponent, IAttachableSkinComponent public partial class KeyCounterController : CompositeComponent, IKeybindingListener
{ {
public readonly Bindable<bool> IsCounting = new BindableBool(true); public readonly Bindable<bool> IsCounting = new BindableBool(true);
@ -36,5 +38,22 @@ namespace osu.Game.Screens.Play.HUD
public override bool HandleNonPositionalInput => true; public override bool HandleNonPositionalInput => true;
public override bool HandlePositionalInput => true; public override bool HandlePositionalInput => true;
#region IKeybindingListener
bool IKeybindingListener.CanHandleKeybindings => true;
void IKeybindingListener.Setup<T>(IEnumerable<T> actions)
=> AddRange(actions.Select(a => new KeyCounterActionTrigger<T>(a)));
void IKeybindingListener.OnPressed<T>(KeyBindingPressEvent<T> action)
{
}
void IKeybindingListener.OnReleased<T>(KeyBindingReleaseEvent<T> action)
{
}
#endregion
} }
} }

View File

@ -10,6 +10,7 @@ using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
@ -102,6 +103,8 @@ namespace osu.Game.Screens.Play
private readonly List<Drawable> hideTargets; private readonly List<Drawable> hideTargets;
private readonly IEnumerable<IKeybindingListener> actionInjectionCandidates;
public HUDOverlay(DrawableRuleset drawableRuleset, IReadOnlyList<Mod> mods, bool alwaysShowLeaderboard = true) public HUDOverlay(DrawableRuleset drawableRuleset, IReadOnlyList<Mod> mods, bool alwaysShowLeaderboard = true)
{ {
Drawable rulesetComponents; Drawable rulesetComponents;
@ -163,6 +166,8 @@ namespace osu.Game.Screens.Play
hideTargets = new List<Drawable> { mainComponents, rulesetComponents, topRightElements }; hideTargets = new List<Drawable> { mainComponents, rulesetComponents, topRightElements };
actionInjectionCandidates = new IKeybindingListener[] { clicksPerSecondCalculator, KeyCounter };
if (!alwaysShowLeaderboard) if (!alwaysShowLeaderboard)
hideTargets.Add(LeaderboardFlow); hideTargets.Add(LeaderboardFlow);
} }
@ -319,11 +324,8 @@ namespace osu.Game.Screens.Play
protected virtual void BindDrawableRuleset(DrawableRuleset drawableRuleset) protected virtual void BindDrawableRuleset(DrawableRuleset drawableRuleset)
{ {
if (drawableRuleset is ICanAttachHUDPieces attachTarget) if (drawableRuleset is IKeybindingEventsEmitter attachTarget)
{ actionInjectionCandidates.ForEach(attachTarget.Attach);
attachTarget.Attach(KeyCounter);
attachTarget.Attach(clicksPerSecondCalculator);
}
replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); replayLoaded.BindTo(drawableRuleset.HasReplayLoaded);
} }