2017-08-09 10:50:34 +08:00
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
2017-08-09 11:37:47 +08:00
using System ;
2017-08-09 10:50:34 +08:00
using System.Collections.Generic ;
2017-08-10 15:08:43 +08:00
using System.Linq ;
2017-08-09 11:37:47 +08:00
using osu.Framework.Allocation ;
2017-08-09 10:50:34 +08:00
using osu.Framework.Input ;
2017-08-09 11:37:47 +08:00
using osu.Game.Rulesets ;
2017-08-09 10:50:34 +08:00
namespace osu.Game.Input
{
2017-08-09 13:50:10 +08:00
/// <summary>
/// Maps custom action data of type <see cref="T"/> and stores to <see cref="InputState.Data"/>.
/// </summary>
/// <typeparam name="T">The type of the custom action.</typeparam>
2017-08-09 16:10:32 +08:00
public abstract class ActionMappingInputManager < T > : PassThroughInputManager
2017-08-09 10:50:34 +08:00
where T : struct
{
2017-08-09 12:04:11 +08:00
private readonly RulesetInfo ruleset ;
2017-08-09 12:23:23 +08:00
private readonly int? variant ;
2017-08-10 15:08:43 +08:00
private readonly bool allowConcurrentActions ;
private readonly List < Binding > mappings = new List < Binding > ( ) ;
2017-08-09 13:50:10 +08:00
/// <summary>
/// Create a new instance.
/// </summary>
/// <param name="ruleset">A reference to identify the current <see cref="Ruleset"/>. Used to lookup mappings. Null for global mappings.</param>
/// <param name="variant">An optional variant for the specified <see cref="Ruleset"/>. Used when a ruleset has more than one possible keyboard layouts.</param>
2017-08-10 15:08:43 +08:00
/// <param name="allowConcurrentActions">Allow concurrent actions to be actuated at once. Note that this disables chord bindings.</param>
protected ActionMappingInputManager ( RulesetInfo ruleset = null , int? variant = null , bool allowConcurrentActions = false )
2017-08-09 12:04:11 +08:00
{
this . ruleset = ruleset ;
2017-08-09 12:23:23 +08:00
this . variant = variant ;
2017-08-10 15:08:43 +08:00
this . allowConcurrentActions = allowConcurrentActions ;
2017-08-09 12:04:11 +08:00
}
2017-08-10 15:08:43 +08:00
protected abstract IDictionary < KeyCombination , T > CreateDefaultMappings ( ) ;
2017-08-09 16:10:32 +08:00
2017-08-10 15:08:43 +08:00
private BindingStore store ;
2017-08-09 10:50:34 +08:00
2017-08-09 11:37:47 +08:00
[BackgroundDependencyLoader]
private void load ( BindingStore bindings )
{
2017-08-10 15:08:43 +08:00
store = bindings ;
ReloadMappings ( ) ;
2017-08-09 11:37:47 +08:00
}
2017-08-10 15:08:43 +08:00
protected void ReloadMappings ( )
2017-08-09 10:50:34 +08:00
{
2017-08-10 15:08:43 +08:00
var rulesetId = ruleset ? . ID ;
2017-08-09 10:50:34 +08:00
2017-08-10 15:08:43 +08:00
mappings . Clear ( ) ;
foreach ( var kvp in CreateDefaultMappings ( ) )
mappings . Add ( new Binding ( kvp . Key , kvp . Value ) ) ;
if ( store ! = null )
{
foreach ( var b in store . Query < Binding > ( b = > b . RulesetID = = rulesetId & & b . Variant = = variant ) )
mappings . Add ( b ) ;
}
if ( allowConcurrentActions )
{
// ensure we have no overlapping bindings.
foreach ( var m in mappings )
foreach ( var colliding in mappings . Where ( k = > ! k . Keys . Equals ( m . Keys ) & & k . Keys . CheckValid ( m . Keys . Keys ) ) )
throw new InvalidOperationException ( $"Multiple partially overlapping bindings are not supported ({m} and {colliding} are colliding)!" ) ;
}
2017-08-09 10:50:34 +08:00
}
2017-08-10 15:08:43 +08:00
private readonly List < Binding > pressedBindings = new List < Binding > ( ) ;
protected override bool OnKeyDown ( InputState state , KeyDownEventArgs args )
2017-08-09 10:50:34 +08:00
{
2017-08-10 15:08:43 +08:00
if ( ! args . Repeat & & ( allowConcurrentActions | | pressedBindings . Count = = 0 ) )
{
Binding validBinding ;
2017-08-10 15:45:33 +08:00
if ( ( validBinding = mappings . Except ( pressedBindings ) . LastOrDefault ( m = > m . Keys . CheckValid ( state . Keyboard . Keys , ! allowConcurrentActions ) ) ) ! = null )
2017-08-10 15:08:43 +08:00
{
// store both the pressed combination and the resulting action, just in case the assignments change while we are actuated.
pressedBindings . Add ( validBinding ) ;
state . Data = validBinding . GetAction < T > ( ) ;
}
}
return base . OnKeyDown ( state , args ) ;
2017-08-09 10:50:34 +08:00
}
2017-08-09 11:37:47 +08:00
2017-08-10 15:08:43 +08:00
protected override bool OnKeyUp ( InputState state , KeyUpEventArgs args )
2017-08-09 11:37:47 +08:00
{
2017-08-10 15:08:43 +08:00
foreach ( var binding in pressedBindings . ToList ( ) )
{
2017-08-10 15:45:33 +08:00
if ( ! binding . Keys . CheckValid ( state . Keyboard . Keys , ! allowConcurrentActions ) )
2017-08-10 15:08:43 +08:00
{
// set data as KeyUp.
state . Data = binding . GetAction < T > ( ) ;
2017-08-09 11:37:47 +08:00
2017-08-10 15:08:43 +08:00
// and clear the no-longer-valid combination/action.
pressedBindings . Remove ( binding ) ;
}
}
2017-08-09 11:37:47 +08:00
2017-08-10 15:08:43 +08:00
return base . OnKeyUp ( state , args ) ;
2017-08-09 11:37:47 +08:00
}
}
2017-08-09 10:50:34 +08:00
}