2019-03-12 08:27:20 +00:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2019-01-24 08:43:03 +00:00
// See the LICENCE file in the repository root for full licence text.
2018-04-13 09:19:50 +00:00
using osu.Framework.Allocation ;
using osu.Framework.Audio ;
using osu.Framework.Audio.Sample ;
using osu.Framework.Audio.Track ;
2019-02-21 10:04:31 +00:00
using osu.Framework.Bindables ;
2018-04-13 09:19:50 +00:00
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
2018-10-02 03:02:47 +00:00
using osu.Framework.Input.Events ;
2019-01-04 19:13:32 +00:00
using osu.Framework.Logging ;
2018-04-13 09:19:50 +00:00
using osu.Framework.Screens ;
using osu.Framework.Threading ;
using osu.Game.Beatmaps ;
using osu.Game.Graphics ;
using osu.Game.Graphics.Containers ;
2018-07-03 09:37:21 +00:00
using osu.Game.Input.Bindings ;
2018-04-13 09:19:50 +00:00
using osu.Game.Overlays ;
2018-12-06 10:29:18 +00:00
using osu.Game.Overlays.Mods ;
2018-06-26 07:32:20 +00:00
using osu.Game.Rulesets ;
2018-07-04 03:40:55 +00:00
using osu.Game.Rulesets.Mods ;
2018-04-13 09:19:50 +00:00
using osu.Game.Screens.Backgrounds ;
using osu.Game.Screens.Edit ;
using osu.Game.Screens.Menu ;
2018-12-21 07:28:33 +00:00
using osu.Game.Screens.Play ;
2018-04-13 09:19:50 +00:00
using osu.Game.Screens.Select.Options ;
2018-12-12 04:21:44 +00:00
using osu.Game.Skinning ;
2019-01-04 19:13:32 +00:00
using osuTK ;
2019-02-04 08:04:52 +00:00
using osuTK.Graphics ;
2019-01-04 19:13:32 +00:00
using osuTK.Input ;
using System ;
using System.Collections.Generic ;
using System.Linq ;
2019-06-10 03:46:21 +00:00
using System.Threading.Tasks ;
2019-03-27 10:29:27 +00:00
using osu.Framework.Graphics.Sprites ;
2019-06-25 08:17:29 +00:00
using osu.Framework.Input.Bindings ;
2019-07-05 05:25:52 +00:00
using osu.Game.Scoring ;
2018-04-13 09:19:50 +00:00
namespace osu.Game.Screens.Select
{
2019-06-25 08:17:29 +00:00
public abstract class SongSelect : OsuScreen , IKeyBindingHandler < GlobalAction >
2018-04-13 09:19:50 +00:00
{
private static readonly Vector2 wedged_container_size = new Vector2 ( 0.5f , 245 ) ;
2019-05-12 06:40:58 +00:00
2019-03-14 07:09:17 +00:00
protected const float BACKGROUND_BLUR = 20 ;
2018-04-13 09:19:50 +00:00
private const float left_area_padding = 20 ;
public readonly FilterControl FilterControl ;
protected virtual bool ShowFooter = > true ;
/// <summary>
/// Can be null if <see cref="ShowFooter"/> is false.
/// </summary>
protected readonly BeatmapOptionsOverlay BeatmapOptions ;
/// <summary>
/// Can be null if <see cref="ShowFooter"/> is false.
/// </summary>
protected readonly Footer Footer ;
/// <summary>
/// Contains any panel which is triggered by a footer button.
/// Helps keep them located beneath the footer itself.
/// </summary>
protected readonly Container FooterPanels ;
2019-03-12 08:27:20 +00:00
protected override BackgroundScreen CreateBackground ( )
{
var background = new BackgroundScreenBeatmap ( ) ;
return background ;
}
2018-04-13 09:19:50 +00:00
protected readonly BeatmapCarousel Carousel ;
private readonly BeatmapInfoWedge beatmapInfoWedge ;
private DialogOverlay dialogOverlay ;
private BeatmapManager beatmaps ;
2018-12-06 10:29:18 +00:00
protected readonly ModSelectOverlay ModSelect ;
2018-12-12 04:21:44 +00:00
protected SampleChannel SampleConfirm ;
2018-04-13 09:19:50 +00:00
private SampleChannel sampleChangeDifficulty ;
private SampleChannel sampleChangeBeatmap ;
2018-12-12 04:21:44 +00:00
protected readonly BeatmapDetailArea BeatmapDetails ;
2018-07-04 03:40:55 +00:00
2019-02-01 06:42:15 +00:00
private readonly Bindable < RulesetInfo > decoupledRuleset = new Bindable < RulesetInfo > ( ) ;
2018-07-04 03:40:55 +00:00
2019-07-10 15:20:01 +00:00
[Resolved(canBeNull: true)]
private MusicController music { get ; set ; }
2019-07-09 09:32:49 +00:00
2018-12-12 04:21:44 +00:00
[Cached]
2019-04-10 08:13:12 +00:00
[Cached(Type = typeof(IBindable<IReadOnlyList<Mod>>))]
private readonly Bindable < IReadOnlyList < Mod > > mods = new Bindable < IReadOnlyList < Mod > > ( Array . Empty < Mod > ( ) ) ; // Bound to the game's mods, but is not reset on exiting
2018-04-13 09:19:50 +00:00
protected SongSelect ( )
{
2019-01-23 11:52:00 +00:00
AddRangeInternal ( new Drawable [ ]
2018-04-13 09:19:50 +00:00
{
new ParallaxContainer
{
2019-03-27 09:12:04 +00:00
Masking = true ,
2018-04-13 09:19:50 +00:00
ParallaxAmount = 0.005f ,
RelativeSizeAxes = Axes . Both ,
Children = new [ ]
{
new WedgeBackground
{
RelativeSizeAxes = Axes . Both ,
2019-05-12 03:08:45 +00:00
Padding = new MarginPadding { Right = - 150 } ,
Size = new Vector2 ( wedged_container_size . X , 1 ) ,
2018-04-13 09:19:50 +00:00
}
}
} ,
2018-12-12 04:21:44 +00:00
new Container
2018-04-13 09:19:50 +00:00
{
Origin = Anchor . BottomLeft ,
Anchor = Anchor . BottomLeft ,
RelativeSizeAxes = Axes . Both ,
Size = new Vector2 ( wedged_container_size . X , 1 ) ,
Padding = new MarginPadding
{
2019-07-26 04:07:28 +00:00
Bottom = Footer . HEIGHT ,
2018-04-13 09:19:50 +00:00
Top = wedged_container_size . Y + left_area_padding ,
Left = left_area_padding ,
Right = left_area_padding * 2 ,
2018-12-12 04:21:44 +00:00
} ,
Child = BeatmapDetails = new BeatmapDetailArea
{
RelativeSizeAxes = Axes . Both ,
Padding = new MarginPadding { Top = 10 , Right = 5 } ,
2018-04-13 09:19:50 +00:00
}
} ,
new Container
{
RelativeSizeAxes = Axes . Both ,
Masking = true ,
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
Width = 2 , //avoid horizontal masking so the panels don't clip when screen stack is pushed.
Child = new Container
{
RelativeSizeAxes = Axes . Both ,
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
Width = 0.5f ,
Children = new Drawable [ ]
{
2019-07-25 06:31:21 +00:00
new Container
2018-04-13 09:19:50 +00:00
{
2019-05-12 03:08:45 +00:00
RelativeSizeAxes = Axes . Both ,
2019-07-25 06:31:21 +00:00
Padding = new MarginPadding
{
2019-07-26 04:07:28 +00:00
Top = FilterControl . HEIGHT ,
Bottom = Footer . HEIGHT
2019-07-25 06:31:21 +00:00
} ,
Child = Carousel = new BeatmapCarousel
{
RelativeSizeAxes = Axes . Both ,
Size = new Vector2 ( 1 - wedged_container_size . X , 1 ) ,
Anchor = Anchor . CentreRight ,
Origin = Anchor . CentreRight ,
SelectionChanged = updateSelectedBeatmap ,
BeatmapSetsChanged = carouselBeatmapsLoaded ,
} ,
2018-04-13 09:19:50 +00:00
} ,
FilterControl = new FilterControl
{
RelativeSizeAxes = Axes . X ,
2019-07-26 04:07:28 +00:00
Height = FilterControl . HEIGHT ,
2018-04-13 09:19:50 +00:00
FilterChanged = c = > Carousel . Filter ( c ) ,
Background = { Width = 2 } ,
2018-07-09 09:12:23 +00:00
Exit = ( ) = >
{
2019-01-23 11:52:00 +00:00
if ( this . IsCurrentScreen ( ) )
this . Exit ( ) ;
2018-07-09 09:12:23 +00:00
} ,
2018-04-13 09:19:50 +00:00
} ,
}
} ,
} ,
2019-01-30 15:02:05 +00:00
beatmapInfoWedge = new BeatmapInfoWedge
{
Size = wedged_container_size ,
RelativeSizeAxes = Axes . X ,
Margin = new MarginPadding
{
Top = left_area_padding ,
Right = left_area_padding ,
} ,
} ,
2018-04-13 09:19:50 +00:00
new ResetScrollContainer ( ( ) = > Carousel . ScrollToSelected ( ) )
{
RelativeSizeAxes = Axes . Y ,
Width = 250 ,
}
} ) ;
if ( ShowFooter )
{
2019-06-25 07:55:49 +00:00
AddRangeInternal ( new [ ]
2018-04-13 09:19:50 +00:00
{
2019-06-25 07:55:49 +00:00
FooterPanels = new Container
2018-12-06 10:29:18 +00:00
{
2019-06-25 07:55:49 +00:00
Anchor = Anchor . BottomLeft ,
Origin = Anchor . BottomLeft ,
2018-12-06 10:29:18 +00:00
RelativeSizeAxes = Axes . X ,
2019-06-25 07:55:49 +00:00
AutoSizeAxes = Axes . Y ,
Margin = new MarginPadding { Bottom = Footer . HEIGHT } ,
Children = new Drawable [ ]
{
BeatmapOptions = new BeatmapOptionsOverlay ( ) ,
ModSelect = new ModSelectOverlay
{
RelativeSizeAxes = Axes . X ,
Origin = Anchor . BottomCentre ,
Anchor = Anchor . BottomCentre ,
}
}
} ,
Footer = new Footer ( )
2018-12-06 10:29:18 +00:00
} ) ;
2018-04-13 09:19:50 +00:00
}
2019-07-22 14:13:48 +00:00
void displayScore ( ScoreInfo score ) = > this . Push ( new SoloResults ( score ) ) ;
BeatmapDetails . Leaderboard . ScoreSelected + = displayScore ;
2018-12-12 04:21:44 +00:00
}
2018-12-04 03:06:05 +00:00
2018-06-06 11:19:53 +00:00
[BackgroundDependencyLoader(true)]
2019-07-05 05:25:52 +00:00
private void load ( BeatmapManager beatmaps , AudioManager audio , DialogOverlay dialog , OsuColour colours , SkinManager skins , ScoreManager scores )
2018-04-13 09:19:50 +00:00
{
if ( Footer ! = null )
{
2019-07-17 05:46:25 +00:00
Footer . AddButton ( new FooterButtonMods { Current = mods } , ModSelect ) ;
2019-05-08 10:29:43 +00:00
Footer . AddButton ( new FooterButtonRandom { Action = triggerRandom } ) ;
Footer . AddButton ( new FooterButtonOptions ( ) , BeatmapOptions ) ;
2018-04-13 09:19:50 +00:00
2019-04-02 10:55:24 +00:00
BeatmapOptions . AddButton ( @"Delete" , @"all difficulties" , FontAwesome . Solid . Trash , colours . Pink , ( ) = > delete ( Beatmap . Value . BeatmapSetInfo ) , Key . Number4 , float . MaxValue ) ;
BeatmapOptions . AddButton ( @"Remove" , @"from unplayed" , FontAwesome . Regular . TimesCircle , colours . Purple , null , Key . Number1 ) ;
BeatmapOptions . AddButton ( @"Clear" , @"local scores" , FontAwesome . Solid . Eraser , colours . Purple , ( ) = > clearScores ( Beatmap . Value . BeatmapInfo ) , Key . Number2 ) ;
2018-04-13 09:19:50 +00:00
}
if ( this . beatmaps = = null )
this . beatmaps = beatmaps ;
this . beatmaps . ItemAdded + = onBeatmapSetAdded ;
this . beatmaps . ItemRemoved + = onBeatmapSetRemoved ;
this . beatmaps . BeatmapHidden + = onBeatmapHidden ;
this . beatmaps . BeatmapRestored + = onBeatmapRestored ;
dialogOverlay = dialog ;
2019-05-28 08:06:01 +00:00
sampleChangeDifficulty = audio . Samples . Get ( @"SongSelect/select-difficulty" ) ;
sampleChangeBeatmap = audio . Samples . Get ( @"SongSelect/select-expand" ) ;
SampleConfirm = audio . Samples . Get ( @"SongSelect/confirm-selection" ) ;
2018-04-13 09:19:50 +00:00
2018-12-12 04:21:44 +00:00
if ( dialogOverlay ! = null )
{
Schedule ( ( ) = >
{
// if we have no beatmaps but osu-stable is found, let's prompt the user to import.
2019-07-16 05:57:11 +00:00
if ( ! beatmaps . GetAllUsableBeatmapSetsEnumerable ( ) . Any ( ) & & beatmaps . StableInstallationAvailable )
2018-12-12 04:21:44 +00:00
dialogOverlay . Push ( new ImportFromStablePopup ( ( ) = >
{
2019-07-05 05:25:52 +00:00
Task . Run ( beatmaps . ImportFromStableAsync ) . ContinueWith ( _ = > scores . ImportFromStableAsync ( ) , TaskContinuationOptions . OnlyOnRanToCompletion ) ;
2019-06-10 03:46:21 +00:00
Task . Run ( skins . ImportFromStableAsync ) ;
2018-12-12 04:21:44 +00:00
} ) ) ;
} ) ;
}
}
2019-07-17 05:46:25 +00:00
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
mods . BindTo ( Mods ) ;
}
2018-12-12 04:21:44 +00:00
private DependencyContainer dependencies ;
protected override IReadOnlyDependencyContainer CreateChildDependencies ( IReadOnlyDependencyContainer parent )
{
dependencies = new DependencyContainer ( base . CreateChildDependencies ( parent ) ) ;
2019-04-08 10:16:34 +00:00
2018-12-12 04:21:44 +00:00
dependencies . CacheAs ( this ) ;
2019-02-01 06:42:15 +00:00
dependencies . CacheAs ( decoupledRuleset ) ;
dependencies . CacheAs < IBindable < RulesetInfo > > ( decoupledRuleset ) ;
2018-12-12 04:21:44 +00:00
return dependencies ;
}
2018-10-29 04:04:51 +00:00
public void Edit ( BeatmapInfo beatmap = null )
2018-04-13 09:19:50 +00:00
{
2018-10-29 04:04:51 +00:00
Beatmap . Value = beatmaps . GetWorkingBeatmap ( beatmap ? ? beatmapNoDebounce ) ;
2019-02-27 12:07:17 +00:00
this . Push ( new Editor ( ) ) ;
2018-04-13 09:19:50 +00:00
}
/// <summary>
/// Call to make a selection and perform the default action for this SongSelect.
/// </summary>
/// <param name="beatmap">An optional beatmap to override the current carousel selection.</param>
2018-05-30 06:44:35 +00:00
/// <param name="performStartAction">Whether to trigger <see cref="OnStart"/>.</param>
public void FinaliseSelection ( BeatmapInfo beatmap = null , bool performStartAction = true )
2018-04-13 09:19:50 +00:00
{
2019-03-21 11:52:34 +00:00
// This is very important as we have not yet bound to screen-level bindables before the carousel load is completed.
if ( ! Carousel . BeatmapSetsLoaded )
return ;
2018-04-13 09:19:50 +00:00
// if we have a pending filter operation, we want to run it now.
// it could change selection (ie. if the ruleset has been changed).
Carousel . FlushPendingFilterOperations ( ) ;
2019-01-08 09:06:46 +00:00
// avoid attempting to continue before a selection has been obtained.
// this could happen via a user interaction while the carousel is still in a loading state.
if ( Carousel . SelectedBeatmap = = null ) return ;
2018-04-13 09:19:50 +00:00
if ( beatmap ! = null )
Carousel . SelectBeatmap ( beatmap ) ;
if ( selectionChangedDebounce ? . Completed = = false )
{
selectionChangedDebounce . RunTask ( ) ;
selectionChangedDebounce . Cancel ( ) ; // cancel the already scheduled task.
selectionChangedDebounce = null ;
}
2018-05-30 06:44:35 +00:00
if ( performStartAction )
OnStart ( ) ;
2018-04-13 09:19:50 +00:00
}
/// <summary>
/// Called when a selection is made.
/// </summary>
/// <returns>If a resultant action occurred that takes the user away from SongSelect.</returns>
2018-05-30 06:44:35 +00:00
protected abstract bool OnStart ( ) ;
2018-04-13 09:19:50 +00:00
private ScheduledDelegate selectionChangedDebounce ;
2019-02-21 09:56:34 +00:00
private void workingBeatmapChanged ( ValueChangedEvent < WorkingBeatmap > e )
2018-04-13 09:19:50 +00:00
{
2019-02-21 09:56:34 +00:00
if ( e . NewValue is DummyWorkingBeatmap ) return ;
2018-04-13 09:19:50 +00:00
2019-02-21 09:56:34 +00:00
if ( this . IsCurrentScreen ( ) & & ! Carousel . SelectBeatmap ( e . NewValue ? . BeatmapInfo , false ) )
2018-04-13 09:19:50 +00:00
// If selecting new beatmap without bypassing filters failed, there's possibly a ruleset mismatch
2019-07-15 06:53:19 +00:00
if ( e . NewValue ? . BeatmapInfo ? . Ruleset ! = null & & ! e . NewValue . BeatmapInfo . Ruleset . Equals ( decoupledRuleset . Value ) )
2018-04-13 09:19:50 +00:00
{
2019-02-21 09:56:34 +00:00
Ruleset . Value = e . NewValue . BeatmapInfo . Ruleset ;
Carousel . SelectBeatmap ( e . NewValue . BeatmapInfo ) ;
2018-04-13 09:19:50 +00:00
}
}
2018-06-29 10:32:42 +00:00
// We need to keep track of the last selected beatmap ignoring debounce to play the correct selection sounds.
private BeatmapInfo beatmapNoDebounce ;
private RulesetInfo rulesetNoDebounce ;
2018-07-19 09:48:40 +00:00
private void updateSelectedBeatmap ( BeatmapInfo beatmap )
{
if ( beatmap ? . Equals ( beatmapNoDebounce ) = = true )
return ;
beatmapNoDebounce = beatmap ;
2019-08-19 02:30:04 +00:00
2018-07-19 09:48:40 +00:00
performUpdateSelected ( ) ;
}
private void updateSelectedRuleset ( RulesetInfo ruleset )
{
if ( ruleset ? . Equals ( rulesetNoDebounce ) = = true )
return ;
rulesetNoDebounce = ruleset ;
performUpdateSelected ( ) ;
}
2018-04-13 09:19:50 +00:00
/// <summary>
2018-06-29 10:32:42 +00:00
/// selection has been changed as the result of a user interaction.
2018-04-13 09:19:50 +00:00
/// </summary>
2018-07-19 09:48:40 +00:00
private void performUpdateSelected ( )
2018-04-13 09:19:50 +00:00
{
2018-07-19 09:48:40 +00:00
var beatmap = beatmapNoDebounce ;
var ruleset = rulesetNoDebounce ;
2018-06-29 10:32:42 +00:00
2019-03-21 11:51:21 +00:00
selectionChangedDebounce ? . Cancel ( ) ;
if ( beatmap = = null )
run ( ) ;
else
selectionChangedDebounce = Scheduler . AddDelayed ( run , 200 ) ;
2018-07-20 02:32:00 +00:00
void run ( )
2018-04-13 09:19:50 +00:00
{
2018-07-20 02:32:00 +00:00
Logger . Log ( $"updating selection with beatmap:{beatmap?.ID.ToString() ?? " null "} ruleset:{ruleset?.ID.ToString() ?? " null "}" ) ;
2018-07-19 09:51:08 +00:00
2019-02-01 06:42:15 +00:00
if ( ruleset ? . Equals ( decoupledRuleset . Value ) = = false )
2018-07-17 13:20:19 +00:00
{
2019-02-01 06:42:15 +00:00
Logger . Log ( $"ruleset changed from \" { decoupledRuleset . Value } \ " to \"{ruleset}\"" ) ;
2018-08-14 04:19:50 +00:00
2019-04-10 08:13:12 +00:00
mods . Value = Array . Empty < Mod > ( ) ;
2019-02-01 06:42:15 +00:00
decoupledRuleset . Value = ruleset ;
2018-07-17 13:20:19 +00:00
// force a filter before attempting to change the beatmap.
// we may still be in the wrong ruleset as there is a debounce delay on ruleset changes.
Carousel . Filter ( null , false ) ;
2018-07-20 02:32:00 +00:00
// Filtering only completes after the carousel runs Update.
// If we also have a pending beatmap change we should delay it one frame.
selectionChangedDebounce = Schedule ( run ) ;
return ;
2018-07-17 13:20:19 +00:00
}
2018-04-13 09:19:50 +00:00
// We may be arriving here due to another component changing the bindable Beatmap.
// In these cases, the other component has already loaded the beatmap, so we don't need to do so again.
2018-07-19 09:48:40 +00:00
if ( ! Equals ( beatmap , Beatmap . Value . BeatmapInfo ) )
2018-04-13 09:19:50 +00:00
{
2018-07-20 02:32:00 +00:00
Logger . Log ( $"beatmap changed from \" { Beatmap . Value . BeatmapInfo } \ " to \"{beatmap}\"" ) ;
2018-07-19 09:51:08 +00:00
2019-09-01 00:17:55 +00:00
WorkingBeatmap previous = Beatmap . Value ;
Beatmap . Value = beatmaps . GetWorkingBeatmap ( beatmap , previous ) ;
if ( this . IsCurrentScreen ( ) & & Beatmap . Value ? . Track ! = previous ? . Track )
ensurePlayingSelected ( ) ;
2018-07-19 09:48:40 +00:00
if ( beatmap ! = null )
{
if ( beatmap . BeatmapSetInfoID = = beatmapNoDebounce ? . BeatmapSetInfoID )
sampleChangeDifficulty . Play ( ) ;
else
sampleChangeBeatmap . Play ( ) ;
}
2018-04-13 09:19:50 +00:00
}
UpdateBeatmap ( Beatmap . Value ) ;
}
}
private void triggerRandom ( )
{
if ( GetContainingInputManager ( ) . CurrentState . Keyboard . ShiftPressed )
Carousel . SelectPreviousRandom ( ) ;
else
Carousel . SelectNextRandom ( ) ;
}
2019-01-23 11:52:00 +00:00
public override void OnEntering ( IScreen last )
2018-04-13 09:19:50 +00:00
{
base . OnEntering ( last ) ;
2019-01-23 11:52:00 +00:00
this . FadeInFromZero ( 250 ) ;
2018-04-13 09:19:50 +00:00
FilterControl . Activate ( ) ;
}
private const double logo_transition = 250 ;
protected override void LogoArriving ( OsuLogo logo , bool resuming )
{
base . LogoArriving ( logo , resuming ) ;
Vector2 position = new Vector2 ( 0.95f , 0.96f ) ;
if ( logo . Alpha > 0.8f )
{
logo . MoveTo ( position , 500 , Easing . OutQuint ) ;
}
else
{
logo . Hide ( ) ;
logo . ScaleTo ( 0.2f ) ;
logo . MoveTo ( position ) ;
}
logo . FadeIn ( logo_transition , Easing . OutQuint ) ;
logo . ScaleTo ( 0.4f , logo_transition , Easing . OutQuint ) ;
logo . Action = ( ) = >
{
FinaliseSelection ( ) ;
return false ;
} ;
}
protected override void LogoExiting ( OsuLogo logo )
{
base . LogoExiting ( logo ) ;
logo . ScaleTo ( 0.2f , logo_transition / 2 , Easing . Out ) ;
logo . FadeOut ( logo_transition / 2 , Easing . Out ) ;
}
2019-01-23 11:52:00 +00:00
public override void OnResuming ( IScreen last )
2018-04-13 09:19:50 +00:00
{
2018-12-12 04:21:44 +00:00
BeatmapDetails . Leaderboard . RefreshScores ( ) ;
Beatmap . Value . Track . Looping = true ;
2018-04-13 09:19:50 +00:00
if ( Beatmap ! = null & & ! Beatmap . Value . BeatmapSetInfo . DeletePending )
{
2018-05-23 08:37:39 +00:00
UpdateBeatmap ( Beatmap . Value ) ;
2018-04-13 09:19:50 +00:00
ensurePlayingSelected ( ) ;
}
base . OnResuming ( last ) ;
2019-01-23 11:52:00 +00:00
this . FadeIn ( 250 ) ;
2018-04-13 09:19:50 +00:00
2019-01-23 11:52:00 +00:00
this . ScaleTo ( 1 , 250 , Easing . OutSine ) ;
2018-04-13 09:19:50 +00:00
FilterControl . Activate ( ) ;
}
2019-01-23 11:52:00 +00:00
public override void OnSuspending ( IScreen next )
2018-04-13 09:19:50 +00:00
{
2018-12-06 10:29:18 +00:00
ModSelect . Hide ( ) ;
2019-05-12 04:26:42 +00:00
BeatmapOptions . Hide ( ) ;
2019-01-23 11:52:00 +00:00
this . ScaleTo ( 1.1f , 250 , Easing . InSine ) ;
2018-04-13 09:19:50 +00:00
2019-01-23 11:52:00 +00:00
this . FadeOut ( 250 ) ;
2018-04-13 09:19:50 +00:00
FilterControl . Deactivate ( ) ;
base . OnSuspending ( next ) ;
}
2019-01-23 11:52:00 +00:00
public override bool OnExiting ( IScreen next )
2018-04-13 09:19:50 +00:00
{
2019-06-25 08:27:13 +00:00
if ( ModSelect . State . Value = = Visibility . Visible )
{
ModSelect . Hide ( ) ;
return true ;
}
2019-02-25 09:40:19 +00:00
if ( base . OnExiting ( next ) )
return true ;
2019-06-11 05:28:52 +00:00
beatmapInfoWedge . Hide ( ) ;
2018-04-13 09:19:50 +00:00
2019-01-23 11:52:00 +00:00
this . FadeOut ( 100 ) ;
2018-04-13 09:19:50 +00:00
FilterControl . Deactivate ( ) ;
2018-12-12 04:21:44 +00:00
if ( Beatmap . Value . Track ! = null )
Beatmap . Value . Track . Looping = false ;
2019-04-10 03:03:57 +00:00
mods . UnbindAll ( ) ;
2019-04-10 08:13:12 +00:00
Mods . Value = Array . Empty < Mod > ( ) ;
2018-12-12 04:21:44 +00:00
2019-02-25 09:40:19 +00:00
return false ;
2018-04-13 09:19:50 +00:00
}
protected override void Dispose ( bool isDisposing )
{
base . Dispose ( isDisposing ) ;
2019-02-01 06:42:15 +00:00
decoupledRuleset . UnbindAll ( ) ;
2018-08-20 05:27:32 +00:00
2018-04-13 09:19:50 +00:00
if ( beatmaps ! = null )
{
beatmaps . ItemAdded - = onBeatmapSetAdded ;
beatmaps . ItemRemoved - = onBeatmapSetRemoved ;
beatmaps . BeatmapHidden - = onBeatmapHidden ;
beatmaps . BeatmapRestored - = onBeatmapRestored ;
}
}
/// <summary>
/// Allow components in SongSelect to update their loaded beatmap details.
/// This is a debounced call (unlike directly binding to WorkingBeatmap.ValueChanged).
/// </summary>
/// <param name="beatmap">The working beatmap.</param>
protected virtual void UpdateBeatmap ( WorkingBeatmap beatmap )
{
2018-07-19 09:51:08 +00:00
Logger . Log ( $"working beatmap updated to {beatmap}" ) ;
2018-04-13 09:19:50 +00:00
if ( Background is BackgroundScreenBeatmap backgroundModeBeatmap )
{
backgroundModeBeatmap . Beatmap = beatmap ;
2019-03-20 05:17:35 +00:00
backgroundModeBeatmap . BlurAmount . Value = BACKGROUND_BLUR ;
2019-02-04 08:04:52 +00:00
backgroundModeBeatmap . FadeColour ( Color4 . White , 250 ) ;
2018-04-13 09:19:50 +00:00
}
2018-06-01 09:03:16 +00:00
beatmapInfoWedge . Beatmap = beatmap ;
2018-12-12 04:21:44 +00:00
BeatmapDetails . Beatmap = beatmap ;
if ( beatmap . Track ! = null )
beatmap . Track . Looping = true ;
2018-04-13 09:19:50 +00:00
}
2019-01-31 11:24:47 +00:00
private void ensurePlayingSelected ( bool restart = false )
2018-04-13 09:19:50 +00:00
{
Track track = Beatmap . Value . Track ;
2019-08-19 02:30:04 +00:00
if ( ! track . IsRunning | | restart )
2018-04-13 09:19:50 +00:00
{
2019-01-31 11:24:47 +00:00
track . RestartPoint = Beatmap . Value . Metadata . PreviewTime ;
2019-08-19 02:30:04 +00:00
if ( music ! = null )
{
// use the global music controller (when available) to cancel a potential local user paused state.
music . SeekTo ( track . RestartPoint ) ;
music . Play ( ) ;
}
else
track . Restart ( ) ;
2018-04-13 09:19:50 +00:00
}
}
2019-06-26 02:40:33 +00:00
private void onBeatmapSetAdded ( BeatmapSetInfo s ) = > Carousel . UpdateBeatmapSet ( s ) ;
2018-04-13 09:19:50 +00:00
private void onBeatmapSetRemoved ( BeatmapSetInfo s ) = > Carousel . RemoveBeatmapSet ( s ) ;
private void onBeatmapRestored ( BeatmapInfo b ) = > Carousel . UpdateBeatmapSet ( beatmaps . QueryBeatmapSet ( s = > s . ID = = b . BeatmapSetInfoID ) ) ;
private void onBeatmapHidden ( BeatmapInfo b ) = > Carousel . UpdateBeatmapSet ( beatmaps . QueryBeatmapSet ( s = > s . ID = = b . BeatmapSetInfoID ) ) ;
private void carouselBeatmapsLoaded ( )
{
2019-03-21 11:52:15 +00:00
bindBindables ( ) ;
2018-07-17 13:20:19 +00:00
2019-06-12 07:07:35 +00:00
// If a selection was already obtained, do not attempt to update the selected beatmap.
2019-05-28 05:04:33 +00:00
if ( Carousel . SelectedBeatmapSet ! = null )
return ;
// Attempt to select the current beatmap on the carousel, if it is valid to be selected.
2018-07-04 03:40:55 +00:00
if ( ! Beatmap . IsDefault & & Beatmap . Value . BeatmapSetInfo ? . DeletePending = = false & & Beatmap . Value . BeatmapSetInfo ? . Protected = = false
& & Carousel . SelectBeatmap ( Beatmap . Value . BeatmapInfo , false ) )
2018-04-13 09:19:50 +00:00
return ;
2019-05-28 05:04:33 +00:00
// If the current active beatmap could not be selected, select a new random beatmap.
if ( ! Carousel . SelectNextRandom ( ) )
2018-04-13 09:19:50 +00:00
{
// in the case random selection failed, we want to trigger selectionChanged
// to show the dummy beatmap (we have nothing else to display).
2018-07-19 09:48:40 +00:00
performUpdateSelected ( ) ;
2018-04-13 09:19:50 +00:00
}
}
2019-03-21 11:52:15 +00:00
private bool boundLocalBindables ;
private void bindBindables ( )
2019-03-21 11:52:34 +00:00
{
2019-03-21 11:52:15 +00:00
if ( boundLocalBindables )
return ;
2019-03-21 11:52:34 +00:00
// manual binding to parent ruleset to allow for delayed load in the incoming direction.
rulesetNoDebounce = decoupledRuleset . Value = Ruleset . Value ;
Ruleset . ValueChanged + = r = > updateSelectedRuleset ( r . NewValue ) ;
2019-03-21 11:52:15 +00:00
2019-03-21 11:52:34 +00:00
decoupledRuleset . ValueChanged + = r = > Ruleset . Value = r . NewValue ;
decoupledRuleset . DisabledChanged + = r = > Ruleset . Disabled = r ;
2019-03-21 11:52:15 +00:00
2019-03-21 11:52:34 +00:00
Beatmap . BindDisabledChanged ( disabled = > Carousel . AllowSelection = ! disabled , true ) ;
Beatmap . BindValueChanged ( workingBeatmapChanged ) ;
2019-03-21 11:52:15 +00:00
boundLocalBindables = true ;
}
2018-04-13 09:19:50 +00:00
private void delete ( BeatmapSetInfo beatmap )
{
2018-09-21 02:29:37 +00:00
if ( beatmap = = null | | beatmap . ID < = 0 ) return ;
2019-02-27 12:07:17 +00:00
2018-04-13 09:19:50 +00:00
dialogOverlay ? . Push ( new BeatmapDeleteDialog ( beatmap ) ) ;
}
2019-01-08 16:57:03 +00:00
private void clearScores ( BeatmapInfo beatmap )
2019-01-04 19:13:32 +00:00
{
if ( beatmap = = null | | beatmap . ID < = 0 ) return ;
2019-03-01 11:52:34 +00:00
dialogOverlay ? . Push ( new BeatmapClearScoresDialog ( beatmap , ( ) = >
// schedule done here rather than inside the dialog as the dialog may fade out and never callback.
Schedule ( ( ) = > BeatmapDetails . Leaderboard . RefreshScores ( ) ) ) ) ;
2019-01-04 19:13:32 +00:00
}
2019-06-25 08:17:29 +00:00
public virtual bool OnPressed ( GlobalAction action )
2018-07-03 09:37:21 +00:00
{
2019-01-23 11:52:00 +00:00
if ( ! this . IsCurrentScreen ( ) ) return false ;
2018-07-03 09:37:21 +00:00
switch ( action )
{
case GlobalAction . Select :
FinaliseSelection ( ) ;
return true ;
}
2019-06-25 08:17:29 +00:00
return false ;
2018-07-03 09:37:21 +00:00
}
2019-06-25 08:17:29 +00:00
public bool OnReleased ( GlobalAction action ) = > action = = GlobalAction . Select ;
2018-10-02 03:02:47 +00:00
protected override bool OnKeyDown ( KeyDownEvent e )
2018-04-13 09:19:50 +00:00
{
2018-10-02 03:02:47 +00:00
if ( e . Repeat ) return false ;
2018-04-13 09:19:50 +00:00
2018-10-02 03:02:47 +00:00
switch ( e . Key )
2018-04-13 09:19:50 +00:00
{
case Key . Delete :
2018-10-02 03:44:14 +00:00
if ( e . ShiftPressed )
2018-04-13 09:19:50 +00:00
{
if ( ! Beatmap . IsDefault )
delete ( Beatmap . Value . BeatmapSetInfo ) ;
return true ;
}
break ;
}
2018-10-02 03:02:47 +00:00
return base . OnKeyDown ( e ) ;
2018-04-13 09:19:50 +00:00
}
private class ResetScrollContainer : Container
{
private readonly Action onHoverAction ;
public ResetScrollContainer ( Action onHoverAction )
{
this . onHoverAction = onHoverAction ;
}
2018-10-02 03:02:47 +00:00
protected override bool OnHover ( HoverEvent e )
2018-04-13 09:19:50 +00:00
{
onHoverAction ? . Invoke ( ) ;
2018-10-02 03:02:47 +00:00
return base . OnHover ( e ) ;
2018-04-13 09:19:50 +00:00
}
}
}
}