2023-01-16 18:35:55 +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.
using System.Collections.Generic ;
using System.Linq ;
2023-01-16 19:13:31 +08:00
using osu.Framework.Allocation ;
using osu.Framework.Bindables ;
2023-01-16 18:35:55 +08:00
using osu.Framework.Graphics ;
using osu.Framework.Input ;
using osu.Framework.Input.Events ;
2023-01-16 19:11:59 +08:00
using osu.Framework.Input.StateChanges ;
2023-01-16 19:13:31 +08:00
using osu.Game.Configuration ;
2023-01-24 04:46:01 +08:00
using osuTK ;
2023-01-16 18:35:55 +08:00
namespace osu.Game.Rulesets.Osu.UI
{
public partial class OsuTouchInputMapper : Drawable
{
/// <summary>
/// All the active <see cref="TouchSource"/>s and the <see cref="OsuAction"/> that it triggered (if any).
/// Ordered from oldest to newest touch chronologically.
/// </summary>
private readonly List < TrackedTouch > trackedTouches = new List < TrackedTouch > ( ) ;
2023-01-24 11:30:11 +08:00
private TrackedTouch ? positionTrackingTouch ;
2023-01-17 13:53:24 +08:00
private readonly OsuInputManager osuInputManager ;
2023-01-16 19:13:31 +08:00
private Bindable < bool > mouseDisabled = null ! ;
2023-01-16 18:35:55 +08:00
public OsuTouchInputMapper ( OsuInputManager inputManager )
{
osuInputManager = inputManager ;
}
2023-01-17 13:52:15 +08:00
[BackgroundDependencyLoader]
2023-01-16 19:13:31 +08:00
private void load ( OsuConfigManager config )
{
2023-01-17 13:54:52 +08:00
// The mouse button disable setting affects touch. It's a bit weird.
// This is mostly just doing the same as what is done in RulesetInputManager to match behaviour.
2023-01-16 19:13:31 +08:00
mouseDisabled = config . GetBindable < bool > ( OsuSetting . MouseDisableButtons ) ;
}
2023-01-24 10:06:54 +08:00
// Required to handle touches outside of the playfield when screen scaling is enabled.
2023-01-24 04:46:01 +08:00
public override bool ReceivePositionalInputAt ( Vector2 screenSpacePos ) = > true ;
2023-01-16 19:11:59 +08:00
protected override void OnTouchMove ( TouchMoveEvent e )
{
base . OnTouchMove ( e ) ;
handleTouchMovement ( e ) ;
}
2023-01-16 18:35:55 +08:00
protected override bool OnTouchDown ( TouchDownEvent e )
{
2023-01-16 19:11:59 +08:00
OsuAction action = trackedTouches . Any ( t = > t . Action = = OsuAction . LeftButton )
? OsuAction . RightButton
: OsuAction . LeftButton ;
2023-01-16 20:07:28 +08:00
// Ignore any taps which trigger an action which is already handled. But track them for potential positional input in the future.
2023-01-17 14:01:01 +08:00
bool shouldResultInAction = osuInputManager . AllowGameplayInputs & & ! mouseDisabled . Value & & trackedTouches . All ( t = > t . Action ! = action ) ;
2023-01-16 20:07:28 +08:00
2023-01-23 16:07:27 +08:00
// If we can actually accept as an action, check whether this tap was on a circle's receptor.
// This case gets special handling to allow for empty-space stream tapping.
bool isDirectCircleTouch = osuInputManager . CheckScreenSpaceActionPressJudgeable ( e . ScreenSpaceTouchDownPosition ) ;
2023-01-25 13:59:54 +08:00
var newTouch = new TrackedTouch ( e . Touch . Source , shouldResultInAction ? action : null , isDirectCircleTouch ) ;
2023-01-23 16:07:27 +08:00
2023-01-25 13:59:54 +08:00
updatePositionTracking ( newTouch ) ;
2023-01-23 16:07:27 +08:00
2023-01-25 13:59:54 +08:00
trackedTouches . Add ( newTouch ) ;
2023-01-16 20:07:28 +08:00
// Important to update position before triggering the pressed action.
2023-01-16 19:11:59 +08:00
handleTouchMovement ( e ) ;
2023-01-16 18:35:55 +08:00
2023-01-16 20:07:28 +08:00
if ( shouldResultInAction )
2023-01-16 18:35:55 +08:00
osuInputManager . KeyBindingContainer . TriggerPressed ( action ) ;
return true ;
}
2023-01-25 13:59:54 +08:00
/// <summary>
/// Given a new touch, update the positional tracking state and any related operations.
/// </summary>
private void updatePositionTracking ( TrackedTouch newTouch )
{
// If the new touch directly interacted with a circle's receptor, it always becomes the current touch for positional tracking.
if ( newTouch . DirectTouch )
{
positionTrackingTouch = newTouch ;
return ;
}
// Otherwise, we only want to use the new touch for position tracking if no other touch is tracking position yet..
if ( positionTrackingTouch = = null )
{
positionTrackingTouch = newTouch ;
return ;
}
// ..or if the current position tracking touch was not a direct touch (this one is debatable and may be change in the future, but it's the simplest way to handle)
if ( ! positionTrackingTouch . DirectTouch )
{
positionTrackingTouch = newTouch ;
return ;
}
// In the case the new touch was not used for position tracking, we should also check the previous position tracking touch.
// If it was a direct touch and still has its action pressed, that action should be released.
//
// This is done to allow tracking with the initial touch while still having both Left/Right actions available for alternating with two more touches.
if ( positionTrackingTouch . DirectTouch & & positionTrackingTouch . Action is OsuAction directTouchAction )
{
osuInputManager . KeyBindingContainer . TriggerReleased ( directTouchAction ) ;
positionTrackingTouch . Action = null ;
}
}
2023-01-16 19:11:59 +08:00
private void handleTouchMovement ( TouchEvent touchEvent )
{
2023-01-16 20:07:28 +08:00
// Movement should only be tracked for the most recent touch.
2023-01-23 16:07:27 +08:00
if ( touchEvent . Touch . Source ! = positionTrackingTouch ? . Source )
2023-01-16 20:07:28 +08:00
return ;
2023-01-17 14:01:01 +08:00
if ( ! osuInputManager . AllowUserCursorMovement )
return ;
2023-01-16 19:11:59 +08:00
new MousePositionAbsoluteInput { Position = touchEvent . ScreenSpaceTouch . Position } . Apply ( osuInputManager . CurrentState , osuInputManager ) ;
}
2023-01-16 18:35:55 +08:00
protected override void OnTouchUp ( TouchUpEvent e )
{
2023-01-17 13:52:45 +08:00
var tracked = trackedTouches . Single ( t = > t . Source = = e . Touch . Source ) ;
2023-01-16 18:35:55 +08:00
if ( tracked . Action is OsuAction action )
osuInputManager . KeyBindingContainer . TriggerReleased ( action ) ;
2023-01-23 16:07:27 +08:00
if ( positionTrackingTouch = = tracked )
positionTrackingTouch = null ;
2023-01-16 18:35:55 +08:00
trackedTouches . Remove ( tracked ) ;
base . OnTouchUp ( e ) ;
}
private class TrackedTouch
{
2023-01-17 13:51:45 +08:00
public readonly TouchSource Source ;
2023-01-16 18:35:55 +08:00
2023-01-23 16:07:27 +08:00
public OsuAction ? Action ;
public readonly bool DirectTouch ;
2023-01-16 18:35:55 +08:00
2023-01-23 16:07:27 +08:00
public TrackedTouch ( TouchSource source , OsuAction ? action , bool directTouch )
2023-01-16 18:35:55 +08:00
{
2023-01-17 13:51:45 +08:00
Source = source ;
2023-01-16 18:35:55 +08:00
Action = action ;
2023-01-23 16:07:27 +08:00
DirectTouch = directTouch ;
2023-01-16 18:35:55 +08:00
}
}
}
}