2019-03-12 16:27:20 +08:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2019-01-24 16:43:03 +08:00
// See the LICENCE file in the repository root for full licence text.
2018-04-13 17:19:50 +08:00
using osu.Framework.Allocation ;
using osu.Framework.Audio ;
using osu.Framework.Audio.Sample ;
using osu.Framework.Audio.Track ;
2019-02-21 18:04:31 +08:00
using osu.Framework.Bindables ;
2018-04-13 17:19:50 +08:00
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
2018-10-02 11:02:47 +08:00
using osu.Framework.Input.Events ;
2019-01-05 03:13:32 +08:00
using osu.Framework.Logging ;
2018-04-13 17:19:50 +08: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 17:37:21 +08:00
using osu.Game.Input.Bindings ;
2018-04-13 17:19:50 +08:00
using osu.Game.Overlays ;
2018-12-06 18:29:18 +08:00
using osu.Game.Overlays.Mods ;
2018-06-26 15:32:20 +08:00
using osu.Game.Rulesets ;
2018-07-04 11:40:55 +08:00
using osu.Game.Rulesets.Mods ;
2018-04-13 17:19:50 +08:00
using osu.Game.Screens.Backgrounds ;
using osu.Game.Screens.Edit ;
using osu.Game.Screens.Menu ;
2018-12-21 15:28:33 +08:00
using osu.Game.Screens.Play ;
2018-04-13 17:19:50 +08:00
using osu.Game.Screens.Select.Options ;
2018-12-12 12:21:44 +08:00
using osu.Game.Skinning ;
2019-01-05 03:13:32 +08:00
using osuTK ;
2019-02-04 16:04:52 +08:00
using osuTK.Graphics ;
2019-01-05 03:13:32 +08:00
using osuTK.Input ;
using System ;
using System.Collections.Generic ;
using System.Linq ;
2019-06-10 11:46:21 +08:00
using System.Threading.Tasks ;
2019-03-27 18:29:27 +08:00
using osu.Framework.Graphics.Sprites ;
2019-06-25 16:17:29 +08:00
using osu.Framework.Input.Bindings ;
2019-07-05 13:25:52 +08:00
using osu.Game.Scoring ;
2018-04-13 17:19:50 +08:00
namespace osu.Game.Screens.Select
{
2019-06-25 16:17:29 +08:00
public abstract class SongSelect : OsuScreen , IKeyBindingHandler < GlobalAction >
2018-04-13 17:19:50 +08:00
{
2019-09-25 07:48:22 +08:00
public static readonly Vector2 WEDGED_CONTAINER_SIZE = new Vector2 ( 0.5f , 245 ) ;
2019-05-12 14:40:58 +08:00
2019-03-14 15:09:17 +08:00
protected const float BACKGROUND_BLUR = 20 ;
2018-04-13 17:19:50 +08: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-10-04 10:45:18 +08:00
protected override BackgroundScreen CreateBackground ( ) = > new BackgroundScreenBeatmap ( ) ;
2018-04-13 17:19:50 +08:00
protected readonly BeatmapCarousel Carousel ;
private readonly BeatmapInfoWedge beatmapInfoWedge ;
private DialogOverlay dialogOverlay ;
private BeatmapManager beatmaps ;
2018-12-06 18:29:18 +08:00
protected readonly ModSelectOverlay ModSelect ;
2018-12-12 12:21:44 +08:00
protected SampleChannel SampleConfirm ;
2018-04-13 17:19:50 +08:00
private SampleChannel sampleChangeDifficulty ;
private SampleChannel sampleChangeBeatmap ;
2018-12-12 12:21:44 +08:00
protected readonly BeatmapDetailArea BeatmapDetails ;
2018-07-04 11:40:55 +08:00
2019-02-01 14:42:15 +08:00
private readonly Bindable < RulesetInfo > decoupledRuleset = new Bindable < RulesetInfo > ( ) ;
2018-07-04 11:40:55 +08:00
2019-07-10 23:20:01 +08:00
[Resolved(canBeNull: true)]
private MusicController music { get ; set ; }
2019-07-09 17:32:49 +08:00
2018-12-12 12:21:44 +08:00
[Cached]
2019-04-10 16:13:12 +08: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 17:19:50 +08:00
protected SongSelect ( )
{
2019-01-23 19:52:00 +08:00
AddRangeInternal ( new Drawable [ ]
2018-04-13 17:19:50 +08:00
{
new ParallaxContainer
{
2019-03-27 17:12:04 +08:00
Masking = true ,
2018-04-13 17:19:50 +08:00
ParallaxAmount = 0.005f ,
RelativeSizeAxes = Axes . Both ,
Children = new [ ]
{
new WedgeBackground
{
RelativeSizeAxes = Axes . Both ,
2019-05-12 11:08:45 +08:00
Padding = new MarginPadding { Right = - 150 } ,
2019-09-25 07:48:22 +08:00
Size = new Vector2 ( WEDGED_CONTAINER_SIZE . X , 1 ) ,
2018-04-13 17:19:50 +08:00
}
}
} ,
2018-12-12 12:21:44 +08:00
new Container
2018-04-13 17:19:50 +08:00
{
Origin = Anchor . BottomLeft ,
Anchor = Anchor . BottomLeft ,
RelativeSizeAxes = Axes . Both ,
2019-09-25 07:48:22 +08:00
Size = new Vector2 ( WEDGED_CONTAINER_SIZE . X , 1 ) ,
2018-04-13 17:19:50 +08:00
Padding = new MarginPadding
{
2019-07-26 12:07:28 +08:00
Bottom = Footer . HEIGHT ,
2019-09-25 07:48:22 +08:00
Top = WEDGED_CONTAINER_SIZE . Y + left_area_padding ,
2018-04-13 17:19:50 +08:00
Left = left_area_padding ,
Right = left_area_padding * 2 ,
2018-12-12 12:21:44 +08:00
} ,
Child = BeatmapDetails = new BeatmapDetailArea
{
RelativeSizeAxes = Axes . Both ,
Padding = new MarginPadding { Top = 10 , Right = 5 } ,
2018-04-13 17:19:50 +08: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 14:31:21 +08:00
new Container
2018-04-13 17:19:50 +08:00
{
2019-05-12 11:08:45 +08:00
RelativeSizeAxes = Axes . Both ,
2019-07-25 14:31:21 +08:00
Padding = new MarginPadding
{
2019-07-26 12:07:28 +08:00
Top = FilterControl . HEIGHT ,
Bottom = Footer . HEIGHT
2019-07-25 14:31:21 +08:00
} ,
Child = Carousel = new BeatmapCarousel
{
RelativeSizeAxes = Axes . Both ,
2019-09-25 07:48:22 +08:00
Size = new Vector2 ( 1 - WEDGED_CONTAINER_SIZE . X , 1 ) ,
2019-07-25 14:31:21 +08:00
Anchor = Anchor . CentreRight ,
Origin = Anchor . CentreRight ,
SelectionChanged = updateSelectedBeatmap ,
BeatmapSetsChanged = carouselBeatmapsLoaded ,
} ,
2018-04-13 17:19:50 +08:00
} ,
FilterControl = new FilterControl
{
RelativeSizeAxes = Axes . X ,
2019-07-26 12:07:28 +08:00
Height = FilterControl . HEIGHT ,
2018-04-13 17:19:50 +08:00
FilterChanged = c = > Carousel . Filter ( c ) ,
Background = { Width = 2 } ,
} ,
}
} ,
} ,
2019-01-30 23:02:05 +08:00
beatmapInfoWedge = new BeatmapInfoWedge
{
2019-09-25 07:48:22 +08:00
Size = WEDGED_CONTAINER_SIZE ,
2019-01-30 23:02:05 +08:00
RelativeSizeAxes = Axes . X ,
Margin = new MarginPadding
{
Top = left_area_padding ,
Right = left_area_padding ,
} ,
} ,
2018-04-13 17:19:50 +08:00
new ResetScrollContainer ( ( ) = > Carousel . ScrollToSelected ( ) )
{
RelativeSizeAxes = Axes . Y ,
Width = 250 ,
}
} ) ;
if ( ShowFooter )
{
2019-06-25 15:55:49 +08:00
AddRangeInternal ( new [ ]
2018-04-13 17:19:50 +08:00
{
2019-06-25 15:55:49 +08:00
FooterPanels = new Container
2018-12-06 18:29:18 +08:00
{
2019-06-25 15:55:49 +08:00
Anchor = Anchor . BottomLeft ,
Origin = Anchor . BottomLeft ,
2018-12-06 18:29:18 +08:00
RelativeSizeAxes = Axes . X ,
2019-06-25 15:55:49 +08: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 18:29:18 +08:00
} ) ;
2018-04-13 17:19:50 +08:00
}
2019-09-19 14:45:08 +08:00
BeatmapDetails . Leaderboard . ScoreSelected + = score = > this . Push ( new SoloResults ( score ) ) ;
2018-12-12 12:21:44 +08:00
}
2018-12-04 11:06:05 +08:00
2018-06-06 19:19:53 +08:00
[BackgroundDependencyLoader(true)]
2019-07-05 13:25:52 +08:00
private void load ( BeatmapManager beatmaps , AudioManager audio , DialogOverlay dialog , OsuColour colours , SkinManager skins , ScoreManager scores )
2018-04-13 17:19:50 +08:00
{
if ( Footer ! = null )
{
2019-07-17 13:46:25 +08:00
Footer . AddButton ( new FooterButtonMods { Current = mods } , ModSelect ) ;
2019-05-08 18:29:43 +08:00
Footer . AddButton ( new FooterButtonRandom { Action = triggerRandom } ) ;
Footer . AddButton ( new FooterButtonOptions ( ) , BeatmapOptions ) ;
2018-04-13 17:19:50 +08:00
2019-10-05 08:14:19 +08:00
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 ) ;
BeatmapOptions . AddButton ( @"Delete" , @"all difficulties" , FontAwesome . Solid . Trash , colours . Pink , ( ) = > delete ( Beatmap . Value . BeatmapSetInfo ) , Key . Number3 ) ;
2018-04-13 17:19:50 +08: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 16:06:01 +08: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 17:19:50 +08:00
2018-12-12 12:21:44 +08: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 13:57:11 +08:00
if ( ! beatmaps . GetAllUsableBeatmapSetsEnumerable ( ) . Any ( ) & & beatmaps . StableInstallationAvailable )
2018-12-12 12:21:44 +08:00
dialogOverlay . Push ( new ImportFromStablePopup ( ( ) = >
{
2019-07-05 13:25:52 +08:00
Task . Run ( beatmaps . ImportFromStableAsync ) . ContinueWith ( _ = > scores . ImportFromStableAsync ( ) , TaskContinuationOptions . OnlyOnRanToCompletion ) ;
2019-06-10 11:46:21 +08:00
Task . Run ( skins . ImportFromStableAsync ) ;
2018-12-12 12:21:44 +08:00
} ) ) ;
} ) ;
}
}
2019-07-17 13:46:25 +08:00
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
mods . BindTo ( Mods ) ;
}
2018-12-12 12:21:44 +08:00
private DependencyContainer dependencies ;
protected override IReadOnlyDependencyContainer CreateChildDependencies ( IReadOnlyDependencyContainer parent )
{
dependencies = new DependencyContainer ( base . CreateChildDependencies ( parent ) ) ;
2019-04-08 18:16:34 +08:00
2018-12-12 12:21:44 +08:00
dependencies . CacheAs ( this ) ;
2019-02-01 14:42:15 +08:00
dependencies . CacheAs ( decoupledRuleset ) ;
dependencies . CacheAs < IBindable < RulesetInfo > > ( decoupledRuleset ) ;
2018-12-12 12:21:44 +08:00
return dependencies ;
}
2018-10-29 12:04:51 +08:00
public void Edit ( BeatmapInfo beatmap = null )
2018-04-13 17:19:50 +08:00
{
2018-10-29 12:04:51 +08:00
Beatmap . Value = beatmaps . GetWorkingBeatmap ( beatmap ? ? beatmapNoDebounce ) ;
2019-02-27 20:07:17 +08:00
this . Push ( new Editor ( ) ) ;
2018-04-13 17:19:50 +08: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 14:44:35 +08:00
/// <param name="performStartAction">Whether to trigger <see cref="OnStart"/>.</param>
public void FinaliseSelection ( BeatmapInfo beatmap = null , bool performStartAction = true )
2018-04-13 17:19:50 +08:00
{
2019-03-21 19:52:34 +08: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 17:19:50 +08: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 17:06:46 +08: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 17:19:50 +08: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 14:44:35 +08:00
if ( performStartAction )
OnStart ( ) ;
2018-04-13 17:19:50 +08: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 14:44:35 +08:00
protected abstract bool OnStart ( ) ;
2018-04-13 17:19:50 +08:00
private ScheduledDelegate selectionChangedDebounce ;
2019-02-21 17:56:34 +08:00
private void workingBeatmapChanged ( ValueChangedEvent < WorkingBeatmap > e )
2018-04-13 17:19:50 +08:00
{
2019-02-21 17:56:34 +08:00
if ( e . NewValue is DummyWorkingBeatmap ) return ;
2018-04-13 17:19:50 +08:00
2019-02-21 17:56:34 +08:00
if ( this . IsCurrentScreen ( ) & & ! Carousel . SelectBeatmap ( e . NewValue ? . BeatmapInfo , false ) )
2018-04-13 17:19:50 +08:00
// If selecting new beatmap without bypassing filters failed, there's possibly a ruleset mismatch
2019-07-15 14:53:19 +08:00
if ( e . NewValue ? . BeatmapInfo ? . Ruleset ! = null & & ! e . NewValue . BeatmapInfo . Ruleset . Equals ( decoupledRuleset . Value ) )
2018-04-13 17:19:50 +08:00
{
2019-02-21 17:56:34 +08:00
Ruleset . Value = e . NewValue . BeatmapInfo . Ruleset ;
Carousel . SelectBeatmap ( e . NewValue . BeatmapInfo ) ;
2018-04-13 17:19:50 +08:00
}
}
2018-06-29 18:32:42 +08: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 17:48:40 +08:00
private void updateSelectedBeatmap ( BeatmapInfo beatmap )
{
if ( beatmap ? . Equals ( beatmapNoDebounce ) = = true )
return ;
beatmapNoDebounce = beatmap ;
2019-08-19 10:30:04 +08:00
2018-07-19 17:48:40 +08:00
performUpdateSelected ( ) ;
}
private void updateSelectedRuleset ( RulesetInfo ruleset )
{
if ( ruleset ? . Equals ( rulesetNoDebounce ) = = true )
return ;
rulesetNoDebounce = ruleset ;
performUpdateSelected ( ) ;
}
2018-04-13 17:19:50 +08:00
/// <summary>
2018-06-29 18:32:42 +08:00
/// selection has been changed as the result of a user interaction.
2018-04-13 17:19:50 +08:00
/// </summary>
2018-07-19 17:48:40 +08:00
private void performUpdateSelected ( )
2018-04-13 17:19:50 +08:00
{
2018-07-19 17:48:40 +08:00
var beatmap = beatmapNoDebounce ;
var ruleset = rulesetNoDebounce ;
2018-06-29 18:32:42 +08:00
2019-03-21 19:51:21 +08:00
selectionChangedDebounce ? . Cancel ( ) ;
if ( beatmap = = null )
run ( ) ;
else
selectionChangedDebounce = Scheduler . AddDelayed ( run , 200 ) ;
2018-07-20 10:32:00 +08:00
void run ( )
2018-04-13 17:19:50 +08:00
{
2018-07-20 10:32:00 +08:00
Logger . Log ( $"updating selection with beatmap:{beatmap?.ID.ToString() ?? " null "} ruleset:{ruleset?.ID.ToString() ?? " null "}" ) ;
2018-07-19 17:51:08 +08:00
2019-02-01 14:42:15 +08:00
if ( ruleset ? . Equals ( decoupledRuleset . Value ) = = false )
2018-07-17 21:20:19 +08:00
{
2019-02-01 14:42:15 +08:00
Logger . Log ( $"ruleset changed from \" { decoupledRuleset . Value } \ " to \"{ruleset}\"" ) ;
2018-08-14 12:19:50 +08:00
2019-04-10 16:13:12 +08:00
mods . Value = Array . Empty < Mod > ( ) ;
2019-02-01 14:42:15 +08:00
decoupledRuleset . Value = ruleset ;
2018-07-17 21:20:19 +08: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 10:32:00 +08: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 21:20:19 +08:00
}
2018-04-13 17:19:50 +08: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 17:48:40 +08:00
if ( ! Equals ( beatmap , Beatmap . Value . BeatmapInfo ) )
2018-04-13 17:19:50 +08:00
{
2018-07-20 10:32:00 +08:00
Logger . Log ( $"beatmap changed from \" { Beatmap . Value . BeatmapInfo } \ " to \"{beatmap}\"" ) ;
2018-07-19 17:51:08 +08:00
2019-09-01 08:17:55 +08:00
WorkingBeatmap previous = Beatmap . Value ;
Beatmap . Value = beatmaps . GetWorkingBeatmap ( beatmap , previous ) ;
2018-07-19 17:48:40 +08:00
if ( beatmap ! = null )
{
if ( beatmap . BeatmapSetInfoID = = beatmapNoDebounce ? . BeatmapSetInfoID )
sampleChangeDifficulty . Play ( ) ;
else
sampleChangeBeatmap . Play ( ) ;
}
2018-04-13 17:19:50 +08:00
}
2019-10-08 09:40:52 +08:00
if ( this . IsCurrentScreen ( ) )
ensurePlayingSelected ( ) ;
2018-04-13 17:19:50 +08:00
UpdateBeatmap ( Beatmap . Value ) ;
}
}
private void triggerRandom ( )
{
if ( GetContainingInputManager ( ) . CurrentState . Keyboard . ShiftPressed )
Carousel . SelectPreviousRandom ( ) ;
else
Carousel . SelectNextRandom ( ) ;
}
2019-01-23 19:52:00 +08:00
public override void OnEntering ( IScreen last )
2018-04-13 17:19:50 +08:00
{
base . OnEntering ( last ) ;
2019-01-23 19:52:00 +08:00
this . FadeInFromZero ( 250 ) ;
2018-04-13 17:19:50 +08: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 19:52:00 +08:00
public override void OnResuming ( IScreen last )
2018-04-13 17:19:50 +08:00
{
2018-12-12 12:21:44 +08:00
BeatmapDetails . Leaderboard . RefreshScores ( ) ;
Beatmap . Value . Track . Looping = true ;
2019-09-28 09:18:16 +08:00
music ? . ResetTrackAdjustments ( ) ;
2018-12-12 12:21:44 +08:00
2018-04-13 17:19:50 +08:00
if ( Beatmap ! = null & & ! Beatmap . Value . BeatmapSetInfo . DeletePending )
{
2018-05-23 16:37:39 +08:00
UpdateBeatmap ( Beatmap . Value ) ;
2019-10-10 19:12:47 +08:00
// restart playback on returning to song select, regardless.
music ? . Play ( ) ;
2018-04-13 17:19:50 +08:00
}
base . OnResuming ( last ) ;
2019-01-23 19:52:00 +08:00
this . FadeIn ( 250 ) ;
2018-04-13 17:19:50 +08:00
2019-01-23 19:52:00 +08:00
this . ScaleTo ( 1 , 250 , Easing . OutSine ) ;
2018-04-13 17:19:50 +08:00
FilterControl . Activate ( ) ;
}
2019-01-23 19:52:00 +08:00
public override void OnSuspending ( IScreen next )
2018-04-13 17:19:50 +08:00
{
2018-12-06 18:29:18 +08:00
ModSelect . Hide ( ) ;
2019-05-12 12:26:42 +08:00
BeatmapOptions . Hide ( ) ;
2019-01-23 19:52:00 +08:00
this . ScaleTo ( 1.1f , 250 , Easing . InSine ) ;
2018-04-13 17:19:50 +08:00
2019-01-23 19:52:00 +08:00
this . FadeOut ( 250 ) ;
2018-04-13 17:19:50 +08:00
FilterControl . Deactivate ( ) ;
base . OnSuspending ( next ) ;
}
2019-01-23 19:52:00 +08:00
public override bool OnExiting ( IScreen next )
2018-04-13 17:19:50 +08:00
{
2019-06-25 16:27:13 +08:00
if ( ModSelect . State . Value = = Visibility . Visible )
{
ModSelect . Hide ( ) ;
return true ;
}
2019-02-25 17:40:19 +08:00
if ( base . OnExiting ( next ) )
return true ;
2019-06-11 13:28:52 +08:00
beatmapInfoWedge . Hide ( ) ;
2018-04-13 17:19:50 +08:00
2019-01-23 19:52:00 +08:00
this . FadeOut ( 100 ) ;
2018-04-13 17:19:50 +08:00
FilterControl . Deactivate ( ) ;
2018-12-12 12:21:44 +08:00
if ( Beatmap . Value . Track ! = null )
Beatmap . Value . Track . Looping = false ;
2019-04-10 11:03:57 +08:00
mods . UnbindAll ( ) ;
2019-04-10 16:13:12 +08:00
Mods . Value = Array . Empty < Mod > ( ) ;
2018-12-12 12:21:44 +08:00
2019-02-25 17:40:19 +08:00
return false ;
2018-04-13 17:19:50 +08:00
}
protected override void Dispose ( bool isDisposing )
{
base . Dispose ( isDisposing ) ;
2019-02-01 14:42:15 +08:00
decoupledRuleset . UnbindAll ( ) ;
2018-08-20 13:27:32 +08:00
2018-04-13 17:19:50 +08: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 17:51:08 +08:00
Logger . Log ( $"working beatmap updated to {beatmap}" ) ;
2018-04-13 17:19:50 +08:00
if ( Background is BackgroundScreenBeatmap backgroundModeBeatmap )
{
backgroundModeBeatmap . Beatmap = beatmap ;
2019-03-20 13:17:35 +08:00
backgroundModeBeatmap . BlurAmount . Value = BACKGROUND_BLUR ;
2019-02-04 16:04:52 +08:00
backgroundModeBeatmap . FadeColour ( Color4 . White , 250 ) ;
2018-04-13 17:19:50 +08:00
}
2018-06-01 17:03:16 +08:00
beatmapInfoWedge . Beatmap = beatmap ;
2018-12-12 12:21:44 +08:00
BeatmapDetails . Beatmap = beatmap ;
if ( beatmap . Track ! = null )
beatmap . Track . Looping = true ;
2018-04-13 17:19:50 +08:00
}
2019-10-08 14:03:48 +08:00
private readonly WeakReference < Track > lastTrack = new WeakReference < Track > ( null ) ;
/// <summary>
/// Ensures some music is playing for the current track.
/// Will resume playback from a manual user pause if the track has changed.
/// </summary>
2019-10-10 19:12:47 +08:00
private void ensurePlayingSelected ( )
2018-04-13 17:19:50 +08:00
{
Track track = Beatmap . Value . Track ;
2019-10-08 14:03:48 +08:00
bool isNewTrack = ! lastTrack . TryGetTarget ( out var last ) | | last ! = track ;
2019-10-08 09:40:52 +08:00
track . RestartPoint = Beatmap . Value . Metadata . PreviewTime ;
2019-08-19 10:30:04 +08:00
2019-10-08 14:03:48 +08:00
if ( ! track . IsRunning & & ( music ? . IsUserPaused ! = true | | isNewTrack ) )
2019-10-10 19:12:47 +08:00
music ? . Play ( true ) ;
2019-10-08 14:03:48 +08:00
lastTrack . SetTarget ( track ) ;
2018-04-13 17:19:50 +08:00
}
2019-06-26 10:40:33 +08:00
private void onBeatmapSetAdded ( BeatmapSetInfo s ) = > Carousel . UpdateBeatmapSet ( s ) ;
2018-04-13 17:19:50 +08: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 19:52:15 +08:00
bindBindables ( ) ;
2018-07-17 21:20:19 +08:00
2019-06-12 15:07:35 +08:00
// If a selection was already obtained, do not attempt to update the selected beatmap.
2019-05-28 13:04:33 +08: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 11:40:55 +08:00
if ( ! Beatmap . IsDefault & & Beatmap . Value . BeatmapSetInfo ? . DeletePending = = false & & Beatmap . Value . BeatmapSetInfo ? . Protected = = false
& & Carousel . SelectBeatmap ( Beatmap . Value . BeatmapInfo , false ) )
2018-04-13 17:19:50 +08:00
return ;
2019-05-28 13:04:33 +08:00
// If the current active beatmap could not be selected, select a new random beatmap.
if ( ! Carousel . SelectNextRandom ( ) )
2018-04-13 17:19:50 +08: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 17:48:40 +08:00
performUpdateSelected ( ) ;
2018-04-13 17:19:50 +08:00
}
}
2019-03-21 19:52:15 +08:00
private bool boundLocalBindables ;
private void bindBindables ( )
2019-03-21 19:52:34 +08:00
{
2019-03-21 19:52:15 +08:00
if ( boundLocalBindables )
return ;
2019-03-21 19:52:34 +08: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 19:52:15 +08:00
2019-03-21 19:52:34 +08:00
decoupledRuleset . ValueChanged + = r = > Ruleset . Value = r . NewValue ;
decoupledRuleset . DisabledChanged + = r = > Ruleset . Disabled = r ;
2019-03-21 19:52:15 +08:00
2019-03-21 19:52:34 +08:00
Beatmap . BindDisabledChanged ( disabled = > Carousel . AllowSelection = ! disabled , true ) ;
Beatmap . BindValueChanged ( workingBeatmapChanged ) ;
2019-03-21 19:52:15 +08:00
boundLocalBindables = true ;
}
2018-04-13 17:19:50 +08:00
private void delete ( BeatmapSetInfo beatmap )
{
2018-09-21 10:29:37 +08:00
if ( beatmap = = null | | beatmap . ID < = 0 ) return ;
2019-02-27 20:07:17 +08:00
2018-04-13 17:19:50 +08:00
dialogOverlay ? . Push ( new BeatmapDeleteDialog ( beatmap ) ) ;
}
2019-01-09 00:57:03 +08:00
private void clearScores ( BeatmapInfo beatmap )
2019-01-05 03:13:32 +08:00
{
if ( beatmap = = null | | beatmap . ID < = 0 ) return ;
2019-03-01 19:52:34 +08: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-05 03:13:32 +08:00
}
2019-06-25 16:17:29 +08:00
public virtual bool OnPressed ( GlobalAction action )
2018-07-03 17:37:21 +08:00
{
2019-01-23 19:52:00 +08:00
if ( ! this . IsCurrentScreen ( ) ) return false ;
2018-07-03 17:37:21 +08:00
switch ( action )
{
case GlobalAction . Select :
FinaliseSelection ( ) ;
return true ;
}
2019-06-25 16:17:29 +08:00
return false ;
2018-07-03 17:37:21 +08:00
}
2019-06-25 16:17:29 +08:00
public bool OnReleased ( GlobalAction action ) = > action = = GlobalAction . Select ;
2018-10-02 11:02:47 +08:00
protected override bool OnKeyDown ( KeyDownEvent e )
2018-04-13 17:19:50 +08:00
{
2018-10-02 11:02:47 +08:00
if ( e . Repeat ) return false ;
2018-04-13 17:19:50 +08:00
2018-10-02 11:02:47 +08:00
switch ( e . Key )
2018-04-13 17:19:50 +08:00
{
case Key . Delete :
2018-10-02 11:44:14 +08:00
if ( e . ShiftPressed )
2018-04-13 17:19:50 +08:00
{
if ( ! Beatmap . IsDefault )
delete ( Beatmap . Value . BeatmapSetInfo ) ;
return true ;
}
break ;
}
2018-10-02 11:02:47 +08:00
return base . OnKeyDown ( e ) ;
2018-04-13 17:19:50 +08:00
}
private class ResetScrollContainer : Container
{
private readonly Action onHoverAction ;
public ResetScrollContainer ( Action onHoverAction )
{
this . onHoverAction = onHoverAction ;
}
2018-10-02 11:02:47 +08:00
protected override bool OnHover ( HoverEvent e )
2018-04-13 17:19:50 +08:00
{
onHoverAction ? . Invoke ( ) ;
2018-10-02 11:02:47 +08:00
return base . OnHover ( e ) ;
2018-04-13 17:19:50 +08:00
}
}
}
}