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