2020-03-17 16:43:16 +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.
2020-03-18 17:28:42 +08:00
using System ;
2020-05-26 16:00:41 +08:00
using System.Collections.Generic ;
2020-06-19 16:28:35 +08:00
using System.Linq ;
2020-03-17 16:43:16 +08:00
using osu.Framework.Allocation ;
2020-05-28 20:40:01 +08:00
using osu.Framework.Bindables ;
2020-03-17 16:43:16 +08:00
using osu.Framework.Extensions.Color4Extensions ;
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
using osu.Framework.Graphics.Shapes ;
2020-10-11 09:30:13 +08:00
using osu.Framework.Input.Bindings ;
2020-03-17 16:43:16 +08:00
using osu.Framework.Screens ;
2020-03-17 21:21:16 +08:00
using osu.Game.Graphics.Containers ;
2020-03-17 16:43:16 +08:00
using osu.Game.Graphics.UserInterface ;
2020-10-11 09:30:13 +08:00
using osu.Game.Input.Bindings ;
2020-05-16 18:00:20 +08:00
using osu.Game.Online.API ;
2020-11-20 13:32:23 +08:00
using osu.Game.Rulesets.Mods ;
2020-03-17 16:43:16 +08:00
using osu.Game.Scoring ;
using osu.Game.Screens.Backgrounds ;
2020-03-17 16:45:25 +08:00
using osu.Game.Screens.Play ;
2020-06-16 16:49:43 +08:00
using osu.Game.Screens.Ranking.Statistics ;
2020-03-17 16:43:16 +08:00
using osuTK ;
namespace osu.Game.Screens.Ranking
{
2020-10-11 09:30:13 +08:00
public abstract class ResultsScreen : OsuScreen , IKeyBindingHandler < GlobalAction >
2020-03-17 16:43:16 +08:00
{
protected const float BACKGROUND_BLUR = 20 ;
2020-06-17 18:28:40 +08:00
private static readonly float screen_height = 768 - TwoLayerButton . SIZE_EXTENDED . Y ;
2020-03-17 16:43:16 +08:00
public override bool DisallowExternalBeatmapRulesetChanges = > true ;
// Temporary for now to stop dual transitions. Should respect the current toolbar mode, but there's no way to do so currently.
public override bool HideOverlaysOnEnter = > true ;
protected override BackgroundScreen CreateBackground ( ) = > new BackgroundScreenBeatmap ( Beatmap . Value ) ;
2020-05-28 20:40:01 +08:00
public readonly Bindable < ScoreInfo > SelectedScore = new Bindable < ScoreInfo > ( ) ;
public readonly ScoreInfo Score ;
2020-07-31 18:57:05 +08:00
protected ScorePanelList ScorePanelList { get ; private set ; }
2020-05-28 20:40:01 +08:00
2020-03-17 16:45:25 +08:00
[Resolved(CanBeNull = true)]
private Player player { get ; set ; }
2020-05-16 18:00:20 +08:00
[Resolved]
private IAPIProvider api { get ; set ; }
2020-06-18 15:50:45 +08:00
private StatisticsPanel statisticsPanel ;
2020-03-17 16:43:16 +08:00
private Drawable bottomPanel ;
2020-06-19 16:28:35 +08:00
private Container < ScorePanel > detachedPanelContainer ;
2020-03-17 16:43:16 +08:00
2020-07-28 19:58:13 +08:00
private bool fetchedInitialScores ;
private APIRequest nextPageRequest ;
2020-07-31 18:57:05 +08:00
private readonly bool allowRetry ;
2020-11-21 21:38:38 +08:00
protected ResultsScreen ( ScoreInfo score , bool allowRetry )
2020-03-17 16:43:16 +08:00
{
2020-03-29 22:50:16 +08:00
Score = score ;
2020-03-29 22:52:50 +08:00
this . allowRetry = allowRetry ;
2020-05-28 20:40:01 +08:00
SelectedScore . Value = score ;
2020-03-17 16:43:16 +08:00
}
[BackgroundDependencyLoader]
private void load ( )
{
2020-03-29 22:52:50 +08:00
FillFlowContainer buttons ;
2020-05-22 19:39:02 +08:00
InternalChild = new GridContainer
2020-03-17 16:43:16 +08:00
{
2020-05-22 19:39:02 +08:00
RelativeSizeAxes = Axes . Both ,
Content = new [ ]
2020-03-17 16:43:16 +08:00
{
2020-05-22 19:39:02 +08:00
new Drawable [ ]
2020-03-17 21:21:16 +08:00
{
2020-06-30 15:36:53 +08:00
new VerticalScrollContainer
2020-03-17 16:43:16 +08:00
{
2020-06-17 21:29:00 +08:00
RelativeSizeAxes = Axes . Both ,
2020-06-30 15:36:53 +08:00
ScrollbarVisible = false ,
Child = new Container
2020-05-22 19:39:02 +08:00
{
2020-06-30 15:36:53 +08:00
RelativeSizeAxes = Axes . Both ,
Children = new Drawable [ ]
2020-06-17 18:28:40 +08:00
{
2020-07-02 05:31:06 +08:00
statisticsPanel = new StatisticsPanel
{
RelativeSizeAxes = Axes . Both ,
Score = { BindTarget = SelectedScore }
} ,
2020-07-31 18:57:05 +08:00
ScorePanelList = new ScorePanelList
2020-06-17 18:28:40 +08:00
{
2020-06-30 15:36:53 +08:00
RelativeSizeAxes = Axes . Both ,
SelectedScore = { BindTarget = SelectedScore } ,
PostExpandAction = ( ) = > statisticsPanel . ToggleVisibility ( )
} ,
detachedPanelContainer = new Container < ScorePanel >
{
RelativeSizeAxes = Axes . Both
} ,
}
2020-05-22 19:39:02 +08:00
}
2020-06-30 15:36:53 +08:00
} ,
2020-05-22 19:39:02 +08:00
} ,
new [ ]
{
bottomPanel = new Container
2020-03-17 16:43:16 +08:00
{
2020-05-22 19:39:02 +08:00
Anchor = Anchor . BottomLeft ,
Origin = Anchor . BottomLeft ,
RelativeSizeAxes = Axes . X ,
Height = TwoLayerButton . SIZE_EXTENDED . Y ,
Alpha = 0 ,
2020-03-17 16:43:16 +08:00
Children = new Drawable [ ]
{
2020-05-22 19:39:02 +08:00
new Box
{
RelativeSizeAxes = Axes . Both ,
Colour = Color4Extensions . FromHex ( "#333" )
} ,
buttons = new FillFlowContainer
{
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
AutoSizeAxes = Axes . Both ,
Spacing = new Vector2 ( 5 ) ,
Direction = FillDirection . Horizontal ,
Children = new Drawable [ ]
{
2020-05-28 20:40:01 +08:00
new ReplayDownloadButton ( null )
{
Score = { BindTarget = SelectedScore } ,
Width = 300
} ,
2020-05-22 19:39:02 +08:00
}
}
2020-03-17 16:43:16 +08:00
}
}
}
2020-05-22 19:39:02 +08:00
} ,
RowDimensions = new [ ]
{
new Dimension ( ) ,
new Dimension ( GridSizeMode . AutoSize )
2020-03-17 16:43:16 +08:00
}
} ;
2020-03-17 16:45:25 +08:00
2020-05-28 20:40:01 +08:00
if ( Score ! = null )
2020-11-20 13:32:23 +08:00
{
// only show flair / animation when arriving after watching a play that isn't autoplay.
bool shouldFlair = player ! = null & & ! Score . Mods . Any ( m = > m is ModAutoplay ) ;
ScorePanelList . AddScore ( Score , shouldFlair ) ;
}
2020-05-28 20:40:01 +08:00
2020-03-30 17:56:35 +08:00
if ( player ! = null & & allowRetry )
2020-03-17 16:45:25 +08:00
{
2020-03-30 17:56:35 +08:00
buttons . Add ( new RetryButton { Width = 300 } ) ;
2020-03-29 22:52:50 +08:00
2020-03-30 17:56:35 +08:00
AddInternal ( new HotkeyRetryOverlay
{
Action = ( ) = >
2020-03-17 16:45:25 +08:00
{
2020-03-30 17:56:35 +08:00
if ( ! this . IsCurrentScreen ( ) ) return ;
2020-03-17 16:45:25 +08:00
2020-03-30 17:56:35 +08:00
player ? . Restart ( ) ;
} ,
} ) ;
2020-03-17 16:45:25 +08:00
}
2020-03-17 16:43:16 +08:00
}
2020-05-16 18:00:20 +08:00
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
2020-07-22 19:24:55 +08:00
var req = FetchScores ( fetchScoresCallback ) ;
2020-05-16 18:00:20 +08:00
2020-05-26 16:00:41 +08:00
if ( req ! = null )
api . Queue ( req ) ;
2020-06-18 21:27:27 +08:00
statisticsPanel . State . BindValueChanged ( onStatisticsStateChanged , true ) ;
2020-05-16 18:00:20 +08:00
}
2020-07-22 19:24:55 +08:00
protected override void Update ( )
{
base . Update ( ) ;
2020-07-28 19:58:13 +08:00
if ( fetchedInitialScores & & nextPageRequest = = null )
2020-07-22 19:24:55 +08:00
{
2020-07-31 18:57:05 +08:00
if ( ScorePanelList . IsScrolledToStart )
2020-07-22 19:24:55 +08:00
nextPageRequest = FetchNextPage ( - 1 , fetchScoresCallback ) ;
2020-07-31 18:57:05 +08:00
else if ( ScorePanelList . IsScrolledToEnd )
2020-07-22 19:24:55 +08:00
nextPageRequest = FetchNextPage ( 1 , fetchScoresCallback ) ;
if ( nextPageRequest ! = null )
{
2020-07-31 20:33:04 +08:00
// Scheduled after children to give the list a chance to update its scroll position and not potentially trigger a second request too early.
nextPageRequest . Success + = ( ) = > ScheduleAfterChildren ( ( ) = > nextPageRequest = null ) ;
nextPageRequest . Failure + = _ = > ScheduleAfterChildren ( ( ) = > nextPageRequest = null ) ;
2020-07-22 19:24:55 +08:00
api . Queue ( nextPageRequest ) ;
}
}
}
2020-05-26 16:00:41 +08:00
/// <summary>
/// Performs a fetch/refresh of scores to be displayed.
/// </summary>
/// <param name="scoresCallback">A callback which should be called when fetching is completed. Scheduling is not required.</param>
/// <returns>An <see cref="APIRequest"/> responsible for the fetch operation. This will be queued and performed automatically.</returns>
protected virtual APIRequest FetchScores ( Action < IEnumerable < ScoreInfo > > scoresCallback ) = > null ;
2020-07-28 19:58:13 +08:00
/// <summary>
/// Performs a fetch of the next page of scores. This is invoked every frame until a non-null <see cref="APIRequest"/> is returned.
/// </summary>
/// <param name="direction">The fetch direction. -1 to fetch scores greater than the current start of the list, and 1 to fetch scores lower than the current end of the list.</param>
/// <param name="scoresCallback">A callback which should be called when fetching is completed. Scheduling is not required.</param>
/// <returns>An <see cref="APIRequest"/> responsible for the fetch operation. This will be queued and performed automatically.</returns>
2020-07-22 19:24:55 +08:00
protected virtual APIRequest FetchNextPage ( int direction , Action < IEnumerable < ScoreInfo > > scoresCallback ) = > null ;
private void fetchScoresCallback ( IEnumerable < ScoreInfo > scores ) = > Schedule ( ( ) = >
{
foreach ( var s in scores )
addScore ( s ) ;
2020-07-28 19:58:13 +08:00
fetchedInitialScores = true ;
2020-07-22 19:24:55 +08:00
} ) ;
2020-03-17 16:43:16 +08:00
public override void OnEntering ( IScreen last )
{
base . OnEntering ( last ) ;
( ( BackgroundScreenBeatmap ) Background ) . BlurAmount . Value = BACKGROUND_BLUR ;
Background . FadeTo ( 0.5f , 250 ) ;
bottomPanel . FadeTo ( 1 , 250 ) ;
}
public override bool OnExiting ( IScreen next )
2020-07-14 15:00:43 +08:00
{
Background . FadeTo ( 1 , 250 ) ;
return base . OnExiting ( next ) ;
}
public override bool OnBackButton ( )
2020-03-17 16:43:16 +08:00
{
2020-06-18 21:27:27 +08:00
if ( statisticsPanel . State . Value = = Visibility . Visible )
{
statisticsPanel . Hide ( ) ;
return true ;
}
2020-07-14 15:00:43 +08:00
return false ;
2020-03-17 16:43:16 +08:00
}
2020-03-17 21:21:16 +08:00
2020-06-19 16:28:35 +08:00
private void addScore ( ScoreInfo score )
{
2020-07-31 18:57:05 +08:00
var panel = ScorePanelList . AddScore ( score ) ;
2020-06-19 16:28:35 +08:00
if ( detachedPanel ! = null )
panel . Alpha = 0 ;
}
private ScorePanel detachedPanel ;
2020-06-18 21:27:27 +08:00
private void onStatisticsStateChanged ( ValueChangedEvent < Visibility > state )
2020-03-17 21:21:16 +08:00
{
2020-06-19 16:28:35 +08:00
if ( state . NewValue = = Visibility . Visible )
2020-03-17 21:21:16 +08:00
{
2020-06-19 16:28:35 +08:00
// Detach the panel in its original location, and move into the desired location in the local container.
2020-07-31 18:57:05 +08:00
var expandedPanel = ScorePanelList . GetPanelForScore ( SelectedScore . Value ) ;
2020-06-19 16:28:35 +08:00
var screenSpacePos = expandedPanel . ScreenSpaceDrawQuad . TopLeft ;
2020-06-18 21:27:27 +08:00
2020-06-19 16:28:35 +08:00
// Detach and move into the local container.
2020-07-31 18:57:05 +08:00
ScorePanelList . Detach ( expandedPanel ) ;
2020-06-19 16:28:35 +08:00
detachedPanelContainer . Add ( expandedPanel ) ;
2020-06-22 14:42:55 +08:00
// Move into its original location in the local container first, then to the final location.
2020-09-24 11:49:32 +08:00
var origLocation = detachedPanelContainer . ToLocalSpace ( screenSpacePos ) . X ;
expandedPanel . MoveToX ( origLocation )
2020-06-22 14:42:55 +08:00
. Then ( )
2020-09-24 11:49:32 +08:00
. MoveToX ( StatisticsPanel . SIDE_PADDING , 150 , Easing . OutQuint ) ;
2020-06-19 16:28:35 +08:00
// Hide contracted panels.
2020-07-31 18:57:05 +08:00
foreach ( var contracted in ScorePanelList . GetScorePanels ( ) . Where ( p = > p . State = = PanelState . Contracted ) )
2020-06-19 16:28:35 +08:00
contracted . FadeOut ( 150 , Easing . OutQuint ) ;
2020-07-31 18:57:05 +08:00
ScorePanelList . HandleInput = false ;
2020-06-19 16:28:35 +08:00
// Dim background.
Background . FadeTo ( 0.1f , 150 ) ;
detachedPanel = expandedPanel ;
2020-06-18 15:50:45 +08:00
}
2020-06-19 16:28:35 +08:00
else if ( detachedPanel ! = null )
2020-06-18 15:50:45 +08:00
{
2020-06-19 16:28:35 +08:00
var screenSpacePos = detachedPanel . ScreenSpaceDrawQuad . TopLeft ;
2020-06-18 21:27:27 +08:00
2020-06-19 16:28:35 +08:00
// Remove from the local container and re-attach.
detachedPanelContainer . Remove ( detachedPanel ) ;
2020-07-31 18:57:05 +08:00
ScorePanelList . Attach ( detachedPanel ) ;
2020-06-18 15:50:45 +08:00
2020-06-22 14:42:55 +08:00
// Move into its original location in the attached container first, then to the final location.
2020-06-19 16:28:35 +08:00
var origLocation = detachedPanel . Parent . ToLocalSpace ( screenSpacePos ) ;
2020-06-22 14:42:55 +08:00
detachedPanel . MoveTo ( origLocation )
. Then ( )
. MoveTo ( new Vector2 ( 0 , origLocation . Y ) , 150 , Easing . OutQuint ) ;
2020-06-19 16:28:35 +08:00
// Show contracted panels.
2020-07-31 18:57:05 +08:00
foreach ( var contracted in ScorePanelList . GetScorePanels ( ) . Where ( p = > p . State = = PanelState . Contracted ) )
2020-06-19 16:28:35 +08:00
contracted . FadeIn ( 150 , Easing . OutQuint ) ;
2020-07-31 18:57:05 +08:00
ScorePanelList . HandleInput = true ;
2020-06-19 16:28:35 +08:00
// Un-dim background.
Background . FadeTo ( 0.5f , 150 ) ;
detachedPanel = null ;
2020-03-17 21:21:16 +08:00
}
}
2020-06-30 15:36:53 +08:00
2020-10-11 09:30:13 +08:00
public bool OnPressed ( GlobalAction action )
{
switch ( action )
{
case GlobalAction . Select :
statisticsPanel . ToggleVisibility ( ) ;
return true ;
}
return false ;
}
public void OnReleased ( GlobalAction action )
{
}
2020-06-30 15:36:53 +08:00
private class VerticalScrollContainer : OsuScrollContainer
{
protected override Container < Drawable > Content = > content ;
private readonly Container content ;
public VerticalScrollContainer ( )
{
base . Content . Add ( content = new Container { RelativeSizeAxes = Axes . X } ) ;
}
protected override void Update ( )
{
base . Update ( ) ;
content . Height = Math . Max ( screen_height , DrawHeight ) ;
}
}
2020-03-17 16:43:16 +08:00
}
}