2020-03-17 17:43:16 +09: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 18:28:42 +09:00
using System ;
2020-05-26 17:00:41 +09:00
using System.Collections.Generic ;
2024-05-14 12:57:30 +02:00
using System.Diagnostics ;
2020-06-19 17:28:35 +09:00
using System.Linq ;
2025-02-25 21:54:13 +09:00
using System.Threading.Tasks ;
2020-03-17 17:43:16 +09:00
using osu.Framework.Allocation ;
2022-07-22 19:06:31 +09:00
using osu.Framework.Audio ;
using osu.Framework.Audio.Sample ;
2020-05-28 21:40:01 +09:00
using osu.Framework.Bindables ;
2020-03-17 17:43:16 +09:00
using osu.Framework.Extensions.Color4Extensions ;
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
2024-07-20 11:49:46 -07:00
using osu.Framework.Graphics.Cursor ;
2020-03-17 17:43:16 +09:00
using osu.Framework.Graphics.Shapes ;
2020-10-10 18:30:13 -07:00
using osu.Framework.Input.Bindings ;
2021-09-16 18:26:12 +09:00
using osu.Framework.Input.Events ;
2020-03-17 17:43:16 +09:00
using osu.Framework.Screens ;
2025-02-03 14:15:43 +09:00
using osu.Game.Audio ;
2021-06-09 17:18:52 +09:00
using osu.Game.Graphics ;
2020-03-17 22:21:16 +09:00
using osu.Game.Graphics.Containers ;
2020-03-17 17:43:16 +09:00
using osu.Game.Graphics.UserInterface ;
2020-10-10 18:30:13 -07:00
using osu.Game.Input.Bindings ;
2024-01-04 12:15:48 +09:00
using osu.Game.Localisation ;
using osu.Game.Online.Placeholders ;
2024-02-20 15:52:15 +01:00
using osu.Game.Overlays ;
2020-03-17 17:43:16 +09:00
using osu.Game.Scoring ;
2020-03-17 17:45:25 +09:00
using osu.Game.Screens.Play ;
2024-02-20 15:52:15 +01:00
using osu.Game.Screens.Ranking.Expanded.Accuracy ;
2020-06-16 17:49:43 +09:00
using osu.Game.Screens.Ranking.Statistics ;
2025-02-03 14:15:43 +09:00
using osu.Game.Skinning ;
2020-03-17 17:43:16 +09:00
using osuTK ;
namespace osu.Game.Screens.Ranking
{
2025-02-03 14:15:43 +09:00
[Cached]
2021-01-04 18:24:21 +09:00
public abstract partial class ResultsScreen : ScreenWithBeatmapBackground , IKeyBindingHandler < GlobalAction >
2020-03-17 17:43:16 +09:00
{
protected const float BACKGROUND_BLUR = 20 ;
2020-06-17 19:28:40 +09:00
private static readonly float screen_height = 768 - TwoLayerButton . SIZE_EXTENDED . Y ;
2020-03-17 17:43:16 +09:00
public override bool DisallowExternalBeatmapRulesetChanges = > true ;
2023-07-25 20:00:31 +09:00
public override bool? AllowGlobalTrackControl = > true ;
2024-02-20 15:52:15 +01:00
protected override OverlayActivation InitialOverlayActivationMode = > OverlayActivation . UserTriggered ;
2024-04-25 09:52:26 +02:00
public readonly Bindable < ScoreInfo ? > SelectedScore = new Bindable < ScoreInfo ? > ( ) ;
2020-05-28 21:40:01 +09:00
2024-04-25 09:52:26 +02:00
public readonly ScoreInfo ? Score ;
2020-07-31 19:57:05 +09:00
2024-04-25 09:52:26 +02:00
protected ScorePanelList ScorePanelList { get ; private set ; } = null ! ;
2020-05-28 21:40:01 +09:00
2024-04-25 09:52:26 +02:00
protected VerticalScrollContainer VerticalScrollContent { get ; private set ; } = null ! ;
2021-08-13 16:14:23 +09:00
2024-04-25 09:52:26 +02:00
[Resolved]
private Player ? player { get ; set ; }
2020-03-17 17:45:25 +09:00
2024-12-04 14:31:39 +09:00
private bool skipExitTransition ;
2024-04-25 09:52:26 +02:00
protected StatisticsPanel StatisticsPanel { get ; private set ; } = null ! ;
2023-08-01 19:00:01 +09:00
2024-04-25 09:52:26 +02:00
private Drawable bottomPanel = null ! ;
private Container < ScorePanel > detachedPanelContainer = null ! ;
2020-03-17 17:43:16 +09:00
2025-02-25 23:17:02 +09:00
private Task lastFetchTask = Task . CompletedTask ;
2020-07-28 20:58:13 +09:00
2024-02-22 19:15:02 +01:00
/// <summary>
/// Whether the user can retry the beatmap from the results screen.
/// </summary>
public bool AllowRetry { get ; init ; }
/// <summary>
/// Whether the user can watch the replay of the completed play from the results screen.
/// </summary>
public bool AllowWatchingReplay { get ; init ; } = true ;
2020-07-31 19:57:05 +09:00
2024-02-22 19:49:14 +01:00
/// <summary>
/// Whether the user's personal statistics should be shown on the extended statistics panel
2024-07-17 12:45:20 -07:00
/// after clicking the score panel associated with the <see cref="Score"/> being presented.
2024-02-22 19:49:14 +01:00
/// Requires <see cref="Score"/> to be present.
/// </summary>
public bool ShowUserStatistics { get ; init ; }
2024-04-25 09:52:26 +02:00
private Sample ? popInSample ;
2022-07-22 19:06:31 +09:00
2024-04-25 09:52:26 +02:00
protected ResultsScreen ( ScoreInfo ? score )
2020-03-17 17:43:16 +09:00
{
2020-03-29 23:50:16 +09:00
Score = score ;
2020-05-28 21:40:01 +09:00
SelectedScore . Value = score ;
2020-03-17 17:43:16 +09:00
}
[BackgroundDependencyLoader]
2022-07-22 19:06:31 +09:00
private void load ( AudioManager audio )
2020-03-17 17:43:16 +09:00
{
2020-03-29 23:52:50 +09:00
FillFlowContainer buttons ;
2022-07-22 19:06:31 +09:00
popInSample = audio . Samples . Get ( @"UI/overlay-pop-in" ) ;
2025-02-03 14:15:43 +09:00
InternalChild = new PopoverContainer
2020-03-17 17:43:16 +09:00
{
2020-05-22 20:39:02 +09:00
RelativeSizeAxes = Axes . Both ,
2025-02-03 14:15:43 +09:00
Child = new GridContainer
2020-03-17 17:43:16 +09:00
{
2024-07-20 11:49:46 -07:00
RelativeSizeAxes = Axes . Both ,
2025-02-03 14:15:43 +09:00
Content = new [ ]
2020-03-17 22:21:16 +09:00
{
2025-02-03 14:15:43 +09:00
new Drawable [ ]
2020-03-17 17:43:16 +09:00
{
2025-02-03 14:15:43 +09:00
VerticalScrollContent = new VerticalScrollContainer
2020-05-22 20:39:02 +09:00
{
2025-02-03 14:15:43 +09:00
RelativeSizeAxes = Axes . Both ,
ScrollbarVisible = false ,
Child = new Container
2020-06-17 19:28:40 +09:00
{
2024-07-20 11:49:46 -07:00
RelativeSizeAxes = Axes . Both ,
2025-01-31 16:22:37 +09:00
Children = new Drawable [ ]
{
2025-02-03 14:15:43 +09:00
StatisticsPanel = createStatisticsPanel ( ) . With ( panel = >
{
panel . RelativeSizeAxes = Axes . Both ;
panel . Score . BindTarget = SelectedScore ;
} ) ,
ScorePanelList = new ScorePanelList
2024-07-20 11:49:46 -07:00
{
RelativeSizeAxes = Axes . Both ,
2025-02-03 14:15:43 +09:00
SelectedScore = { BindTarget = SelectedScore } ,
PostExpandAction = ( ) = > StatisticsPanel . ToggleVisibility ( )
2024-07-20 11:49:46 -07:00
} ,
2025-02-03 14:15:43 +09:00
detachedPanelContainer = new Container < ScorePanel >
2024-07-20 11:49:46 -07:00
{
2025-02-03 14:15:43 +09:00
RelativeSizeAxes = Axes . Both
2024-07-20 11:49:46 -07:00
} ,
}
}
2025-02-03 14:15:43 +09:00
} ,
2024-07-20 11:49:46 -07:00
} ,
2025-02-03 14:15:43 +09:00
new [ ]
2024-07-20 11:49:46 -07:00
{
2025-02-03 14:15:43 +09:00
bottomPanel = new Container
{
Anchor = Anchor . BottomLeft ,
Origin = Anchor . BottomLeft ,
RelativeSizeAxes = Axes . X ,
Height = TwoLayerButton . SIZE_EXTENDED . Y ,
Alpha = 0 ,
Children = new Drawable [ ]
{
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
} ,
}
}
2024-07-20 11:49:46 -07:00
}
2025-02-03 14:15:43 +09:00
} ,
RowDimensions = new [ ]
{
new Dimension ( ) ,
new Dimension ( GridSizeMode . AutoSize )
2020-03-17 17:43:16 +09:00
}
}
} ;
2020-03-17 17:45:25 +09:00
2020-05-28 21:40:01 +09:00
if ( Score ! = null )
2020-11-20 14:32:23 +09:00
{
// only show flair / animation when arriving after watching a play that isn't autoplay.
2023-08-29 23:28:50 +02:00
bool shouldFlair = player ! = null & & ! Score . User . IsBot ;
2020-11-20 14:32:23 +09:00
ScorePanelList . AddScore ( Score , shouldFlair ) ;
2024-02-20 15:52:15 +01:00
// this is mostly for medal display.
// we don't want the medal animation to trample on the results screen animation, so we (ab)use `OverlayActivationMode`
// to give the results screen enough time to play the animation out before the medals can be shown.
Scheduler . AddDelayed ( ( ) = > OverlayActivationMode . Value = OverlayActivation . All , shouldFlair ? AccuracyCircle . TOTAL_DURATION + 1000 : 0 ) ;
2020-11-20 14:32:23 +09:00
}
2020-05-28 21:40:01 +09:00
2024-12-13 18:14:45 +09:00
bool allowHotkeyRetry = false ;
2024-07-25 07:40:17 +03:00
if ( AllowWatchingReplay )
2020-12-21 00:14:54 +09:00
{
2023-06-21 17:50:06 +09:00
buttons . Add ( new ReplayDownloadButton ( SelectedScore . Value )
2020-12-21 00:14:54 +09:00
{
2024-07-25 07:40:17 +03:00
Score = { BindTarget = SelectedScore } ,
2020-12-21 00:14:54 +09:00
Width = 300
} ) ;
2024-12-13 18:14:45 +09:00
2024-12-13 20:02:17 +09:00
// for simplicity, only allow this when coming from a replay player where we know the replay is ready to be played.
//
// if we show it in all cases, consider the case where a user comes from song select and potentially has to download
// the replay before it can be played back. it wouldn't flow well with the quick retry in such a case.
2024-12-13 18:14:45 +09:00
allowHotkeyRetry = player is ReplayPlayer ;
2020-12-21 00:14:54 +09:00
}
2024-02-22 19:15:02 +01:00
if ( player ! = null & & AllowRetry )
2020-03-17 17:45:25 +09:00
{
2020-03-30 18:56:35 +09:00
buttons . Add ( new RetryButton { Width = 300 } ) ;
2024-12-13 18:14:45 +09:00
allowHotkeyRetry = true ;
}
2020-03-29 23:52:50 +09:00
2024-12-13 18:14:45 +09:00
if ( allowHotkeyRetry )
{
2020-03-30 18:56:35 +09:00
AddInternal ( new HotkeyRetryOverlay
{
Action = ( ) = >
2020-03-17 17:45:25 +09:00
{
2020-03-30 18:56:35 +09:00
if ( ! this . IsCurrentScreen ( ) ) return ;
2020-03-17 17:45:25 +09:00
2024-12-04 14:31:39 +09:00
skipExitTransition = true ;
2022-08-16 13:04:56 +09:00
player ? . Restart ( true ) ;
2020-03-30 18:56:35 +09:00
} ,
} ) ;
2020-03-17 17:45:25 +09:00
}
2024-07-17 12:45:20 -07:00
2024-07-20 11:49:46 -07:00
if ( Score ? . BeatmapInfo ! = null )
2024-07-27 18:32:35 -07:00
buttons . Add ( new CollectionButton ( Score . BeatmapInfo ) ) ;
2024-07-20 11:49:46 -07:00
2024-07-21 10:01:06 -07:00
if ( Score ? . BeatmapInfo ? . BeatmapSet ! = null & & Score . BeatmapInfo . BeatmapSet . OnlineID > 0 )
2024-07-27 18:32:35 -07:00
buttons . Add ( new FavouriteButton ( Score . BeatmapInfo . BeatmapSet ) ) ;
2020-03-17 17:43:16 +09:00
}
2020-05-16 19:00:20 +09:00
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
2023-08-01 19:00:01 +09:00
StatisticsPanel . State . BindValueChanged ( onStatisticsStateChanged , true ) ;
2025-02-26 18:58:29 +09:00
fetchScores ( null ) ;
2020-05-16 19:00:20 +09:00
}
2020-07-22 20:24:55 +09:00
protected override void Update ( )
{
base . Update ( ) ;
2025-02-26 18:58:29 +09:00
if ( ScorePanelList . IsScrolledToStart )
fetchScores ( - 1 ) ;
else if ( ScorePanelList . IsScrolledToEnd )
fetchScores ( 1 ) ;
2020-07-22 20:24:55 +09:00
}
2025-02-03 14:15:43 +09:00
#region Applause
private PoolableSkinnableSample ? rankApplauseSound ;
public void PlayApplause ( ScoreRank rank )
{
const double applause_volume = 0.8f ;
if ( ! this . IsCurrentScreen ( ) )
return ;
rankApplauseSound ? . Dispose ( ) ;
var applauseSamples = new List < string > ( ) ;
if ( rank > = ScoreRank . B )
// when rank is B or higher, play legacy applause sample on legacy skins.
applauseSamples . Insert ( 0 , @"applause" ) ;
switch ( rank )
{
default :
case ScoreRank . D :
applauseSamples . Add ( @"Results/applause-d" ) ;
break ;
case ScoreRank . C :
applauseSamples . Add ( @"Results/applause-c" ) ;
break ;
case ScoreRank . B :
applauseSamples . Add ( @"Results/applause-b" ) ;
break ;
case ScoreRank . A :
applauseSamples . Add ( @"Results/applause-a" ) ;
break ;
case ScoreRank . S :
case ScoreRank . SH :
case ScoreRank . X :
case ScoreRank . XH :
applauseSamples . Add ( @"Results/applause-s" ) ;
break ;
}
LoadComponentAsync ( rankApplauseSound = new PoolableSkinnableSample ( new SampleInfo ( applauseSamples . ToArray ( ) ) ) , s = >
{
if ( ! this . IsCurrentScreen ( ) | | s ! = rankApplauseSound )
return ;
2025-02-20 23:42:32 +09:00
AddInternal ( rankApplauseSound ) ;
2025-02-03 14:15:43 +09:00
rankApplauseSound . VolumeTo ( applause_volume ) ;
rankApplauseSound . Play ( ) ;
} ) ;
}
#endregion
2025-02-26 18:58:29 +09:00
/// <summary>
/// Fetches the next page of scores in the given direction.
/// </summary>
/// <param name="direction">The direction, or <c>null</c> to fetch any scores.</param>
private void fetchScores ( int? direction )
{
Debug . Assert ( direction = = null | | direction = = - 1 | | direction = = 1 ) ;
if ( ! lastFetchTask . IsCompleted )
return ;
lastFetchTask = Task . Run ( async ( ) = >
{
ScoreInfo [ ] scores ;
switch ( direction )
{
default :
scores = await FetchScores ( ) . ConfigureAwait ( false ) ;
break ;
case - 1 :
case 1 :
scores = await FetchNextPage ( direction . Value ) . ConfigureAwait ( false ) ;
break ;
}
await addScores ( scores ) . ConfigureAwait ( false ) ;
} ) ;
}
2020-05-26 17:00:41 +09:00
/// <summary>
/// Performs a fetch/refresh of scores to be displayed.
/// </summary>
2025-02-25 22:55:55 +09:00
protected virtual Task < ScoreInfo [ ] > FetchScores ( ) = > Task . FromResult < ScoreInfo [ ] > ( [ ] ) ;
2020-05-26 17:00:41 +09:00
2020-07-28 20:58:13 +09:00
/// <summary>
2025-02-25 21:54:13 +09:00
/// Performs a fetch of the next page of scores. This is invoked every frame.
2020-07-28 20:58:13 +09:00
/// </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>
2025-02-25 22:55:55 +09:00
protected virtual Task < ScoreInfo [ ] > FetchNextPage ( int direction ) = > Task . FromResult < ScoreInfo [ ] > ( [ ] ) ;
2020-07-22 20:24:55 +09:00
2022-12-24 10:35:42 +01:00
/// <summary>
2023-08-01 19:00:01 +09:00
/// Creates the <see cref="Statistics.StatisticsPanel"/> to be used to display extended information about scores.
2022-12-24 10:35:42 +01:00
/// </summary>
2024-02-22 19:49:14 +01:00
private StatisticsPanel createStatisticsPanel ( )
{
return ShowUserStatistics & & Score ! = null
? new UserStatisticsPanel ( Score )
: new StatisticsPanel ( ) ;
}
2022-12-24 10:35:42 +01:00
2025-02-25 23:17:02 +09:00
private Task addScores ( ScoreInfo [ ] scores )
2020-07-22 20:24:55 +09:00
{
2025-02-25 23:17:02 +09:00
var tcs = new TaskCompletionSource ( ) ;
Schedule ( ( ) = >
2025-02-25 21:54:13 +09:00
{
2025-02-25 23:17:02 +09:00
foreach ( var s in scores )
{
var panel = ScorePanelList . AddScore ( s ) ;
if ( detachedPanel ! = null )
panel . Alpha = 0 ;
}
2020-07-22 20:24:55 +09:00
2025-02-25 23:17:02 +09:00
// allow a frame for scroll container to adjust its dimensions with the added scores before fetching again.
Schedule ( ( ) = > tcs . SetResult ( ) ) ;
2024-01-09 21:28:46 +09:00
2025-02-25 23:17:02 +09:00
if ( ScorePanelList . IsEmpty )
{
// This can happen if for example a beatmap that is part of a playlist hasn't been played yet.
VerticalScrollContent . Add ( new MessagePlaceholder ( LeaderboardStrings . NoRecordsYet ) ) ;
}
2025-02-25 22:51:36 +09:00
2025-02-25 23:17:02 +09:00
OnScoresAdded ( scores ) ;
} ) ;
return tcs . Task ;
}
2020-07-22 20:24:55 +09:00
2025-02-25 22:51:36 +09:00
/// <summary>
/// Invoked after online scores are fetched and added to the list.
/// </summary>
/// <param name="scores">The scores that were added.</param>
2025-02-25 22:55:55 +09:00
protected virtual void OnScoresAdded ( ScoreInfo [ ] scores )
2025-02-25 22:51:36 +09:00
{
}
2022-04-22 00:52:44 +09:00
public override void OnEntering ( ScreenTransitionEvent e )
2020-03-17 17:43:16 +09:00
{
2022-04-22 00:52:44 +09:00
base . OnEntering ( e ) ;
2020-03-17 17:43:16 +09:00
2021-01-04 18:32:23 +09:00
ApplyToBackground ( b = >
{
b . BlurAmount . Value = BACKGROUND_BLUR ;
2021-06-09 17:18:52 +09:00
b . FadeColour ( OsuColour . Gray ( 0.5f ) , 250 ) ;
2021-01-04 18:32:23 +09:00
} ) ;
2020-03-17 17:43:16 +09:00
bottomPanel . FadeTo ( 1 , 250 ) ;
2022-07-22 19:06:31 +09:00
popInSample ? . Play ( ) ;
2020-03-17 17:43:16 +09:00
}
2022-04-22 00:52:44 +09:00
public override bool OnExiting ( ScreenExitEvent e )
2020-07-14 00:00:43 -07:00
{
2022-04-22 00:52:44 +09:00
if ( base . OnExiting ( e ) )
2021-06-09 17:19:36 +09:00
return true ;
2020-07-14 00:00:43 -07:00
2024-01-29 15:39:31 +09:00
// This is a stop-gap safety against components holding references to gameplay after exiting the gameplay flow.
// Right now, HitEvents are only used up to the results screen. If this changes in the future we need to remove
// HitObject references from HitEvent.
2024-01-29 16:13:30 +09:00
Score ? . HitEvents . Clear ( ) ;
2024-01-29 15:39:31 +09:00
2024-12-04 14:31:39 +09:00
if ( ! skipExitTransition )
this . FadeOut ( 100 ) ;
2025-01-31 16:22:37 +09:00
2025-02-03 14:15:43 +09:00
rankApplauseSound ? . Stop ( ) ;
2021-06-09 17:19:36 +09:00
return false ;
2020-07-14 00:00:43 -07:00
}
public override bool OnBackButton ( )
2020-03-17 17:43:16 +09:00
{
2023-08-01 19:00:01 +09:00
if ( StatisticsPanel . State . Value = = Visibility . Visible )
2020-06-18 22:27:27 +09:00
{
2023-08-01 19:00:01 +09:00
StatisticsPanel . Hide ( ) ;
2020-06-18 22:27:27 +09:00
return true ;
}
2020-07-14 00:00:43 -07:00
return false ;
2020-03-17 17:43:16 +09:00
}
2020-03-17 22:21:16 +09:00
2024-04-25 09:52:26 +02:00
private ScorePanel ? detachedPanel ;
2020-06-19 17:28:35 +09:00
2020-06-18 22:27:27 +09:00
private void onStatisticsStateChanged ( ValueChangedEvent < Visibility > state )
2020-03-17 22:21:16 +09:00
{
2020-06-19 17:28:35 +09:00
if ( state . NewValue = = Visibility . Visible )
2020-03-17 22:21:16 +09:00
{
2024-05-14 12:57:30 +02:00
Debug . Assert ( SelectedScore . Value ! = null ) ;
2020-06-19 17:28:35 +09:00
// Detach the panel in its original location, and move into the desired location in the local container.
2020-07-31 19:57:05 +09:00
var expandedPanel = ScorePanelList . GetPanelForScore ( SelectedScore . Value ) ;
2020-06-19 17:28:35 +09:00
var screenSpacePos = expandedPanel . ScreenSpaceDrawQuad . TopLeft ;
2020-06-18 22:27:27 +09:00
2020-06-19 17:28:35 +09:00
// Detach and move into the local container.
2020-07-31 19:57:05 +09:00
ScorePanelList . Detach ( expandedPanel ) ;
2020-06-19 17:28:35 +09:00
detachedPanelContainer . Add ( expandedPanel ) ;
2020-06-22 15:42:55 +09:00
// Move into its original location in the local container first, then to the final location.
2021-10-27 13:04:41 +09:00
float origLocation = detachedPanelContainer . ToLocalSpace ( screenSpacePos ) . X ;
2020-09-24 12:49:32 +09:00
expandedPanel . MoveToX ( origLocation )
2020-06-22 15:42:55 +09:00
. Then ( )
2023-07-07 17:32:22 +09:00
. MoveToX ( StatisticsPanel . SIDE_PADDING , 400 , Easing . OutElasticQuarter ) ;
2020-06-19 17:28:35 +09:00
// Hide contracted panels.
2020-07-31 19:57:05 +09:00
foreach ( var contracted in ScorePanelList . GetScorePanels ( ) . Where ( p = > p . State = = PanelState . Contracted ) )
2020-06-19 17:28:35 +09:00
contracted . FadeOut ( 150 , Easing . OutQuint ) ;
2020-07-31 19:57:05 +09:00
ScorePanelList . HandleInput = false ;
2020-06-19 17:28:35 +09:00
// Dim background.
2023-07-07 17:32:22 +09:00
ApplyToBackground ( b = > b . FadeColour ( OsuColour . Gray ( 0.4f ) , 400 , Easing . OutQuint ) ) ;
2020-06-19 17:28:35 +09:00
detachedPanel = expandedPanel ;
2020-06-18 16:50:45 +09:00
}
2020-06-19 17:28:35 +09:00
else if ( detachedPanel ! = null )
2020-06-18 16:50:45 +09:00
{
2020-06-19 17:28:35 +09:00
var screenSpacePos = detachedPanel . ScreenSpaceDrawQuad . TopLeft ;
2020-06-18 22:27:27 +09:00
2020-06-19 17:28:35 +09:00
// Remove from the local container and re-attach.
2022-08-26 15:19:05 +09:00
detachedPanelContainer . Remove ( detachedPanel , false ) ;
2020-07-31 19:57:05 +09:00
ScorePanelList . Attach ( detachedPanel ) ;
2020-06-18 16:50:45 +09:00
2020-06-22 15:42:55 +09:00
// Move into its original location in the attached container first, then to the final location.
2023-10-17 17:40:44 +09:00
float origLocation = detachedPanel . Parent ! . ToLocalSpace ( screenSpacePos ) . X ;
2021-11-17 23:15:51 -08:00
detachedPanel . MoveToX ( origLocation )
2020-06-22 15:42:55 +09:00
. Then ( )
2023-07-07 17:32:22 +09:00
. MoveToX ( 0 , 250 , Easing . OutElasticQuarter ) ;
2020-06-19 17:28:35 +09:00
// Show contracted panels.
2020-07-31 19:57:05 +09:00
foreach ( var contracted in ScorePanelList . GetScorePanels ( ) . Where ( p = > p . State = = PanelState . Contracted ) )
2020-06-19 17:28:35 +09:00
contracted . FadeIn ( 150 , Easing . OutQuint ) ;
2020-07-31 19:57:05 +09:00
ScorePanelList . HandleInput = true ;
2020-06-19 17:28:35 +09:00
// Un-dim background.
2023-07-07 17:32:22 +09:00
ApplyToBackground ( b = > b . FadeColour ( OsuColour . Gray ( 0.5f ) , 250 , Easing . OutQuint ) ) ;
2020-06-19 17:28:35 +09:00
detachedPanel = null ;
2020-03-17 22:21:16 +09:00
}
}
2020-06-30 16:36:53 +09:00
2021-09-16 18:26:12 +09:00
public bool OnPressed ( KeyBindingPressEvent < GlobalAction > e )
2020-10-10 18:30:13 -07:00
{
2021-11-18 12:35:47 +09:00
if ( e . Repeat )
return false ;
2021-09-16 18:26:12 +09:00
switch ( e . Action )
2020-10-10 18:30:13 -07:00
{
2024-04-20 04:53:31 +08:00
case GlobalAction . QuickExit :
if ( this . IsCurrentScreen ( ) )
{
this . Exit ( ) ;
return true ;
}
break ;
2020-10-10 18:30:13 -07:00
case GlobalAction . Select :
2024-07-25 11:42:56 +02:00
if ( SelectedScore . Value ! = null )
StatisticsPanel . ToggleVisibility ( ) ;
2020-10-10 18:30:13 -07:00
return true ;
}
return false ;
}
2021-09-16 18:26:12 +09:00
public void OnReleased ( KeyBindingReleaseEvent < GlobalAction > e )
2020-10-10 18:30:13 -07:00
{
}
2021-08-13 16:14:23 +09:00
protected partial class VerticalScrollContainer : OsuScrollContainer
2020-06-30 16:36:53 +09:00
{
protected override Container < Drawable > Content = > content ;
private readonly Container content ;
public VerticalScrollContainer ( )
{
2021-08-13 16:14:23 +09:00
Masking = false ;
2020-06-30 16:36:53 +09:00
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 17:43:16 +09:00
}
}