2020-03-17 07:59:34 +00: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 07:37:17 +00:00
#nullable disable
2020-03-17 07:59:34 +00:00
using System ;
using osu.Framework ;
using osu.Framework.Allocation ;
2021-12-12 21:54:57 +00:00
using osu.Framework.Bindables ;
2020-03-17 07:59:34 +00:00
using osu.Framework.Extensions.Color4Extensions ;
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Colour ;
using osu.Framework.Graphics.Containers ;
using osu.Framework.Graphics.Shapes ;
2020-05-20 14:46:47 +00:00
using osu.Framework.Input.Events ;
2020-03-17 07:59:34 +00:00
using osu.Game.Scoring ;
2020-05-16 09:17:32 +00:00
using osu.Game.Screens.Ranking.Contracted ;
2020-03-17 07:59:34 +00:00
using osu.Game.Screens.Ranking.Expanded ;
2020-07-22 02:29:23 +00:00
using osu.Game.Users ;
2020-03-17 07:59:34 +00: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 08:47:14 +00:00
public const float CONTRACTED_WIDTH = 130 ;
2020-03-17 07:59:34 +00:00
/// <summary>
/// Height of the panel when contracted.
/// </summary>
2020-10-09 04:26:09 +00:00
private const float contracted_height = 385 ;
2020-03-17 07:59:34 +00:00
/// <summary>
/// Width of the panel when expanded.
/// </summary>
2020-05-01 05:13:38 +00:00
public const float EXPANDED_WIDTH = 360 ;
2020-03-17 07:59:34 +00:00
/// <summary>
/// Height of the panel when expanded.
/// </summary>
2020-10-07 07:30:26 +00:00
private const float expanded_height = 586 ;
2020-03-17 07:59:34 +00: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 08:47:14 +00:00
private const float contracted_top_layer_height = 30 ;
2020-03-17 07:59:34 +00:00
/// <summary>
/// Duration for the panel to resize into its expanded/contracted size.
/// </summary>
2021-05-18 18:19:18 +00:00
public const double RESIZE_DURATION = 200 ;
2020-03-17 07:59:34 +00:00
/// <summary>
2021-05-18 18:19:18 +00:00
/// Delay after <see cref="RESIZE_DURATION"/> before the top layer is expanded.
2020-03-17 07:59:34 +00:00
/// </summary>
2021-05-18 18:19:18 +00:00
public const double TOP_LAYER_EXPAND_DELAY = 100 ;
2020-03-17 07:59:34 +00: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 09:17:32 +00:00
private static readonly Color4 contracted_middle_layer_colour = Color4Extensions . FromHex ( "#353535" ) ;
2020-03-17 07:59:34 +00:00
public event Action < PanelState > StateChanged ;
2020-06-18 07:50:45 +00:00
2021-12-12 21:54:57 +00:00
/// <summary>
/// The position of the score in the rankings.
/// </summary>
public readonly Bindable < int? > ScorePosition = new Bindable < int? > ( ) ;
2020-06-18 07:50:45 +00:00
/// <summary>
2020-06-19 12:41:48 +00:00
/// An action to be invoked if this <see cref="ScorePanel"/> is clicked while in an expanded state.
2020-06-18 07:50:45 +00:00
/// </summary>
2020-06-19 12:41:48 +00:00
public Action PostExpandAction ;
2020-06-18 07:50:45 +00:00
2020-05-20 14:46:47 +00:00
public readonly ScoreInfo Score ;
2020-03-17 07:59:34 +00:00
2020-10-29 08:04:33 +00:00
private bool displayWithFlair ;
2020-10-29 07:11:25 +00:00
2020-05-21 13:07:06 +00:00
private Container content ;
2020-03-17 07:59:34 +00: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 ;
2020-10-29 07:11:25 +00:00
public ScorePanel ( ScoreInfo score , bool isNewLocalScore = false )
2020-03-17 07:59:34 +00:00
{
2020-05-20 14:46:47 +00:00
Score = score ;
2020-10-29 08:04:33 +00:00
displayWithFlair = isNewLocalScore ;
2021-12-12 21:54:57 +00:00
ScorePosition . Value = score . Position ;
2020-03-17 07:59:34 +00:00
}
[BackgroundDependencyLoader]
private void load ( )
{
2020-10-07 07:30:26 +00: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 ;
2020-05-21 13:07:06 +00:00
InternalChild = content = new Container
2020-03-17 07:59:34 +00:00
{
2020-05-21 13:07:06 +00:00
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
2020-05-26 07:27:41 +00:00
Size = new Vector2 ( 40 ) ,
2020-10-07 07:30:26 +00:00
Y = vertical_fudge ,
2020-05-21 13:07:06 +00:00
Children = new Drawable [ ]
2020-03-17 07:59:34 +00:00
{
2020-05-21 13:07:06 +00:00
topLayerContainer = new Container
2020-03-17 07:59:34 +00:00
{
2020-05-21 13:07:06 +00:00
Name = "Top layer" ,
RelativeSizeAxes = Axes . X ,
2020-05-26 07:27:41 +00:00
Alpha = 0 ,
2020-05-21 13:07:06 +00:00
Height = 120 ,
Children = new Drawable [ ]
2020-03-17 07:59:34 +00:00
{
2020-05-21 13:07:06 +00: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 07:59:34 +00:00
{
2020-05-21 13:07:06 +00:00
Name = "Middle layer" ,
RelativeSizeAxes = Axes . Both ,
Children = new Drawable [ ]
2020-03-17 07:59:34 +00:00
{
2020-05-21 13:07:06 +00:00
new Container
{
RelativeSizeAxes = Axes . Both ,
CornerRadius = 20 ,
CornerExponent = 2.5f ,
Masking = true ,
2020-07-22 02:29:23 +00: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 11:50:55 +00:00
}
2020-07-22 02:29:23 +00:00
}
2020-05-21 13:07:06 +00:00
} ,
middleLayerContentContainer = new Container { RelativeSizeAxes = Axes . Both }
}
2020-03-17 07:59:34 +00:00
}
}
} ;
}
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
updateState ( ) ;
2020-07-22 02:29:23 +00:00
topLayerBackground . FinishTransforms ( false , nameof ( Colour ) ) ;
middleLayerBackground . FinishTransforms ( false , nameof ( Colour ) ) ;
2020-03-17 07:59:34 +00:00
}
private PanelState state = PanelState . Contracted ;
public PanelState State
{
get = > state ;
set
{
if ( state = = value )
return ;
state = value ;
2020-10-29 08:04:33 +00:00
if ( IsLoaded )
2020-03-17 07:59:34 +00:00
updateState ( ) ;
StateChanged ? . Invoke ( value ) ;
}
}
private void updateState ( )
{
topLayerContent ? . FadeOut ( content_fade_duration ) . Expire ( ) ;
middleLayerContent ? . FadeOut ( content_fade_duration ) . Expire ( ) ;
switch ( state )
{
case PanelState . Expanded :
2020-05-21 13:07:06 +00:00
Size = new Vector2 ( EXPANDED_WIDTH , expanded_height ) ;
2020-03-17 07:59:34 +00:00
2021-05-18 18:19:18 +00:00
topLayerBackground . FadeColour ( expanded_top_layer_colour , RESIZE_DURATION , Easing . OutQuint ) ;
middleLayerBackground . FadeColour ( expanded_middle_layer_colour , RESIZE_DURATION , Easing . OutQuint ) ;
2020-03-17 07:59:34 +00:00
2021-12-12 21:54:57 +00:00
topLayerContentContainer . Add ( topLayerContent = new ExpandedPanelTopContent ( Score . User ) { Alpha = 0 } ) ;
middleLayerContentContainer . Add ( middleLayerContent = new ExpandedPanelMiddleContent ( Score , displayWithFlair ) { Alpha = 0 } ) ;
2020-10-29 08:04:33 +00:00
// only the first expanded display should happen with flair.
displayWithFlair = false ;
2020-03-17 07:59:34 +00:00
break ;
case PanelState . Contracted :
2020-05-21 13:07:06 +00:00
Size = new Vector2 ( CONTRACTED_WIDTH , contracted_height ) ;
2020-03-17 07:59:34 +00:00
2021-05-18 18:19:18 +00:00
topLayerBackground . FadeColour ( contracted_top_layer_colour , RESIZE_DURATION , Easing . OutQuint ) ;
middleLayerBackground . FadeColour ( contracted_middle_layer_colour , RESIZE_DURATION , Easing . OutQuint ) ;
2020-05-16 09:17:32 +00:00
2021-12-12 21:54:57 +00:00
topLayerContentContainer . Add ( topLayerContent = new ContractedPanelTopContent
{
ScorePosition = { BindTarget = ScorePosition } ,
Alpha = 0
} ) ;
middleLayerContentContainer . Add ( middleLayerContent = new ContractedPanelMiddleContent ( Score ) { Alpha = 0 } ) ;
2020-03-17 07:59:34 +00:00
break ;
}
2021-05-18 18:19:18 +00:00
content . ResizeTo ( Size , RESIZE_DURATION , Easing . OutQuint ) ;
2020-05-21 13:07:06 +00:00
2020-05-21 09:07:31 +00: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 15:52:39 +00:00
using ( BeginDelayedSequence ( topLayerExpanded ? 0 : RESIZE_DURATION + TOP_LAYER_EXPAND_DELAY ) )
2020-03-17 07:59:34 +00:00
{
2020-05-26 07:27:41 +00:00
topLayerContainer . FadeIn ( ) ;
2020-03-17 07:59:34 +00: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 14:46:47 +00:00
2020-06-18 07:50:45 +00:00
public override Vector2 Size
{
get = > base . Size ;
set
{
base . Size = value ;
2020-06-19 12:41:48 +00: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 08:28:35 +00:00
if ( trackingContainer ! = null )
trackingContainer . Size = value ;
2020-06-18 07:50:45 +00:00
}
}
2020-05-20 14:46:47 +00:00
protected override bool OnClick ( ClickEvent e )
{
if ( State = = PanelState . Contracted )
2020-06-18 07:50:45 +00:00
{
2020-06-19 12:41:48 +00:00
State = PanelState . Expanded ;
2020-06-18 07:50:45 +00:00
return true ;
}
PostExpandAction ? . Invoke ( ) ;
2020-05-20 14:46:47 +00:00
return true ;
}
2020-06-04 12:48:55 +00:00
public override bool ReceivePositionalInputAt ( Vector2 screenSpacePos )
= > base . ReceivePositionalInputAt ( screenSpacePos )
| | topLayerContainer . ReceivePositionalInputAt ( screenSpacePos )
| | middleLayerContainer . ReceivePositionalInputAt ( screenSpacePos ) ;
2020-06-17 13:29:00 +00:00
2020-06-19 08:28:35 +00:00
private ScorePanelTrackingContainer trackingContainer ;
2020-06-17 13:29:00 +00:00
2020-06-19 12:41:48 +00: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 08:28:35 +00:00
public ScorePanelTrackingContainer CreateTrackingContainer ( )
2020-06-17 13:29:00 +00:00
{
2020-06-19 08:28:35 +00:00
if ( trackingContainer ! = null )
throw new InvalidOperationException ( "A score panel container has already been created." ) ;
2020-06-18 07:50:45 +00:00
2020-06-19 08:28:35 +00:00
return trackingContainer = new ScorePanelTrackingContainer ( this ) ;
2020-06-17 13:29:00 +00:00
}
2020-03-17 07:59:34 +00:00
}
}