2020-03-17 15:59:34 +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.
2022-06-17 15:37:17 +08:00
#nullable disable
2020-03-17 15:59:34 +08:00
using System ;
using osu.Framework ;
using osu.Framework.Allocation ;
2022-07-22 18:06:31 +08:00
using osu.Framework.Audio ;
2021-12-13 05:54:57 +08:00
using osu.Framework.Bindables ;
2020-03-17 15:59:34 +08:00
using osu.Framework.Extensions.Color4Extensions ;
using osu.Framework.Graphics ;
2022-07-22 18:06:31 +08:00
using osu.Framework.Graphics.Audio ;
2020-03-17 15:59:34 +08:00
using osu.Framework.Graphics.Colour ;
using osu.Framework.Graphics.Containers ;
using osu.Framework.Graphics.Shapes ;
2020-05-20 22:46:47 +08:00
using osu.Framework.Input.Events ;
2022-07-22 18:06:31 +08:00
using osu.Framework.Utils ;
2020-03-17 15:59:34 +08:00
using osu.Game.Scoring ;
2020-05-16 17:17:32 +08:00
using osu.Game.Screens.Ranking.Contracted ;
2020-03-17 15:59:34 +08:00
using osu.Game.Screens.Ranking.Expanded ;
2020-07-22 10:29:23 +08:00
using osu.Game.Users ;
2020-03-17 15:59:34 +08:00
using osuTK ;
using osuTK.Graphics ;
namespace osu.Game.Screens.Ranking
{
public class ScorePanel : CompositeDrawable , IStateful < PanelState >
{
/// <summary>
/// Width of the panel when contracted.
/// </summary>
2020-05-21 16:47:14 +08:00
public const float CONTRACTED_WIDTH = 130 ;
2020-03-17 15:59:34 +08:00
/// <summary>
/// Height of the panel when contracted.
/// </summary>
2020-10-09 12:26:09 +08:00
private const float contracted_height = 385 ;
2020-03-17 15:59:34 +08:00
/// <summary>
/// Width of the panel when expanded.
/// </summary>
2020-05-01 13:13:38 +08:00
public const float EXPANDED_WIDTH = 360 ;
2020-03-17 15:59:34 +08:00
/// <summary>
/// Height of the panel when expanded.
/// </summary>
2020-10-07 15:30:26 +08:00
private const float expanded_height = 586 ;
2020-03-17 15:59:34 +08:00
/// <summary>
/// Height of the top layer when the panel is expanded.
/// </summary>
private const float expanded_top_layer_height = 53 ;
/// <summary>
/// Height of the top layer when the panel is contracted.
/// </summary>
2020-05-21 16:47:14 +08:00
private const float contracted_top_layer_height = 30 ;
2020-03-17 15:59:34 +08:00
/// <summary>
/// Duration for the panel to resize into its expanded/contracted size.
/// </summary>
2021-05-19 02:19:18 +08:00
public const double RESIZE_DURATION = 200 ;
2020-03-17 15:59:34 +08:00
/// <summary>
2021-05-19 02:19:18 +08:00
/// Delay after <see cref="RESIZE_DURATION"/> before the top layer is expanded.
2020-03-17 15:59:34 +08:00
/// </summary>
2021-05-19 02:19:18 +08:00
public const double TOP_LAYER_EXPAND_DELAY = 100 ;
2020-03-17 15:59:34 +08:00
/// <summary>
/// Duration for the top layer expansion.
/// </summary>
private const double top_layer_expand_duration = 200 ;
/// <summary>
/// Duration for the panel contents to fade in.
/// </summary>
private const double content_fade_duration = 50 ;
private static readonly ColourInfo expanded_top_layer_colour = ColourInfo . GradientVertical ( Color4Extensions . FromHex ( "#444" ) , Color4Extensions . FromHex ( "#333" ) ) ;
private static readonly ColourInfo expanded_middle_layer_colour = ColourInfo . GradientVertical ( Color4Extensions . FromHex ( "#555" ) , Color4Extensions . FromHex ( "#333" ) ) ;
private static readonly Color4 contracted_top_layer_colour = Color4Extensions . FromHex ( "#353535" ) ;
2020-05-16 17:17:32 +08:00
private static readonly Color4 contracted_middle_layer_colour = Color4Extensions . FromHex ( "#353535" ) ;
2020-03-17 15:59:34 +08:00
public event Action < PanelState > StateChanged ;
2020-06-18 15:50:45 +08:00
2021-12-13 05:54:57 +08:00
/// <summary>
/// The position of the score in the rankings.
/// </summary>
public readonly Bindable < int? > ScorePosition = new Bindable < int? > ( ) ;
2020-06-18 15:50:45 +08:00
/// <summary>
2020-06-19 20:41:48 +08:00
/// An action to be invoked if this <see cref="ScorePanel"/> is clicked while in an expanded state.
2020-06-18 15:50:45 +08:00
/// </summary>
2020-06-19 20:41:48 +08:00
public Action PostExpandAction ;
2020-06-18 15:50:45 +08:00
2020-05-20 22:46:47 +08:00
public readonly ScoreInfo Score ;
2020-03-17 15:59:34 +08:00
2022-07-23 01:09:08 +08:00
[Resolved]
2022-07-23 01:27:47 +08:00
private OsuGameBase game { get ; set ; }
2022-07-23 01:09:08 +08:00
2022-08-30 14:05:21 +08:00
private AudioContainer audioContent ;
2020-10-29 15:11:25 +08:00
2022-07-22 18:52:42 +08:00
private bool displayWithFlair ;
2020-05-21 21:07:06 +08:00
2020-03-17 15:59:34 +08:00
private Container topLayerContainer ;
private Drawable topLayerBackground ;
private Container topLayerContentContainer ;
private Drawable topLayerContent ;
private Container middleLayerContainer ;
private Drawable middleLayerBackground ;
private Container middleLayerContentContainer ;
private Drawable middleLayerContent ;
2022-07-22 18:06:31 +08:00
private DrawableSample samplePanelFocus ;
2020-10-29 15:11:25 +08:00
public ScorePanel ( ScoreInfo score , bool isNewLocalScore = false )
2020-03-17 15:59:34 +08:00
{
2020-05-20 22:46:47 +08:00
Score = score ;
2020-10-29 16:04:33 +08:00
displayWithFlair = isNewLocalScore ;
2021-12-13 05:54:57 +08:00
ScorePosition . Value = score . Position ;
2020-03-17 15:59:34 +08:00
}
[BackgroundDependencyLoader]
2022-07-22 18:06:31 +08:00
private void load ( AudioManager audio )
2020-03-17 15:59:34 +08:00
{
2020-10-07 15:30:26 +08:00
// ScorePanel doesn't include the top extruding area in its own size.
// Adding a manual offset here allows the expanded version to take on an "acceptable" vertical centre when at 100% UI scale.
const float vertical_fudge = 20 ;
2022-08-30 14:05:21 +08:00
InternalChild = audioContent = new AudioContainer
2020-03-17 15:59:34 +08:00
{
2020-05-21 21:07:06 +08:00
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
2020-05-26 15:27:41 +08:00
Size = new Vector2 ( 40 ) ,
2020-10-07 15:30:26 +08:00
Y = vertical_fudge ,
2020-05-21 21:07:06 +08:00
Children = new Drawable [ ]
2020-03-17 15:59:34 +08:00
{
2020-05-21 21:07:06 +08:00
topLayerContainer = new Container
2020-03-17 15:59:34 +08:00
{
2020-05-21 21:07:06 +08:00
Name = "Top layer" ,
RelativeSizeAxes = Axes . X ,
2020-05-26 15:27:41 +08:00
Alpha = 0 ,
2020-05-21 21:07:06 +08:00
Height = 120 ,
Children = new Drawable [ ]
2020-03-17 15:59:34 +08:00
{
2020-05-21 21:07:06 +08:00
new Container
{
RelativeSizeAxes = Axes . Both ,
CornerRadius = 20 ,
CornerExponent = 2.5f ,
Masking = true ,
Child = topLayerBackground = new Box { RelativeSizeAxes = Axes . Both }
} ,
topLayerContentContainer = new Container { RelativeSizeAxes = Axes . Both }
}
} ,
middleLayerContainer = new Container
2020-03-17 15:59:34 +08:00
{
2020-05-21 21:07:06 +08:00
Name = "Middle layer" ,
RelativeSizeAxes = Axes . Both ,
Children = new Drawable [ ]
2020-03-17 15:59:34 +08:00
{
2020-05-21 21:07:06 +08:00
new Container
{
RelativeSizeAxes = Axes . Both ,
CornerRadius = 20 ,
CornerExponent = 2.5f ,
Masking = true ,
2020-07-22 10:29:23 +08:00
Children = new [ ]
{
middleLayerBackground = new Box { RelativeSizeAxes = Axes . Both } ,
new UserCoverBackground
{
RelativeSizeAxes = Axes . Both ,
User = Score . User ,
Colour = ColourInfo . GradientVertical ( Color4 . White . Opacity ( 0.5f ) , Color4Extensions . FromHex ( "#444" ) . Opacity ( 0 ) )
2020-07-28 19:50:55 +08:00
}
2020-07-22 10:29:23 +08:00
}
2020-05-21 21:07:06 +08:00
} ,
middleLayerContentContainer = new Container { RelativeSizeAxes = Axes . Both }
}
2022-07-22 18:06:31 +08:00
} ,
samplePanelFocus = new DrawableSample ( audio . Samples . Get ( @"Results/score-panel-focus" ) )
2020-03-17 15:59:34 +08:00
}
} ;
}
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
updateState ( ) ;
2020-07-22 10:29:23 +08:00
topLayerBackground . FinishTransforms ( false , nameof ( Colour ) ) ;
middleLayerBackground . FinishTransforms ( false , nameof ( Colour ) ) ;
2020-03-17 15:59:34 +08:00
}
private PanelState state = PanelState . Contracted ;
public PanelState State
{
get = > state ;
set
{
if ( state = = value )
return ;
state = value ;
2020-10-29 16:04:33 +08:00
if ( IsLoaded )
2022-07-22 18:06:31 +08:00
{
2020-03-17 15:59:34 +08:00
updateState ( ) ;
2022-07-22 18:06:31 +08:00
if ( value = = PanelState . Expanded )
playAppearSample ( ) ;
}
2020-03-17 15:59:34 +08:00
StateChanged ? . Invoke ( value ) ;
}
}
2022-07-23 01:09:08 +08:00
protected override void Update ( )
{
base . Update ( ) ;
2022-08-30 14:05:21 +08:00
audioContent . Balance . Value = ( ScreenSpaceDrawQuad . Centre . X / game . ScreenSpaceDrawQuad . Width ) * 2 - 1 ;
2022-07-23 01:09:08 +08:00
}
2022-07-22 18:06:31 +08:00
private void playAppearSample ( )
{
var channel = samplePanelFocus ? . GetChannel ( ) ;
if ( channel = = null ) return ;
channel . Frequency . Value = 0.99 + RNG . NextDouble ( 0.2 ) ;
channel . Play ( ) ;
}
2020-03-17 15:59:34 +08:00
private void updateState ( )
{
topLayerContent ? . FadeOut ( content_fade_duration ) . Expire ( ) ;
middleLayerContent ? . FadeOut ( content_fade_duration ) . Expire ( ) ;
switch ( state )
{
case PanelState . Expanded :
2020-05-21 21:07:06 +08:00
Size = new Vector2 ( EXPANDED_WIDTH , expanded_height ) ;
2020-03-17 15:59:34 +08:00
2021-05-19 02:19:18 +08:00
topLayerBackground . FadeColour ( expanded_top_layer_colour , RESIZE_DURATION , Easing . OutQuint ) ;
middleLayerBackground . FadeColour ( expanded_middle_layer_colour , RESIZE_DURATION , Easing . OutQuint ) ;
2020-03-17 15:59:34 +08:00
2022-07-22 18:06:31 +08:00
bool firstLoad = topLayerContent = = null ;
topLayerContentContainer . Add ( topLayerContent = new ExpandedPanelTopContent ( Score . User , firstLoad ) { Alpha = 0 } ) ;
2021-12-13 05:54:57 +08:00
middleLayerContentContainer . Add ( middleLayerContent = new ExpandedPanelMiddleContent ( Score , displayWithFlair ) { Alpha = 0 } ) ;
2020-10-29 16:04:33 +08:00
// only the first expanded display should happen with flair.
displayWithFlair = false ;
2020-03-17 15:59:34 +08:00
break ;
case PanelState . Contracted :
2020-05-21 21:07:06 +08:00
Size = new Vector2 ( CONTRACTED_WIDTH , contracted_height ) ;
2020-03-17 15:59:34 +08:00
2021-05-19 02:19:18 +08:00
topLayerBackground . FadeColour ( contracted_top_layer_colour , RESIZE_DURATION , Easing . OutQuint ) ;
middleLayerBackground . FadeColour ( contracted_middle_layer_colour , RESIZE_DURATION , Easing . OutQuint ) ;
2020-05-16 17:17:32 +08:00
2021-12-13 05:54:57 +08:00
topLayerContentContainer . Add ( topLayerContent = new ContractedPanelTopContent
{
ScorePosition = { BindTarget = ScorePosition } ,
Alpha = 0
} ) ;
middleLayerContentContainer . Add ( middleLayerContent = new ContractedPanelMiddleContent ( Score ) { Alpha = 0 } ) ;
2020-03-17 15:59:34 +08:00
break ;
}
2022-08-30 14:05:21 +08:00
audioContent . ResizeTo ( Size , RESIZE_DURATION , Easing . OutQuint ) ;
2020-05-21 21:07:06 +08:00
2020-05-21 17:07:31 +08:00
bool topLayerExpanded = topLayerContainer . Y < 0 ;
// If the top layer was already expanded, then we don't need to wait for the resize and can instead transform immediately. This looks better when changing the panel state.
2021-07-05 23:52:39 +08:00
using ( BeginDelayedSequence ( topLayerExpanded ? 0 : RESIZE_DURATION + TOP_LAYER_EXPAND_DELAY ) )
2020-03-17 15:59:34 +08:00
{
2020-05-26 15:27:41 +08:00
topLayerContainer . FadeIn ( ) ;
2020-03-17 15:59:34 +08:00
switch ( state )
{
case PanelState . Expanded :
topLayerContainer . MoveToY ( - expanded_top_layer_height / 2 , top_layer_expand_duration , Easing . OutQuint ) ;
middleLayerContainer . MoveToY ( expanded_top_layer_height / 2 , top_layer_expand_duration , Easing . OutQuint ) ;
break ;
case PanelState . Contracted :
topLayerContainer . MoveToY ( - contracted_top_layer_height / 2 , top_layer_expand_duration , Easing . OutQuint ) ;
middleLayerContainer . MoveToY ( contracted_top_layer_height / 2 , top_layer_expand_duration , Easing . OutQuint ) ;
break ;
}
topLayerContent ? . FadeIn ( content_fade_duration ) ;
middleLayerContent ? . FadeIn ( content_fade_duration ) ;
}
}
2020-05-20 22:46:47 +08:00
2020-06-18 15:50:45 +08:00
public override Vector2 Size
{
get = > base . Size ;
set
{
base . Size = value ;
2020-06-19 20:41:48 +08:00
// Auto-size isn't used to avoid 1-frame issues and because the score panel is removed/re-added to the container.
2020-06-19 16:28:35 +08:00
if ( trackingContainer ! = null )
trackingContainer . Size = value ;
2020-06-18 15:50:45 +08:00
}
}
2020-05-20 22:46:47 +08:00
protected override bool OnClick ( ClickEvent e )
{
if ( State = = PanelState . Contracted )
2020-06-18 15:50:45 +08:00
{
2020-06-19 20:41:48 +08:00
State = PanelState . Expanded ;
2020-06-18 15:50:45 +08:00
return true ;
}
PostExpandAction ? . Invoke ( ) ;
2020-05-20 22:46:47 +08:00
return true ;
}
2020-06-04 20:48:55 +08:00
public override bool ReceivePositionalInputAt ( Vector2 screenSpacePos )
= > base . ReceivePositionalInputAt ( screenSpacePos )
| | topLayerContainer . ReceivePositionalInputAt ( screenSpacePos )
| | middleLayerContainer . ReceivePositionalInputAt ( screenSpacePos ) ;
2020-06-17 21:29:00 +08:00
2020-06-19 16:28:35 +08:00
private ScorePanelTrackingContainer trackingContainer ;
2020-06-17 21:29:00 +08:00
2020-06-19 20:41:48 +08:00
/// <summary>
/// Creates a <see cref="ScorePanelTrackingContainer"/> which this <see cref="ScorePanel"/> can reside inside.
/// The <see cref="ScorePanelTrackingContainer"/> will track the size of this <see cref="ScorePanel"/>.
/// </summary>
/// <remarks>
/// This <see cref="ScorePanel"/> is immediately added as a child of the <see cref="ScorePanelTrackingContainer"/>.
/// </remarks>
/// <returns>The <see cref="ScorePanelTrackingContainer"/>.</returns>
/// <exception cref="InvalidOperationException">If a <see cref="ScorePanelTrackingContainer"/> already exists.</exception>
2020-06-19 16:28:35 +08:00
public ScorePanelTrackingContainer CreateTrackingContainer ( )
2020-06-17 21:29:00 +08:00
{
2020-06-19 16:28:35 +08:00
if ( trackingContainer ! = null )
throw new InvalidOperationException ( "A score panel container has already been created." ) ;
2020-06-18 15:50:45 +08:00
2020-06-19 16:28:35 +08:00
return trackingContainer = new ScorePanelTrackingContainer ( this ) ;
2020-06-17 21:29:00 +08:00
}
2020-03-17 15:59:34 +08:00
}
}