2020-06-15 21:45:18 +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-06-19 19:31:52 +08:00
using System.Linq ;
2020-06-19 18:12:55 +08:00
using System.Threading ;
2020-06-22 17:38:41 +08:00
using System.Threading.Tasks ;
2020-06-18 21:21:30 +08:00
using osu.Framework.Allocation ;
using osu.Framework.Bindables ;
2020-06-15 21:45:18 +08:00
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
2020-06-23 14:21:23 +08:00
using osu.Framework.Input.Events ;
2020-06-22 17:38:41 +08:00
using osu.Game.Beatmaps ;
2022-02-02 17:20:22 +08:00
using osu.Game.Graphics.Containers ;
2020-06-19 18:12:55 +08:00
using osu.Game.Graphics.UserInterface ;
2020-06-18 21:21:30 +08:00
using osu.Game.Online.Placeholders ;
2020-06-15 21:45:18 +08:00
using osu.Game.Scoring ;
using osuTK ;
namespace osu.Game.Screens.Ranking.Statistics
{
2020-06-18 15:50:45 +08:00
public class StatisticsPanel : VisibilityContainer
2020-06-15 21:45:18 +08:00
{
2020-06-18 16:06:05 +08:00
public const float SIDE_PADDING = 30 ;
2020-06-18 21:21:30 +08:00
public readonly Bindable < ScoreInfo > Score = new Bindable < ScoreInfo > ( ) ;
2020-06-18 15:50:45 +08:00
2020-06-18 21:21:30 +08:00
protected override bool StartHidden = > true ;
2020-06-15 21:45:18 +08:00
2020-06-22 17:38:41 +08:00
[Resolved]
private BeatmapManager beatmapManager { get ; set ; }
2020-06-18 21:21:30 +08:00
private readonly Container content ;
2020-06-19 18:12:55 +08:00
private readonly LoadingSpinner spinner ;
2020-06-15 21:45:18 +08:00
2020-06-18 21:21:30 +08:00
public StatisticsPanel ( )
{
2020-06-18 15:50:45 +08:00
InternalChild = new Container
2020-06-15 21:45:18 +08:00
{
2020-06-18 15:50:45 +08:00
RelativeSizeAxes = Axes . Both ,
Padding = new MarginPadding
2020-06-15 21:45:18 +08:00
{
2020-06-18 16:06:05 +08:00
Left = ScorePanel . EXPANDED_WIDTH + SIDE_PADDING * 3 ,
Right = SIDE_PADDING ,
Top = SIDE_PADDING ,
Bottom = 50 // Approximate padding to the bottom of the score panel.
2020-06-15 21:45:18 +08:00
} ,
2020-06-19 18:12:55 +08:00
Children = new Drawable [ ]
{
content = new Container { RelativeSizeAxes = Axes . Both } ,
spinner = new LoadingSpinner ( )
}
2020-06-18 21:21:30 +08:00
} ;
}
[BackgroundDependencyLoader]
private void load ( )
{
Score . BindValueChanged ( populateStatistics , true ) ;
}
2020-06-19 18:12:55 +08:00
private CancellationTokenSource loadCancellation ;
2020-06-18 21:21:30 +08:00
private void populateStatistics ( ValueChangedEvent < ScoreInfo > score )
{
2020-06-19 18:12:55 +08:00
loadCancellation ? . Cancel ( ) ;
2020-06-22 19:20:42 +08:00
loadCancellation = null ;
2020-06-19 18:12:55 +08:00
2020-06-18 21:21:30 +08:00
foreach ( var child in content )
child . FadeOut ( 150 ) . Expire ( ) ;
2021-11-08 11:35:30 +08:00
spinner . Hide ( ) ;
2020-06-18 21:21:30 +08:00
var newScore = score . NewValue ;
2020-06-19 21:47:55 +08:00
if ( newScore = = null )
return ;
2022-02-02 13:41:51 +08:00
spinner . Show ( ) ;
var localCancellationSource = loadCancellation = new CancellationTokenSource ( ) ;
IBeatmap playableBeatmap = null ;
// Todo: The placement of this is temporary. Eventually we'll both generate the playable beatmap _and_ run through it in a background task to generate the hit events.
Task . Run ( ( ) = >
{
playableBeatmap = beatmapManager . GetWorkingBeatmap ( newScore . BeatmapInfo ) . GetPlayableBeatmap ( newScore . Ruleset , newScore . Mods ) ;
} , loadCancellation . Token ) . ContinueWith ( t = > Schedule ( ( ) = >
2020-09-24 11:39:08 +08:00
{
2022-02-02 17:20:22 +08:00
FillFlowContainer rows ;
Container < Drawable > container = new OsuScrollContainer ( Direction . Vertical )
2020-09-24 11:21:08 +08:00
{
2022-02-02 17:20:22 +08:00
RelativeSizeAxes = Axes . Both ,
2022-02-02 13:41:51 +08:00
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
2022-02-02 17:20:22 +08:00
Alpha = 0 ,
Children = new [ ]
{
rows = new FillFlowContainer
{
RelativeSizeAxes = Axes . X ,
AutoSizeAxes = Axes . Y
}
}
2022-02-02 13:41:51 +08:00
} ;
bool panelIsEmpty = true ;
bool hitEventsAvailable = newScore . HitEvents . Count ! = 0 ;
foreach ( var row in newScore . Ruleset . CreateInstance ( ) . CreateStatisticsForScore ( newScore , playableBeatmap ) )
{
var columnsToDisplay = hitEventsAvailable
? row . Columns
2022-02-02 17:26:17 +08:00
: row . Columns . Where ( c = > ! c . RequiresHitEvents ) . ToArray ( ) ;
2022-02-02 13:41:51 +08:00
2022-02-02 17:26:17 +08:00
if ( columnsToDisplay . Any ( ) )
2022-02-02 13:41:51 +08:00
panelIsEmpty = false ;
2022-02-02 17:20:22 +08:00
else
continue ;
2022-02-02 13:41:51 +08:00
rows . Add ( new GridContainer
2020-09-24 11:21:08 +08:00
{
2022-02-02 13:41:51 +08:00
Anchor = Anchor . TopCentre ,
Origin = Anchor . TopCentre ,
RelativeSizeAxes = Axes . X ,
AutoSizeAxes = Axes . Y ,
2022-02-02 17:20:22 +08:00
Margin = new MarginPadding { Bottom = 15 } ,
2022-02-02 13:41:51 +08:00
Content = new [ ]
2020-09-24 11:21:08 +08:00
{
2022-02-02 13:41:51 +08:00
columnsToDisplay ? . Select ( c = > new StatisticContainer ( c )
{
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
} ) . Cast < Drawable > ( ) . ToArray ( )
2020-09-24 11:21:08 +08:00
} ,
2022-02-02 17:26:17 +08:00
ColumnDimensions = Enumerable . Range ( 0 , columnsToDisplay . Length )
. Select ( i = > columnsToDisplay [ i ] . Dimension ? ? new Dimension ( ) ) . ToArray ( ) ,
2022-02-02 13:41:51 +08:00
RowDimensions = new [ ] { new Dimension ( GridSizeMode . AutoSize ) }
} ) ;
}
2020-06-15 21:45:18 +08:00
2022-02-02 13:41:51 +08:00
if ( ! hitEventsAvailable )
2020-06-22 17:38:41 +08:00
{
2022-02-02 17:20:22 +08:00
if ( panelIsEmpty )
2020-06-18 21:21:30 +08:00
{
2022-02-02 17:20:22 +08:00
// Replace the scroll container with fill flow container to get the message centered.
2022-02-02 19:00:46 +08:00
rows . Dispose ( ) ;
2022-02-02 17:32:16 +08:00
container . Dispose ( ) ;
2022-02-02 17:20:22 +08:00
container = new FillFlowContainer
2020-08-28 02:07:30 +08:00
{
2022-02-02 17:20:22 +08:00
RelativeSizeAxes = Axes . Both ,
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
Direction = FillDirection . Vertical ,
Children = new Drawable [ ]
2020-08-28 02:07:30 +08:00
{
2022-02-02 17:20:22 +08:00
new MessagePlaceholder ( "Extended statistics are only available after watching a replay!" ) ,
new ReplayDownloadButton ( newScore )
{
Scale = new Vector2 ( 1.5f ) ,
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
} ,
}
} ;
}
else
{
rows . Add ( new FillFlowContainer
{
RelativeSizeAxes = Axes . X ,
AutoSizeAxes = Axes . Y ,
Direction = FillDirection . Vertical ,
Anchor = Anchor . TopCentre ,
Origin = Anchor . TopCentre ,
Children = new Drawable [ ]
{
new MessagePlaceholder ( "More statistics available after watching a replay!" ) ,
new ReplayDownloadButton ( newScore )
{
Scale = new Vector2 ( 1.5f ) ,
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
} ,
}
} ) ;
}
2022-02-02 13:41:51 +08:00
}
2020-06-22 17:38:41 +08:00
2022-02-02 17:20:22 +08:00
LoadComponentAsync ( container , d = >
2022-02-02 13:41:51 +08:00
{
if ( ! Score . Value . Equals ( newScore ) )
return ;
spinner . Hide ( ) ;
content . Add ( d ) ;
d . FadeIn ( 250 , Easing . OutQuint ) ;
} , localCancellationSource . Token ) ;
} ) , localCancellationSource . Token ) ;
2020-06-15 21:45:18 +08:00
}
2020-06-18 15:50:45 +08:00
2020-06-23 14:21:23 +08:00
protected override bool OnClick ( ClickEvent e )
{
ToggleVisibility ( ) ;
return true ;
}
2020-06-19 20:41:48 +08:00
protected override void PopIn ( ) = > this . FadeIn ( 150 , Easing . OutQuint ) ;
2020-06-18 15:50:45 +08:00
2020-06-19 20:41:48 +08:00
protected override void PopOut ( ) = > this . FadeOut ( 150 , Easing . OutQuint ) ;
2020-06-22 19:20:42 +08:00
protected override void Dispose ( bool isDisposing )
{
loadCancellation ? . Cancel ( ) ;
base . Dispose ( isDisposing ) ;
}
2020-06-15 21:45:18 +08:00
}
}