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
2018-11-20 15:51:59 +08:00
using osuTK ;
using osuTK.Input ;
2018-04-13 17:19:50 +08:00
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
using osu.Game.Graphics.Sprites ;
using osu.Game.Rulesets.Mods ;
using System ;
using System.Linq ;
using System.Collections.Generic ;
2019-06-20 22:06:07 +08:00
using System.Threading ;
2021-02-02 19:11:40 +08:00
using Humanizer ;
2018-10-02 11:02:47 +08:00
using osu.Framework.Input.Events ;
2019-02-12 12:04:46 +08:00
using osu.Game.Graphics ;
2018-04-13 17:19:50 +08:00
namespace osu.Game.Overlays.Mods
{
2021-02-02 19:11:40 +08:00
public class ModSection : CompositeDrawable
2018-04-13 17:19:50 +08:00
{
2021-02-02 19:11:40 +08:00
private readonly Drawable header ;
2018-04-13 17:19:50 +08:00
public FillFlowContainer < ModButtonEmpty > ButtonsContainer { get ; }
public Action < Mod > Action ;
2021-02-02 19:11:40 +08:00
public Key [ ] ToggleKeys ;
public readonly ModType ModType ;
2018-04-13 17:19:50 +08:00
public IEnumerable < Mod > SelectedMods = > buttons . Select ( b = > b . SelectedMod ) . Where ( m = > m ! = null ) ;
2019-06-20 22:06:07 +08:00
private CancellationTokenSource modsLoadCts ;
2021-02-04 17:10:55 +08:00
protected bool SelectionAnimationRunning = > pendingSelectionOperations . Count > 0 ;
2019-06-20 23:12:39 +08:00
/// <summary>
/// True when all mod icons have completed loading.
/// </summary>
public bool ModIconsLoaded { get ; private set ; } = true ;
2018-04-13 17:19:50 +08:00
public IEnumerable < Mod > Mods
{
set
{
var modContainers = value . Select ( m = >
{
if ( m = = null )
return new ModButtonEmpty ( ) ;
return new ModButton ( m )
{
2021-02-04 17:10:55 +08:00
SelectionChanged = mod = >
{
ModButtonStateChanged ( mod ) ;
Action ? . Invoke ( mod ) ;
} ,
2018-04-13 17:19:50 +08:00
} ;
} ) . ToArray ( ) ;
2019-06-20 22:06:07 +08:00
modsLoadCts ? . Cancel ( ) ;
2019-12-06 18:04:55 +08:00
if ( modContainers . Length = = 0 )
{
ModIconsLoaded = true ;
2021-02-02 19:11:40 +08:00
header . Hide ( ) ;
2019-12-06 18:04:55 +08:00
Hide ( ) ;
return ;
}
2019-06-20 23:12:39 +08:00
ModIconsLoaded = false ;
LoadComponentsAsync ( modContainers , c = >
{
ModIconsLoaded = true ;
ButtonsContainer . ChildrenEnumerable = c ;
} , ( modsLoadCts = new CancellationTokenSource ( ) ) . Token ) ;
2019-06-20 22:06:07 +08:00
2018-04-13 17:19:50 +08:00
buttons = modContainers . OfType < ModButton > ( ) . ToArray ( ) ;
2019-06-14 00:12:56 +08:00
2021-02-02 19:11:40 +08:00
header . FadeIn ( 200 ) ;
2019-12-06 18:04:55 +08:00
this . FadeIn ( 200 ) ;
2018-04-13 17:19:50 +08:00
}
}
2021-02-04 17:10:55 +08:00
protected virtual void ModButtonStateChanged ( Mod mod )
{
}
2019-11-28 21:41:29 +08:00
private ModButton [ ] buttons = Array . Empty < ModButton > ( ) ;
2018-04-13 17:19:50 +08:00
2018-10-02 11:02:47 +08:00
protected override bool OnKeyDown ( KeyDownEvent e )
2018-04-13 17:19:50 +08:00
{
2020-11-10 16:57:57 +08:00
if ( e . ControlPressed ) return false ;
2018-07-31 17:00:42 +08:00
if ( ToggleKeys ! = null )
{
2018-10-02 11:02:47 +08:00
var index = Array . IndexOf ( ToggleKeys , e . Key ) ;
2018-07-31 17:00:42 +08:00
if ( index > - 1 & & index < buttons . Length )
2018-10-02 11:44:14 +08:00
buttons [ index ] . SelectNext ( e . ShiftPressed ? - 1 : 1 ) ;
2018-07-31 17:00:42 +08:00
}
2018-04-13 17:19:50 +08:00
2018-10-02 11:02:47 +08:00
return base . OnKeyDown ( e ) ;
2018-04-13 17:19:50 +08:00
}
2021-02-04 17:56:40 +08:00
private const double initial_multiple_selection_delay = 120 ;
private double selectionDelay = initial_multiple_selection_delay ;
private double lastSelection ;
2021-02-04 17:10:55 +08:00
private readonly Queue < Action > pendingSelectionOperations = new Queue < Action > ( ) ;
2021-02-04 17:56:40 +08:00
protected override void Update ( )
2021-02-04 17:10:55 +08:00
{
2021-02-04 17:56:40 +08:00
base . Update ( ) ;
2021-02-04 17:10:55 +08:00
2021-02-04 17:56:40 +08:00
if ( selectionDelay = = initial_multiple_selection_delay | | Time . Current - lastSelection > = selectionDelay )
2021-02-04 17:10:55 +08:00
{
if ( pendingSelectionOperations . TryDequeue ( out var dequeuedAction ) )
2021-02-04 17:56:40 +08:00
{
2021-02-04 17:10:55 +08:00
dequeuedAction ( ) ;
2021-02-04 17:56:40 +08:00
2021-02-04 17:58:56 +08:00
// each time we play an animation, we decrease the time until the next animation (to ramp the visual and audible elements).
2021-02-04 17:56:40 +08:00
selectionDelay = Math . Max ( 30 , selectionDelay * 0.8f ) ;
lastSelection = Time . Current ;
}
else
{
2021-02-04 17:58:56 +08:00
// reset the selection delay after all animations have been completed.
// this will cause the next action to be immediately performed.
2021-02-04 17:56:40 +08:00
selectionDelay = initial_multiple_selection_delay ;
}
}
2021-02-04 17:10:55 +08:00
}
2021-02-02 20:14:38 +08:00
/// <summary>
/// Selects all mods.
/// </summary>
public void SelectAll ( )
{
2021-02-04 17:10:55 +08:00
pendingSelectionOperations . Clear ( ) ;
2021-02-02 20:14:38 +08:00
foreach ( var button in buttons . Where ( b = > ! b . Selected ) )
2021-02-04 17:10:55 +08:00
pendingSelectionOperations . Enqueue ( ( ) = > button . SelectAt ( 0 ) ) ;
2021-02-02 20:14:38 +08:00
}
/// <summary>
/// Deselects all mods.
/// </summary>
2021-02-04 18:55:09 +08:00
public void DeselectAll ( )
{
pendingSelectionOperations . Clear ( ) ;
DeselectTypes ( buttons . Select ( b = > b . SelectedMod ? . GetType ( ) ) . Where ( t = > t ! = null ) ) ;
}
2018-04-13 17:19:50 +08:00
/// <summary>
/// Deselect one or more mods in this section.
/// </summary>
/// <param name="modTypes">The types of <see cref="Mod"/>s which should be deselected.</param>
2021-02-04 22:44:46 +08:00
/// <param name="immediate">Whether the deselection should happen immediately. Should only be used when required to ensure correct selection flow.</param>
public void DeselectTypes ( IEnumerable < Type > modTypes , bool immediate = false )
2018-04-13 17:19:50 +08:00
{
foreach ( var button in buttons )
{
2021-02-04 17:10:55 +08:00
if ( button . SelectedMod = = null ) continue ;
2019-02-28 12:31:40 +08:00
2018-04-13 17:19:50 +08:00
foreach ( var type in modTypes )
2019-11-11 19:53:22 +08:00
{
2021-02-04 17:10:55 +08:00
if ( type . IsInstanceOfType ( button . SelectedMod ) )
2021-02-04 22:44:46 +08:00
{
if ( immediate )
button . Deselect ( ) ;
else
pendingSelectionOperations . Enqueue ( button . Deselect ) ;
}
2019-11-11 19:53:22 +08:00
}
2018-04-13 17:19:50 +08:00
}
}
/// <summary>
2021-01-01 08:47:13 +08:00
/// Updates all buttons with the given list of selected mods.
2018-04-13 17:19:50 +08:00
/// </summary>
2021-01-01 20:34:09 +08:00
/// <param name="newSelectedMods">The new list of selected mods to select.</param>
2021-02-02 19:50:54 +08:00
public void UpdateSelectedButtons ( IReadOnlyList < Mod > newSelectedMods )
2018-04-13 17:19:50 +08:00
{
foreach ( var button in buttons )
2021-02-02 19:50:54 +08:00
updateButtonSelection ( button , newSelectedMods ) ;
2021-01-01 21:16:00 +08:00
}
2021-01-01 08:47:13 +08:00
2021-02-02 19:50:54 +08:00
private void updateButtonSelection ( ModButton button , IReadOnlyList < Mod > newSelectedMods )
2021-01-01 21:16:00 +08:00
{
foreach ( var mod in newSelectedMods )
{
var index = Array . FindIndex ( button . Mods , m1 = > mod . GetType ( ) = = m1 . GetType ( ) ) ;
2021-01-01 08:47:13 +08:00
if ( index < 0 )
2021-01-01 21:16:00 +08:00
continue ;
var buttonMod = button . Mods [ index ] ;
2021-02-09 12:44:42 +08:00
2021-02-10 14:12:29 +08:00
// as this is likely coming from an external change, ensure the settings of the mod are in sync.
2021-02-09 12:44:42 +08:00
buttonMod . CopyFrom ( mod ) ;
2021-02-10 14:30:17 +08:00
button . SelectAt ( index , false ) ;
2021-01-01 21:16:00 +08:00
return ;
2018-04-13 17:19:50 +08:00
}
2021-01-01 21:16:00 +08:00
button . Deselect ( ) ;
2018-04-13 17:19:50 +08:00
}
2021-02-02 19:11:40 +08:00
public ModSection ( ModType type )
2018-04-13 17:19:50 +08:00
{
2021-02-02 19:11:40 +08:00
ModType = type ;
2018-04-13 17:19:50 +08:00
AutoSizeAxes = Axes . Y ;
2018-07-31 17:00:42 +08:00
RelativeSizeAxes = Axes . X ;
Origin = Anchor . TopCentre ;
Anchor = Anchor . TopCentre ;
2018-04-13 17:19:50 +08:00
2021-02-02 19:11:40 +08:00
InternalChildren = new [ ]
2018-04-13 17:19:50 +08:00
{
2021-02-02 19:11:40 +08:00
header = CreateHeader ( type . Humanize ( LetterCasing . Title ) ) ,
2018-04-13 17:19:50 +08:00
ButtonsContainer = new FillFlowContainer < ModButtonEmpty >
{
2019-11-03 23:32:47 +08:00
AutoSizeAxes = Axes . Y ,
RelativeSizeAxes = Axes . X ,
2018-04-13 17:19:50 +08:00
Origin = Anchor . BottomLeft ,
Anchor = Anchor . BottomLeft ,
Spacing = new Vector2 ( 50f , 0f ) ,
Margin = new MarginPadding
{
2019-11-23 02:23:48 +08:00
Top = 20 ,
2018-04-13 17:19:50 +08:00
} ,
AlwaysPresent = true
} ,
} ;
}
2021-02-02 19:11:40 +08:00
protected virtual Drawable CreateHeader ( string text ) = > new OsuSpriteText
{
Font = OsuFont . GetFont ( weight : FontWeight . Bold ) ,
Text = text
} ;
2021-02-04 18:12:37 +08:00
/// <summary>
/// Play out all remaining animations immediately to leave mods in a good (final) state.
/// </summary>
public void FlushAnimation ( )
{
while ( pendingSelectionOperations . TryDequeue ( out var dequeuedAction ) )
dequeuedAction ( ) ;
}
2018-04-13 17:19:50 +08:00
}
}