2019-01-24 16:43:03 +08:00
// 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.
2018-04-13 17:19:50 +08:00
2020-03-23 18:03:42 +08:00
using System ;
2017-08-21 11:31:21 +08:00
using System.Linq ;
2017-08-24 14:23:17 +08:00
using osu.Framework.Allocation ;
2019-02-21 18:04:31 +08:00
using osu.Framework.Bindables ;
2017-12-06 22:03:31 +08:00
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
2017-08-24 14:23:17 +08:00
using osu.Framework.Input ;
2017-08-21 11:31:21 +08:00
using osu.Framework.Input.Bindings ;
2018-10-02 11:44:14 +08:00
using osu.Framework.Input.Events ;
2024-07-03 16:18:45 +08:00
using osu.Framework.Input.StateChanges ;
2018-09-18 16:05:26 +08:00
using osu.Framework.Input.StateChanges.Events ;
2018-07-21 10:38:28 +08:00
using osu.Framework.Input.States ;
2017-08-24 14:23:17 +08:00
using osu.Game.Configuration ;
2021-05-24 16:23:09 +08:00
using osu.Game.Input ;
2017-08-21 11:31:21 +08:00
using osu.Game.Input.Bindings ;
2017-08-24 14:23:17 +08:00
using osu.Game.Input.Handlers ;
2022-01-31 17:54:23 +08:00
using osu.Game.Rulesets.Scoring ;
2023-06-25 21:04:39 +08:00
using osu.Game.Screens.Play.HUD ;
using osu.Game.Screens.Play.HUD.ClicksPerSecond ;
2018-06-11 22:00:26 +08:00
using static osu . Game . Input . Handlers . ReplayInputHandler ;
2018-04-13 17:19:50 +08:00
2017-08-21 11:31:21 +08:00
namespace osu.Game.Rulesets.UI
{
2023-06-25 21:04:39 +08:00
public abstract partial class RulesetInputManager < T > : PassThroughInputManager , ICanAttachHUDPieces , IHasReplayHandler , IHasRecordingHandler
2017-08-21 11:31:21 +08:00
where T : struct
{
2023-05-13 20:12:21 +08:00
protected override bool AllowRightClickFromLongTouch = > false ;
2022-01-31 15:47:20 +08:00
public readonly KeyBindingContainer < T > KeyBindingContainer ;
2024-07-03 15:33:48 +08:00
[Resolved]
private ScoreProcessor ? scoreProcessor { get ; set ; }
2022-01-31 17:54:23 +08:00
2024-07-03 15:33:48 +08:00
private ReplayRecorder ? recorder ;
2020-03-23 18:03:42 +08:00
2024-07-03 15:33:48 +08:00
public ReplayRecorder ? Recorder
2020-03-23 18:03:42 +08:00
{
set
{
2023-02-14 15:55:35 +08:00
if ( value = = recorder )
return ;
2021-06-04 00:59:56 +08:00
if ( value ! = null & & recorder ! = null )
2020-03-23 18:03:42 +08:00
throw new InvalidOperationException ( "Cannot attach more than one recorder" ) ;
2021-06-04 00:59:56 +08:00
recorder ? . Expire ( ) ;
2020-03-23 18:03:42 +08:00
recorder = value ;
2021-06-04 00:59:56 +08:00
if ( recorder ! = null )
KeyBindingContainer . Add ( recorder ) ;
2020-03-23 18:03:42 +08:00
}
}
2020-07-01 17:54:11 +08:00
protected override InputState CreateInitialState ( ) = > new RulesetInputManagerInputState < T > ( base . CreateInitialState ( ) ) ;
2018-06-11 22:00:26 +08:00
2019-03-26 10:28:43 +08:00
protected override Container < Drawable > Content = > content ;
private readonly Container content ;
2018-04-13 17:19:50 +08:00
2017-12-06 22:03:31 +08:00
protected RulesetInputManager ( RulesetInfo ruleset , int variant , SimultaneousBindingMode unique )
{
2019-03-26 10:28:43 +08:00
InternalChild = KeyBindingContainer =
2019-03-29 10:36:40 +08:00
CreateKeyBindingContainer ( ruleset , variant , unique )
2019-03-26 10:28:43 +08:00
. WithChild ( content = new Container { RelativeSizeAxes = Axes . Both } ) ;
2019-03-16 12:47:11 +08:00
}
[BackgroundDependencyLoader(true)]
private void load ( OsuConfigManager config )
{
mouseDisabled = config . GetBindable < bool > ( OsuSetting . MouseDisableButtons ) ;
2023-11-07 07:13:46 +08:00
tapsDisabled = config . GetBindable < bool > ( OsuSetting . TouchDisableGameplayTaps ) ;
2017-08-21 11:31:21 +08:00
}
2018-04-13 17:19:50 +08:00
2017-08-24 14:48:40 +08:00
#region Action mapping ( for replays )
2018-04-13 17:19:50 +08:00
2018-09-18 16:05:26 +08:00
public override void HandleInputStateChange ( InputStateChangeEvent inputStateChange )
2017-08-24 14:23:17 +08:00
{
2022-01-31 17:54:23 +08:00
switch ( inputStateChange )
2018-06-11 22:00:26 +08:00
{
2022-01-31 17:54:23 +08:00
case ReplayStateChangeEvent < T > stateChangeEvent :
foreach ( var action in stateChangeEvent . ReleasedActions )
KeyBindingContainer . TriggerReleased ( action ) ;
2018-04-13 17:19:50 +08:00
2022-01-31 17:54:23 +08:00
foreach ( var action in stateChangeEvent . PressedActions )
KeyBindingContainer . TriggerPressed ( action ) ;
break ;
case ReplayStatisticsFrameEvent statisticsStateChangeEvent :
2022-05-31 16:16:23 +08:00
scoreProcessor ? . ResetFromReplayFrame ( statisticsStateChangeEvent . Frame ) ;
2022-01-31 17:54:23 +08:00
break ;
default :
base . HandleInputStateChange ( inputStateChange ) ;
break ;
2018-06-11 22:00:26 +08:00
}
2017-08-24 14:23:17 +08:00
}
2018-04-13 17:19:50 +08:00
2017-08-24 14:48:40 +08:00
#endregion
2018-04-13 17:19:50 +08:00
2017-08-24 14:48:40 +08:00
#region IHasReplayHandler
2018-04-13 17:19:50 +08:00
2024-07-03 15:33:48 +08:00
private ReplayInputHandler ? replayInputHandler ;
2018-10-04 02:03:59 +08:00
2024-07-03 15:33:48 +08:00
public ReplayInputHandler ? ReplayInputHandler
2017-08-24 14:23:17 +08:00
{
2018-10-04 02:03:59 +08:00
get = > replayInputHandler ;
2017-08-24 14:23:17 +08:00
set
{
2024-07-03 16:32:08 +08:00
if ( replayInputHandler = = value )
return ;
2024-07-03 16:18:45 +08:00
if ( replayInputHandler ! = null )
{
RemoveHandler ( replayInputHandler ) ;
2024-07-03 16:32:08 +08:00
// ensures that all replay keys are released, and that the last replay state is correctly cleared
2024-07-03 16:18:45 +08:00
new ReplayStateReset ( ) . Apply ( CurrentState , this ) ;
}
2024-07-03 16:32:08 +08:00
else
{
// ensures that all user-pressed keys are released, so that the replay handler may trigger them itself
// setting `UseParentInput` will only sync releases (https://github.com/ppy/osu-framework/blob/45cd7c7c702c081334fce41e7771b9dc6481b28d/osu.Framework/Input/PassThroughInputManager.cs#L179-L182)
SyncInputState ( CreateInitialState ( ) ) ;
}
2018-04-13 17:19:50 +08:00
2017-08-24 14:23:17 +08:00
replayInputHandler = value ;
2018-06-11 22:00:26 +08:00
UseParentInput = replayInputHandler = = null ;
2018-04-13 17:19:50 +08:00
2017-08-24 14:23:17 +08:00
if ( replayInputHandler ! = null )
AddHandler ( replayInputHandler ) ;
}
}
2018-04-13 17:19:50 +08:00
2017-08-24 14:48:40 +08:00
#endregion
2018-04-13 17:19:50 +08:00
2017-08-24 14:48:40 +08:00
#region Setting application ( disables etc . )
2018-04-13 17:19:50 +08:00
2024-07-03 15:33:48 +08:00
private Bindable < bool > mouseDisabled = null ! ;
private Bindable < bool > tapsDisabled = null ! ;
2018-04-13 17:19:50 +08:00
2018-10-04 02:03:59 +08:00
protected override bool Handle ( UIEvent e )
{
2018-10-04 16:55:31 +08:00
switch ( e )
{
2022-06-24 20:25:23 +08:00
case MouseDownEvent :
2018-10-04 16:55:31 +08:00
if ( mouseDisabled . Value )
2021-02-23 13:24:24 +08:00
return true ; // importantly, block upwards propagation so global bindings also don't fire.
2019-02-27 20:07:17 +08:00
2018-10-04 16:55:31 +08:00
break ;
2019-04-01 11:44:46 +08:00
2018-10-04 16:55:31 +08:00
case MouseUpEvent mouseUp :
if ( ! CurrentState . Mouse . IsPressed ( mouseUp . Button ) )
return false ;
2019-02-27 20:07:17 +08:00
2018-10-04 16:55:31 +08:00
break ;
}
2019-02-21 20:24:02 +08:00
2018-10-04 02:03:59 +08:00
return base . Handle ( e ) ;
2022-01-05 17:29:32 +08:00
}
protected override bool HandleMouseTouchStateChange ( TouchStateChangeEvent e )
{
2023-11-07 03:53:22 +08:00
if ( tapsDisabled . Value )
2022-01-05 17:29:32 +08:00
{
2023-11-07 03:53:22 +08:00
// Only propagate positional data when taps are disabled.
2022-01-05 17:29:32 +08:00
e = new TouchStateChangeEvent ( e . State , e . Input , e . Touch , false , e . LastPosition ) ;
}
return base . HandleMouseTouchStateChange ( e ) ;
2018-10-04 02:03:59 +08:00
}
2017-08-24 14:48:40 +08:00
#endregion
2018-04-13 17:19:50 +08:00
2023-06-25 21:04:39 +08:00
#region Key Counter Attachment
2023-06-06 06:04:29 +08:00
2023-06-27 01:27:42 +08:00
public void Attach ( InputCountController inputCountController )
2017-08-21 11:31:21 +08:00
{
2023-06-27 15:35:59 +08:00
var triggers = KeyBindingContainer . DefaultKeyBindings
. Select ( b = > b . GetAction < T > ( ) )
. Distinct ( )
. Select ( action = > new KeyCounterActionTrigger < T > ( action ) )
. ToArray ( ) ;
KeyBindingContainer . AddRange ( triggers ) ;
inputCountController . AddRange ( triggers ) ;
2023-06-25 21:04:39 +08:00
}
2018-04-13 17:19:50 +08:00
2023-06-25 21:04:39 +08:00
#endregion
2022-08-09 03:27:46 +08:00
2023-06-25 21:04:39 +08:00
#region Keys per second Counter Attachment
2023-06-27 15:38:46 +08:00
public void Attach ( ClicksPerSecondController controller ) = > KeyBindingContainer . Add ( new ActionListener ( controller ) ) ;
2022-08-09 03:27:46 +08:00
2022-08-24 23:12:52 +08:00
private partial class ActionListener : Component , IKeyBindingHandler < T >
2022-08-09 03:27:46 +08:00
{
2023-06-27 15:38:46 +08:00
private readonly ClicksPerSecondController controller ;
2022-08-24 23:12:52 +08:00
2023-06-27 15:38:46 +08:00
public ActionListener ( ClicksPerSecondController controller )
2023-06-25 21:04:39 +08:00
{
2023-06-27 15:38:46 +08:00
this . controller = controller ;
2023-06-25 21:04:39 +08:00
}
2022-08-24 18:36:01 +08:00
2022-08-09 03:27:46 +08:00
public bool OnPressed ( KeyBindingPressEvent < T > e )
{
2023-06-27 15:38:46 +08:00
controller . AddInputTimestamp ( ) ;
2022-08-09 03:27:46 +08:00
return false ;
}
public void OnReleased ( KeyBindingReleaseEvent < T > e )
{
}
}
#endregion
2020-03-23 16:33:02 +08:00
protected virtual KeyBindingContainer < T > CreateKeyBindingContainer ( RulesetInfo ruleset , int variant , SimultaneousBindingMode unique )
2018-06-07 19:24:33 +08:00
= > new RulesetKeyBindingContainer ( ruleset , variant , unique ) ;
2019-01-23 13:51:13 +08:00
public partial class RulesetKeyBindingContainer : DatabasedKeyBindingContainer < T >
{
2021-11-18 11:57:51 +08:00
protected override bool HandleRepeats = > false ;
2019-01-23 13:51:13 +08:00
public RulesetKeyBindingContainer ( RulesetInfo ruleset , int variant , SimultaneousBindingMode unique )
: base ( ruleset , variant , unique )
{
}
2021-05-24 16:23:09 +08:00
2022-10-09 22:14:16 +08:00
protected override void ReloadMappings ( IQueryable < RealmKeyBinding > realmKeyBindings )
2021-05-24 16:23:09 +08:00
{
2022-10-09 22:14:16 +08:00
base . ReloadMappings ( realmKeyBindings ) ;
2021-05-24 16:23:09 +08:00
2024-01-29 15:12:00 +08:00
KeyBindings = KeyBindings . Where ( static b = > RealmKeyBindingStore . CheckValidForGameplay ( b . KeyCombination ) ) . ToList ( ) ;
2023-10-17 03:26:46 +08:00
RealmKeyBindingStore . ClearDuplicateBindings ( KeyBindings ) ;
2021-05-24 16:23:09 +08:00
}
2019-01-23 13:51:13 +08:00
}
2024-07-03 16:18:45 +08:00
private class ReplayStateReset : IInput
{
public void Apply ( InputState state , IInputStateChangeHandler handler )
{
if ( ! ( state is RulesetInputManagerInputState < T > inputState ) )
throw new InvalidOperationException ( $"{nameof(ReplayState<T>)} should only be applied to a {nameof(RulesetInputManagerInputState<T>)}" ) ;
inputState . LastReplayState = null ;
handler . HandleInputStateChange ( new ReplayStateChangeEvent < T > ( state , this , inputState . LastReplayState ? . PressedActions . ToArray ( ) ? ? [ ] , [ ] ) ) ;
}
}
2017-08-21 11:31:21 +08:00
}
2018-04-13 17:19:50 +08:00
2018-06-11 22:00:26 +08:00
public class RulesetInputManagerInputState < T > : InputState
2018-10-04 02:03:59 +08:00
where T : struct
2018-06-11 22:00:26 +08:00
{
2024-07-03 15:33:48 +08:00
public ReplayState < T > ? LastReplayState ;
2018-09-19 19:52:57 +08:00
2024-07-03 15:33:48 +08:00
public RulesetInputManagerInputState ( InputState state )
2020-07-01 17:54:11 +08:00
: base ( state )
2018-09-19 19:52:57 +08:00
{
}
2018-06-11 22:00:26 +08:00
}
2017-08-24 19:32:55 +08:00
}