2022-03-27 05:43:17 +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.
2022-03-28 04:55:52 +08:00
#nullable enable
2022-03-27 05:43:17 +08:00
using System ;
using System.Collections.Generic ;
using System.Diagnostics ;
using System.Linq ;
using osu.Framework.Allocation ;
using osu.Framework.Bindables ;
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
using osu.Framework.Input.Events ;
2022-05-07 03:38:29 +08:00
using osu.Framework.Lists ;
2022-04-25 01:13:19 +08:00
using osu.Framework.Utils ;
2022-05-07 22:28:28 +08:00
using osu.Game.Audio ;
2022-03-27 05:43:17 +08:00
using osu.Game.Configuration ;
2022-04-25 01:13:19 +08:00
using osu.Game.Graphics ;
2022-03-27 05:43:17 +08:00
using osu.Game.Graphics.Containers ;
using osu.Game.Graphics.UserInterface ;
2022-05-05 23:08:02 +08:00
using osu.Game.Input.Bindings ;
2022-05-08 00:30:21 +08:00
using osu.Game.Localisation ;
2022-03-27 05:43:17 +08:00
using osu.Game.Rulesets.Mods ;
2022-05-12 01:02:45 +08:00
using osu.Game.Utils ;
2022-03-27 05:43:17 +08:00
using osuTK ;
using osuTK.Input ;
namespace osu.Game.Overlays.Mods
{
2022-05-11 04:29:57 +08:00
public abstract class ModSelectOverlay : ShearedOverlayContainer , ISamplePlaybackDisabler
2022-03-27 05:43:17 +08:00
{
2022-05-07 18:23:48 +08:00
protected const int BUTTON_WIDTH = 200 ;
2022-03-27 05:43:17 +08:00
[Cached]
2022-03-27 06:21:17 +08:00
public Bindable < IReadOnlyList < Mod > > SelectedMods { get ; private set ; } = new Bindable < IReadOnlyList < Mod > > ( Array . Empty < Mod > ( ) ) ;
2022-03-27 05:43:17 +08:00
2022-03-28 04:55:52 +08:00
private Func < Mod , bool > isValidMod = m = > true ;
2022-05-08 00:49:29 +08:00
/// <summary>
/// A function determining whether each mod in the column should be displayed.
/// A return value of <see langword="true"/> means that the mod is not filtered and therefore its corresponding panel should be displayed.
/// A return value of <see langword="false"/> means that the mod is filtered out and therefore its corresponding panel should be hidden.
/// </summary>
2022-03-28 04:55:52 +08:00
public Func < Mod , bool > IsValidMod
{
get = > isValidMod ;
set
{
isValidMod = value ? ? throw new ArgumentNullException ( nameof ( value ) ) ;
2022-05-12 01:02:45 +08:00
filterMods ( ) ;
2022-03-28 04:55:52 +08:00
}
}
/// <summary>
/// Whether the total score multiplier calculated from the current selected set of mods should be shown.
/// </summary>
protected virtual bool ShowTotalMultiplier = > true ;
protected virtual ModColumn CreateModColumn ( ModType modType , Key [ ] ? toggleKeys = null ) = > new ModColumn ( modType , false , toggleKeys ) ;
2022-05-08 00:58:37 +08:00
protected virtual IReadOnlyList < Mod > ComputeNewModsFromSelection ( IReadOnlyList < Mod > oldSelection , IReadOnlyList < Mod > newSelection ) = > newSelection ;
protected virtual IEnumerable < ShearedButton > CreateFooterButtons ( ) = > createDefaultFooterButtons ( ) ;
2022-05-06 22:31:59 +08:00
2022-05-12 01:02:45 +08:00
private readonly Bindable < Dictionary < ModType , IReadOnlyList < Mod > > > availableMods = new Bindable < Dictionary < ModType , IReadOnlyList < Mod > > > ( ) ;
private readonly Dictionary < ModType , IReadOnlyList < ModState > > localAvailableMods = new Dictionary < ModType , IReadOnlyList < ModState > > ( ) ;
2022-03-27 05:43:17 +08:00
private readonly BindableBool customisationVisible = new BindableBool ( ) ;
2022-03-28 04:55:52 +08:00
private ModSettingsArea modSettingsArea = null ! ;
2022-04-27 04:35:18 +08:00
private ColumnScrollContainer columnScroll = null ! ;
2022-04-25 01:13:19 +08:00
private ColumnFlowContainer columnFlow = null ! ;
2022-05-07 04:36:08 +08:00
private FillFlowContainer < ShearedButton > footerButtonFlow = null ! ;
2022-05-07 18:59:15 +08:00
private ShearedButton backButton = null ! ;
2022-05-08 00:58:37 +08:00
private DifficultyMultiplierDisplay ? multiplierDisplay ;
2022-05-07 18:59:15 +08:00
private ShearedToggleButton ? customisationButton ;
2022-04-04 14:45:44 +08:00
2022-05-11 04:29:57 +08:00
protected ModSelectOverlay ( OverlayColourScheme colourScheme = OverlayColourScheme . Green )
2022-05-05 04:17:40 +08:00
: base ( colourScheme )
{
}
2022-03-27 05:43:17 +08:00
[BackgroundDependencyLoader]
2022-05-12 01:02:45 +08:00
private void load ( OsuGameBase game , OsuColour colours )
2022-03-27 05:43:17 +08:00
{
2022-05-12 00:01:33 +08:00
Header . Title = ModSelectOverlayStrings . ModSelectTitle ;
Header . Description = ModSelectOverlayStrings . ModSelectDescription ;
2022-03-27 05:43:17 +08:00
2022-04-20 14:57:45 +08:00
AddRange ( new Drawable [ ]
2022-03-27 05:43:17 +08:00
{
2022-04-20 14:57:45 +08:00
new ClickToReturnContainer
{
RelativeSizeAxes = Axes . Both ,
HandleMouse = { BindTarget = customisationVisible } ,
OnClicked = ( ) = > customisationVisible . Value = false
} ,
modSettingsArea = new ModSettingsArea
2022-03-27 05:43:17 +08:00
{
Anchor = Anchor . BottomCentre ,
2022-04-20 14:57:45 +08:00
Origin = Anchor . BottomCentre ,
Height = 0
}
} ) ;
2022-04-20 14:05:07 +08:00
MainAreaContent . AddRange ( new Drawable [ ]
2022-03-27 05:43:17 +08:00
{
2022-04-20 14:57:45 +08:00
new Container
{
2022-04-20 15:28:52 +08:00
Padding = new MarginPadding
2022-04-20 14:57:45 +08:00
{
2022-04-20 22:17:29 +08:00
Top = ( ShowTotalMultiplier ? DifficultyMultiplierDisplay . HEIGHT : 0 ) + PADDING ,
2022-05-06 22:02:32 +08:00
Bottom = PADDING
2022-04-20 14:57:45 +08:00
} ,
2022-03-27 05:43:17 +08:00
RelativeSizeAxes = Axes . Both ,
2022-04-20 14:57:45 +08:00
RelativePositionAxes = Axes . Both ,
2022-03-27 05:43:17 +08:00
Children = new Drawable [ ]
{
2022-04-27 04:35:18 +08:00
columnScroll = new ColumnScrollContainer
2022-03-27 05:43:17 +08:00
{
2022-04-27 03:57:19 +08:00
RelativeSizeAxes = Axes . Both ,
Masking = false ,
ClampExtension = 100 ,
ScrollbarOverlapsContent = false ,
Child = columnFlow = new ColumnFlowContainer
2022-03-27 05:43:17 +08:00
{
2022-05-09 02:40:14 +08:00
Anchor = Anchor . BottomLeft ,
Origin = Anchor . BottomLeft ,
2022-04-20 14:57:45 +08:00
Direction = FillDirection . Horizontal ,
2022-04-20 15:30:58 +08:00
Shear = new Vector2 ( SHEAR , 0 ) ,
2022-04-20 14:57:45 +08:00
RelativeSizeAxes = Axes . Y ,
AutoSizeAxes = Axes . X ,
2022-04-25 01:27:27 +08:00
Margin = new MarginPadding { Horizontal = 70 } ,
2022-05-09 02:40:14 +08:00
Padding = new MarginPadding { Bottom = 10 } ,
2022-04-20 14:57:45 +08:00
Children = new [ ]
2022-03-27 05:43:17 +08:00
{
2022-04-27 03:57:19 +08:00
createModColumnContent ( ModType . DifficultyReduction , new [ ] { Key . Q , Key . W , Key . E , Key . R , Key . T , Key . Y , Key . U , Key . I , Key . O , Key . P } ) ,
createModColumnContent ( ModType . DifficultyIncrease , new [ ] { Key . A , Key . S , Key . D , Key . F , Key . G , Key . H , Key . J , Key . K , Key . L } ) ,
createModColumnContent ( ModType . Automation , new [ ] { Key . Z , Key . X , Key . C , Key . V , Key . B , Key . N , Key . M } ) ,
createModColumnContent ( ModType . Conversion ) ,
createModColumnContent ( ModType . Fun )
2022-04-20 14:57:45 +08:00
}
2022-04-27 03:57:19 +08:00
}
}
2022-03-27 05:43:17 +08:00
}
}
2022-04-20 14:05:07 +08:00
} ) ;
2022-04-18 05:26:25 +08:00
2022-04-20 22:17:29 +08:00
if ( ShowTotalMultiplier )
2022-03-28 04:55:52 +08:00
{
2022-04-20 22:17:29 +08:00
MainAreaContent . Add ( new Container
{
Anchor = Anchor . TopRight ,
Origin = Anchor . TopRight ,
AutoSizeAxes = Axes . X ,
Height = DifficultyMultiplierDisplay . HEIGHT ,
Margin = new MarginPadding { Horizontal = 100 } ,
Child = multiplierDisplay = new DifficultyMultiplierDisplay
{
Anchor = Anchor . Centre ,
Origin = Anchor . Centre
} ,
} ) ;
}
2022-05-07 04:36:08 +08:00
FooterContent . Child = footerButtonFlow = new FillFlowContainer < ShearedButton >
2022-04-20 22:17:29 +08:00
{
2022-05-06 22:31:59 +08:00
RelativeSizeAxes = Axes . X ,
AutoSizeAxes = Axes . Y ,
Direction = FillDirection . Horizontal ,
Anchor = Anchor . BottomLeft ,
Origin = Anchor . BottomLeft ,
Padding = new MarginPadding
2022-04-20 22:17:29 +08:00
{
2022-05-06 22:31:59 +08:00
Vertical = PADDING ,
Horizontal = 70
} ,
Spacing = new Vector2 ( 10 ) ,
2022-05-07 18:59:15 +08:00
ChildrenEnumerable = CreateFooterButtons ( ) . Prepend ( backButton = new ShearedButton ( BUTTON_WIDTH )
2022-05-07 04:30:07 +08:00
{
Text = CommonStrings . Back ,
Action = Hide ,
DarkerColour = colours . Pink2 ,
LighterColour = colours . Pink1
} )
2022-05-06 22:31:59 +08:00
} ;
2022-05-12 01:02:45 +08:00
availableMods . BindTo ( game . AvailableMods ) ;
2022-03-27 05:43:17 +08:00
}
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
2022-05-12 01:02:45 +08:00
availableMods . BindValueChanged ( _ = > createLocalMods ( ) , true ) ;
2022-05-07 22:28:28 +08:00
State . BindValueChanged ( _ = > samplePlaybackDisabled . Value = State . Value = = Visibility . Hidden , true ) ;
2022-05-10 14:07:08 +08:00
// This is an optimisation to prevent refreshing the available settings controls when it can be
2022-05-12 00:06:09 +08:00
// reasonably assumed that the settings panel is never to be displayed (e.g. FreeModSelectOverlay).
2022-05-10 13:48:41 +08:00
if ( customisationButton ! = null )
( ( IBindable < IReadOnlyList < Mod > > ) modSettingsArea . SelectedMods ) . BindTo ( SelectedMods ) ;
2022-03-28 06:16:10 +08:00
2022-03-27 06:21:17 +08:00
SelectedMods . BindValueChanged ( val = >
2022-03-27 05:43:17 +08:00
{
updateMultiplier ( ) ;
updateCustomisation ( val ) ;
2022-05-12 01:30:06 +08:00
updateFromExternalSelection ( ) ;
2022-03-27 05:43:17 +08:00
} , true ) ;
2022-03-28 06:16:10 +08:00
2022-03-27 05:43:17 +08:00
customisationVisible . BindValueChanged ( _ = > updateCustomisationVisualState ( ) , true ) ;
2022-03-28 04:55:52 +08:00
2022-05-08 16:00:20 +08:00
// Start scrolled slightly to the right to give the user a sense that
// there is more horizontal content available.
ScheduleAfterChildren ( ( ) = >
{
2022-05-08 18:57:03 +08:00
columnScroll . ScrollTo ( 200 , false ) ;
2022-05-08 16:00:20 +08:00
columnScroll . ScrollToStart ( ) ;
} ) ;
2022-03-27 05:43:17 +08:00
}
2022-05-08 00:58:37 +08:00
/// <summary>
/// Select all visible mods in all columns.
/// </summary>
protected void SelectAll ( )
{
foreach ( var column in columnFlow . Columns )
column . SelectAll ( ) ;
}
/// <summary>
/// Deselect all visible mods in all columns.
/// </summary>
protected void DeselectAll ( )
{
foreach ( var column in columnFlow . Columns )
column . DeselectAll ( ) ;
}
private ColumnDimContainer createModColumnContent ( ModType modType , Key [ ] ? toggleKeys = null )
2022-05-08 21:22:32 +08:00
{
var column = CreateModColumn ( modType , toggleKeys ) . With ( column = >
{
// spacing applied here rather than via `columnFlow.Spacing` to avoid uneven gaps when some of the columns are hidden.
column . Margin = new MarginPadding { Right = 10 } ;
} ) ;
return new ColumnDimContainer ( column )
2022-05-08 00:58:37 +08:00
{
AutoSizeAxes = Axes . X ,
RelativeSizeAxes = Axes . Y ,
2022-05-08 21:22:32 +08:00
RequestScroll = col = > columnScroll . ScrollIntoView ( col , extraScroll : 140 ) ,
2022-05-08 00:58:37 +08:00
} ;
2022-05-08 21:22:32 +08:00
}
2022-05-08 00:58:37 +08:00
private ShearedButton [ ] createDefaultFooterButtons ( )
= > new [ ]
{
customisationButton = new ShearedToggleButton ( BUTTON_WIDTH )
{
2022-05-12 00:01:33 +08:00
Text = ModSelectOverlayStrings . ModCustomisation ,
2022-05-08 00:58:37 +08:00
Active = { BindTarget = customisationVisible }
} ,
new ShearedButton ( BUTTON_WIDTH )
{
Text = CommonStrings . DeselectAll ,
Action = DeselectAll
}
} ;
2022-05-12 01:02:45 +08:00
private void createLocalMods ( )
{
localAvailableMods . Clear ( ) ;
foreach ( var ( modType , mods ) in availableMods . Value )
{
var modStates = mods . SelectMany ( ModUtils . FlattenMod )
. Select ( mod = > new ModState ( mod . DeepClone ( ) ) )
. ToArray ( ) ;
2022-05-12 01:30:06 +08:00
foreach ( var modState in modStates )
modState . Active . BindValueChanged ( _ = > updateFromInternalSelection ( ) ) ;
2022-05-12 01:02:45 +08:00
localAvailableMods [ modType ] = modStates ;
}
filterMods ( ) ;
foreach ( var column in columnFlow . Columns )
column . AvailableMods = localAvailableMods . GetValueOrDefault ( column . ModType , Array . Empty < ModState > ( ) ) ;
}
private void filterMods ( )
{
foreach ( var modState in localAvailableMods . Values . SelectMany ( m = > m ) )
modState . Filtered . Value = ! modState . Mod . HasImplementation | | ! IsValidMod . Invoke ( modState . Mod ) ;
}
2022-03-27 05:43:17 +08:00
private void updateMultiplier ( )
{
2022-03-28 04:55:52 +08:00
if ( multiplierDisplay = = null )
return ;
2022-03-27 05:43:17 +08:00
double multiplier = 1.0 ;
2022-03-27 06:21:17 +08:00
foreach ( var mod in SelectedMods . Value )
2022-03-27 05:43:17 +08:00
multiplier * = mod . ScoreMultiplier ;
multiplierDisplay . Current . Value = multiplier ;
}
private void updateCustomisation ( ValueChangedEvent < IReadOnlyList < Mod > > valueChangedEvent )
{
2022-05-06 22:31:59 +08:00
if ( customisationButton = = null )
2022-03-28 04:55:52 +08:00
return ;
2022-03-27 05:43:17 +08:00
bool anyCustomisableMod = false ;
bool anyModWithRequiredCustomisationAdded = false ;
2022-03-27 06:21:17 +08:00
foreach ( var mod in SelectedMods . Value )
2022-03-27 05:43:17 +08:00
{
anyCustomisableMod | = mod . GetSettingsSourceProperties ( ) . Any ( ) ;
2022-05-06 01:07:44 +08:00
anyModWithRequiredCustomisationAdded | = valueChangedEvent . OldValue . All ( m = > m . GetType ( ) ! = mod . GetType ( ) ) & & mod . RequiresConfiguration ;
2022-03-27 05:43:17 +08:00
}
if ( anyCustomisableMod )
{
customisationVisible . Disabled = false ;
if ( anyModWithRequiredCustomisationAdded & & ! customisationVisible . Value )
customisationVisible . Value = true ;
}
else
{
if ( customisationVisible . Value )
customisationVisible . Value = false ;
customisationVisible . Disabled = true ;
}
}
private void updateCustomisationVisualState ( )
{
2022-04-05 15:56:24 +08:00
const double transition_duration = 300 ;
2022-04-04 14:50:40 +08:00
2022-04-20 14:57:45 +08:00
MainAreaContent . FadeColour ( customisationVisible . Value ? Colour4 . Gray : Colour4 . White , transition_duration , Easing . InOutCubic ) ;
2022-03-27 05:43:17 +08:00
2022-05-07 04:36:08 +08:00
foreach ( var button in footerButtonFlow )
{
if ( button ! = customisationButton )
button . Enabled . Value = ! customisationVisible . Value ;
}
2022-03-27 05:43:17 +08:00
float modAreaHeight = customisationVisible . Value ? ModSettingsArea . HEIGHT : 0 ;
2022-04-04 14:50:40 +08:00
modSettingsArea . ResizeHeightTo ( modAreaHeight , transition_duration , Easing . InOutCubic ) ;
2022-04-20 14:05:07 +08:00
TopLevelContent . MoveToY ( - modAreaHeight , transition_duration , Easing . InOutCubic ) ;
2022-03-27 05:43:17 +08:00
}
2022-05-12 01:30:06 +08:00
/// <summary>
/// This flag helps to determine the source of changes to <see cref="SelectedMods"/>.
/// If the value is false, then <see cref="SelectedMods"/> are changing due to a user selection on the UI.
/// If the value is true, then <see cref="SelectedMods"/> are changing due to an external <see cref="SelectedMods"/> change.
/// </summary>
private bool externalSelectionUpdateInProgress ;
private void updateFromExternalSelection ( )
2022-03-28 06:16:10 +08:00
{
2022-05-12 01:30:06 +08:00
externalSelectionUpdateInProgress = true ;
var newSelection = new List < Mod > ( ) ;
foreach ( var modState in localAvailableMods . SelectMany ( pair = > pair . Value ) )
{
var matchingSelectedMod = SelectedMods . Value . SingleOrDefault ( selected = > selected . GetType ( ) = = modState . Mod . GetType ( ) ) ;
if ( matchingSelectedMod ! = null )
{
modState . Mod . CopyFrom ( matchingSelectedMod ) ;
modState . Active . Value = true ;
newSelection . Add ( modState . Mod ) ;
}
else
{
modState . Mod . ResetSettingsToDefaults ( ) ;
modState . Active . Value = false ;
}
}
SelectedMods . Value = newSelection ;
2022-03-28 06:16:10 +08:00
2022-05-12 01:30:06 +08:00
externalSelectionUpdateInProgress = false ;
2022-05-04 03:44:44 +08:00
}
2022-04-21 05:34:43 +08:00
2022-05-12 01:30:06 +08:00
private void updateFromInternalSelection ( )
2022-03-28 06:16:10 +08:00
{
2022-05-12 01:30:06 +08:00
if ( externalSelectionUpdateInProgress )
return ;
var candidateSelection = localAvailableMods . SelectMany ( pair = > pair . Value )
. Where ( modState = > modState . Active . Value )
. Select ( modState = > modState . Mod )
. ToArray ( ) ;
2022-03-28 06:16:10 +08:00
2022-05-07 03:38:29 +08:00
// the following guard intends to check cases where we've already replaced potentially-external mod references with our own and avoid endless recursion.
// TODO: replace custom comparer with System.Collections.Generic.ReferenceEqualityComparer when fully on .NET 6
if ( candidateSelection . SequenceEqual ( SelectedMods . Value , new FuncEqualityComparer < Mod > ( ReferenceEquals ) ) )
2022-05-04 03:44:44 +08:00
return ;
2022-03-28 06:16:10 +08:00
2022-05-04 03:44:44 +08:00
SelectedMods . Value = ComputeNewModsFromSelection ( SelectedMods . Value , candidateSelection ) ;
2022-03-28 06:16:10 +08:00
}
2022-05-08 00:58:37 +08:00
#region Transition handling
2022-04-21 05:34:43 +08:00
2022-05-08 20:44:54 +08:00
private const float distance = 700 ;
2022-03-27 05:43:17 +08:00
protected override void PopIn ( )
{
2022-04-05 17:38:31 +08:00
const double fade_in_duration = 400 ;
2022-04-04 14:45:44 +08:00
2022-03-27 05:43:17 +08:00
base . PopIn ( ) ;
2022-04-04 14:45:44 +08:00
2022-03-28 04:55:52 +08:00
multiplierDisplay ?
2022-04-05 17:38:31 +08:00
. Delay ( fade_in_duration * 0.65f )
. FadeIn ( fade_in_duration / 2 , Easing . OutQuint )
2022-04-05 17:25:27 +08:00
. ScaleTo ( 1 , fade_in_duration , Easing . OutElastic ) ;
2022-04-05 17:27:34 +08:00
2022-05-08 20:44:54 +08:00
int nonFilteredColumnCount = 0 ;
2022-04-05 17:27:34 +08:00
for ( int i = 0 ; i < columnFlow . Count ; i + + )
{
2022-05-08 20:44:54 +08:00
var column = columnFlow [ i ] . Column ;
2022-05-12 01:30:06 +08:00
bool allFiltered = column . AvailableMods . All ( modState = > modState . Filtered . Value ) ;
double delay = allFiltered ? 0 : nonFilteredColumnCount * 30 ;
double duration = allFiltered ? 0 : fade_in_duration ;
2022-05-08 20:44:54 +08:00
float startingYPosition = 0 ;
2022-05-12 01:30:06 +08:00
if ( ! allFiltered )
2022-05-08 20:44:54 +08:00
startingYPosition = nonFilteredColumnCount % 2 = = 0 ? - distance : distance ;
column . TopLevelContent
. MoveToY ( startingYPosition )
. Delay ( delay )
. MoveToY ( 0 , duration , Easing . OutQuint )
. FadeIn ( duration , Easing . OutQuint ) ;
2022-05-12 01:30:06 +08:00
if ( ! allFiltered )
2022-05-08 20:44:54 +08:00
nonFilteredColumnCount + = 1 ;
2022-04-05 17:27:34 +08:00
}
2022-03-27 05:43:17 +08:00
}
protected override void PopOut ( )
{
2022-04-04 14:45:44 +08:00
const double fade_out_duration = 500 ;
2022-03-27 05:43:17 +08:00
base . PopOut ( ) ;
2022-04-04 14:45:44 +08:00
2022-03-28 04:55:52 +08:00
multiplierDisplay ?
2022-04-05 17:38:31 +08:00
. FadeOut ( fade_out_duration / 2 , Easing . OutQuint )
2022-04-05 17:25:27 +08:00
. ScaleTo ( 0.75f , fade_out_duration , Easing . OutQuint ) ;
2022-05-08 20:44:54 +08:00
int nonFilteredColumnCount = 0 ;
2022-04-05 17:27:34 +08:00
for ( int i = 0 ; i < columnFlow . Count ; i + + )
{
2022-04-18 02:32:45 +08:00
var column = columnFlow [ i ] . Column ;
2022-05-12 01:30:06 +08:00
bool allFiltered = column . AvailableMods . All ( modState = > modState . Filtered . Value ) ;
double duration = allFiltered ? 0 : fade_out_duration ;
2022-05-08 20:44:54 +08:00
float newYPosition = 0 ;
2022-05-12 01:30:06 +08:00
if ( ! allFiltered )
2022-05-08 20:44:54 +08:00
newYPosition = nonFilteredColumnCount % 2 = = 0 ? - distance : distance ;
2022-05-04 18:40:08 +08:00
column . FlushPendingSelections ( ) ;
2022-04-18 02:32:45 +08:00
column . TopLevelContent
2022-05-08 20:44:54 +08:00
. MoveToY ( newYPosition , duration , Easing . OutQuint )
. FadeOut ( duration , Easing . OutQuint ) ;
2022-05-12 01:30:06 +08:00
if ( ! allFiltered )
2022-05-08 20:44:54 +08:00
nonFilteredColumnCount + = 1 ;
2022-04-05 17:27:34 +08:00
}
2022-03-27 05:43:17 +08:00
}
2022-05-08 00:58:37 +08:00
#endregion
2022-05-06 22:31:59 +08:00
2022-05-08 00:58:37 +08:00
#region Input handling
2022-03-27 05:43:17 +08:00
2022-05-05 23:08:02 +08:00
public override bool OnPressed ( KeyBindingPressEvent < GlobalAction > e )
{
2022-05-06 03:22:07 +08:00
if ( e . Repeat )
return false ;
2022-05-08 00:30:21 +08:00
switch ( e . Action )
2022-05-06 03:22:07 +08:00
{
2022-05-08 11:56:07 +08:00
case GlobalAction . Back :
// Pressing the back binding should only go back one step at a time.
hideOverlay ( false ) ;
return true ;
// This is handled locally here because this overlay is being registered at the game level
// and therefore takes away keyboard focus from the screen stack.
2022-05-08 00:30:21 +08:00
case GlobalAction . ToggleModSelection :
case GlobalAction . Select :
{
2022-05-08 11:56:07 +08:00
// Pressing toggle or select should completely hide the overlay in one shot.
hideOverlay ( true ) ;
2022-05-08 00:30:21 +08:00
return true ;
}
2022-05-08 11:56:07 +08:00
}
return base . OnPressed ( e ) ;
void hideOverlay ( bool immediate )
{
if ( customisationVisible . Value )
{
Debug . Assert ( customisationButton ! = null ) ;
customisationButton . TriggerClick ( ) ;
if ( ! immediate )
return ;
}
2022-05-06 03:22:07 +08:00
2022-05-08 11:56:07 +08:00
backButton . TriggerClick ( ) ;
2022-05-08 00:30:21 +08:00
}
2022-05-05 23:08:02 +08:00
}
2022-05-08 00:58:37 +08:00
#endregion
2022-05-07 22:28:28 +08:00
#region Sample playback control
private readonly Bindable < bool > samplePlaybackDisabled = new BindableBool ( true ) ;
IBindable < bool > ISamplePlaybackDisabler . SamplePlaybackDisabled = > samplePlaybackDisabled ;
#endregion
2022-05-08 01:03:28 +08:00
/// <summary>
/// Manages horizontal scrolling of mod columns, along with the "active" states of each column based on visibility.
/// </summary>
2022-04-27 16:10:27 +08:00
internal class ColumnScrollContainer : OsuScrollContainer < ColumnFlowContainer >
2022-04-27 03:57:19 +08:00
{
2022-04-27 04:35:18 +08:00
public ColumnScrollContainer ( )
: base ( Direction . Horizontal )
{
}
2022-04-27 04:15:58 +08:00
2022-04-27 04:35:18 +08:00
protected override void Update ( )
2022-04-27 03:57:19 +08:00
{
2022-04-27 04:35:18 +08:00
base . Update ( ) ;
// the bounds below represent the horizontal range of scroll items to be considered fully visible/active, in the scroll's internal coordinate space.
// note that clamping is applied to the left scroll bound to ensure scrolling past extents does not change the set of active columns.
2022-04-27 04:43:58 +08:00
float leftVisibleBound = Math . Clamp ( Current , 0 , ScrollableExtent ) ;
float rightVisibleBound = leftVisibleBound + DrawWidth ;
// if a movement is occurring at this time, the bounds below represent the full range of columns that the scroll movement will encompass.
// this will be used to ensure that columns do not change state from active to inactive back and forth until they are fully scrolled past.
float leftMovementBound = Math . Min ( Current , Target ) ;
float rightMovementBound = Math . Max ( Current , Target ) + DrawWidth ;
2022-04-27 04:35:18 +08:00
foreach ( var column in Child )
{
// DrawWidth/DrawPosition do not include shear effects, and we want to know the full extents of the columns post-shear,
// so we have to manually compensate.
2022-04-27 04:54:54 +08:00
var topLeft = column . ToSpaceOfOtherDrawable ( Vector2 . Zero , ScrollContent ) ;
var bottomRight = column . ToSpaceOfOtherDrawable ( new Vector2 ( column . DrawWidth - column . DrawHeight * SHEAR , 0 ) , ScrollContent ) ;
2022-04-27 04:35:18 +08:00
2022-04-27 04:43:58 +08:00
bool isCurrentlyVisible = Precision . AlmostBigger ( topLeft . X , leftVisibleBound )
2022-04-27 04:54:54 +08:00
& & Precision . DefinitelyBigger ( rightVisibleBound , bottomRight . X ) ;
2022-04-27 04:43:58 +08:00
bool isBeingScrolledToward = Precision . AlmostBigger ( topLeft . X , leftMovementBound )
2022-04-27 04:54:54 +08:00
& & Precision . DefinitelyBigger ( rightMovementBound , bottomRight . X ) ;
2022-04-27 04:43:58 +08:00
column . Active . Value = isCurrentlyVisible | | isBeingScrolledToward ;
2022-04-27 04:35:18 +08:00
}
2022-04-27 03:57:19 +08:00
}
}
2022-05-08 01:03:28 +08:00
/// <summary>
2022-05-09 02:40:14 +08:00
/// Manages layout of mod columns.
2022-05-08 01:03:28 +08:00
/// </summary>
2022-04-27 16:10:27 +08:00
internal class ColumnFlowContainer : FillFlowContainer < ColumnDimContainer >
2022-03-27 05:43:17 +08:00
{
2022-04-25 01:13:19 +08:00
public IEnumerable < ModColumn > Columns = > Children . Select ( dimWrapper = > dimWrapper . Column ) ;
public override void Add ( ColumnDimContainer dimContainer )
2022-03-27 05:43:17 +08:00
{
2022-04-25 01:13:19 +08:00
base . Add ( dimContainer ) ;
2022-03-27 05:43:17 +08:00
2022-04-25 01:13:19 +08:00
Debug . Assert ( dimContainer ! = null ) ;
dimContainer . Column . Shear = Vector2 . Zero ;
2022-03-27 05:43:17 +08:00
}
}
2022-05-08 01:03:28 +08:00
/// <summary>
/// Encapsulates a column and provides dim and input blocking based on an externally managed "active" state.
/// </summary>
2022-04-27 05:11:38 +08:00
internal class ColumnDimContainer : Container
2022-04-25 01:13:19 +08:00
{
public ModColumn Column { get ; }
2022-05-08 01:03:28 +08:00
/// <summary>
/// Tracks whether this column is in an interactive state. Generally only the case when the column is on-screen.
/// </summary>
2022-04-27 03:57:19 +08:00
public readonly Bindable < bool > Active = new BindableBool ( ) ;
2022-05-08 01:03:28 +08:00
/// <summary>
/// Invoked when the column is clicked while not active, requesting a scroll to be performed to bring it on-screen.
/// </summary>
2022-04-27 03:57:19 +08:00
public Action < ColumnDimContainer > ? RequestScroll { get ; set ; }
2022-04-25 01:13:19 +08:00
[Resolved]
private OsuColour colours { get ; set ; } = null ! ;
2022-04-27 03:57:19 +08:00
public ColumnDimContainer ( ModColumn column )
2022-04-25 01:13:19 +08:00
{
Child = Column = column ;
2022-04-27 03:57:19 +08:00
column . Active . BindTo ( Active ) ;
2022-04-25 01:13:19 +08:00
}
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
2022-05-12 01:30:06 +08:00
Active . BindValueChanged ( _ = > updateState ( ) , true ) ;
2022-04-25 01:13:19 +08:00
FinishTransforms ( ) ;
}
2022-05-07 00:08:11 +08:00
protected override bool RequiresChildrenUpdate = > base . RequiresChildrenUpdate | | Column . SelectionAnimationRunning ;
2022-05-08 21:13:29 +08:00
private void updateState ( )
2022-04-25 01:13:19 +08:00
{
Colour4 targetColour ;
2022-05-08 21:13:29 +08:00
if ( Column . Active . Value )
2022-04-25 01:13:19 +08:00
targetColour = Colour4 . White ;
else
targetColour = IsHovered ? colours . GrayC : colours . Gray8 ;
2022-04-28 13:59:39 +08:00
this . FadeColour ( targetColour , 800 , Easing . OutQuint ) ;
2022-04-25 01:13:19 +08:00
}
2022-04-27 04:01:24 +08:00
protected override bool OnClick ( ClickEvent e )
2022-04-25 01:13:19 +08:00
{
2022-04-27 03:57:19 +08:00
if ( ! Active . Value )
RequestScroll ? . Invoke ( this ) ;
2022-04-25 01:13:19 +08:00
return true ;
}
protected override bool OnHover ( HoverEvent e )
{
base . OnHover ( e ) ;
2022-05-08 21:13:29 +08:00
updateState ( ) ;
2022-04-27 03:57:19 +08:00
return Active . Value ;
2022-04-25 01:13:19 +08:00
}
protected override void OnHoverLost ( HoverLostEvent e )
{
base . OnHoverLost ( e ) ;
2022-05-08 21:13:29 +08:00
updateState ( ) ;
2022-04-25 01:13:19 +08:00
}
}
2022-05-08 01:03:28 +08:00
/// <summary>
/// A container which blocks and handles input, managing the "return from customisation" state change.
/// </summary>
2022-03-27 05:43:17 +08:00
private class ClickToReturnContainer : Container
{
public BindableBool HandleMouse { get ; } = new BindableBool ( ) ;
2022-03-28 04:55:52 +08:00
public Action ? OnClicked { get ; set ; }
2022-03-27 05:43:17 +08:00
2022-05-06 18:26:09 +08:00
public override bool HandlePositionalInput = > base . HandlePositionalInput & & HandleMouse . Value ;
2022-03-27 05:43:17 +08:00
protected override bool Handle ( UIEvent e )
{
if ( ! HandleMouse . Value )
return base . Handle ( e ) ;
switch ( e )
{
case ClickEvent _ :
OnClicked ? . Invoke ( ) ;
return true ;
case MouseEvent _ :
return true ;
}
return base . Handle ( e ) ;
}
}
}
}