1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-22 06:47:24 +08:00
osu-lazer/osu.Game/Screens/Ranking/ScorePanel.cs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

353 lines
14 KiB
C#
Raw Normal View History

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.
using System;
using osu.Framework;
using osu.Framework.Allocation;
2022-07-22 18:06:31 +08:00
using osu.Framework.Audio;
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;
using osu.Game.Screens.Ranking.Contracted;
2020-03-17 15:59:34 +08:00
using osu.Game.Screens.Ranking.Expanded;
using osu.Game.Users;
2020-03-17 15:59:34 +08:00
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Ranking
{
public partial 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>
private const float contracted_height = 385;
2020-03-17 15:59:34 +08:00
/// <summary>
/// Width of the panel when expanded.
/// </summary>
public const float EXPANDED_WIDTH = 360;
2020-03-17 15:59:34 +08:00
/// <summary>
/// Height of the panel when expanded.
/// </summary>
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");
private static readonly Color4 contracted_middle_layer_colour = Color4Extensions.FromHex("#353535");
2020-03-17 15:59:34 +08:00
2024-05-14 19:03:46 +08:00
public event Action<PanelState>? StateChanged;
/// <summary>
/// The position of the score in the rankings.
/// </summary>
public readonly Bindable<int?> ScorePosition = new Bindable<int?>();
/// <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.
/// </summary>
2024-05-14 19:03:46 +08:00
public Action? PostExpandAction;
2020-05-20 22:46:47 +08:00
public readonly ScoreInfo Score;
2020-03-17 15:59:34 +08:00
[Resolved]
2024-05-14 19:03:46 +08:00
private OsuGameBase game { get; set; } = null!;
2024-05-14 19:03:46 +08:00
private AudioContainer audioContent = null!;
private bool displayWithFlair;
2024-05-14 19:03:46 +08:00
private Container topLayerContainer = null!;
private Drawable topLayerBackground = null!;
private Container topLayerContentContainer = null!;
private Drawable? topLayerContent;
private Container middleLayerContainer = null!;
private Drawable middleLayerBackground = null!;
private Container middleLayerContentContainer = null!;
private Drawable? middleLayerContent;
2020-03-17 15:59:34 +08:00
2024-05-14 19:03:46 +08:00
private ScorePanelTrackingContainer? trackingContainer;
2020-03-17 15:59:34 +08:00
2024-05-14 19:03:46 +08:00
private DrawableSample? samplePanelFocus;
2022-07-22 18:06:31 +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;
displayWithFlair = isNewLocalScore;
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
{
// 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;
InternalChild = audioContent = new AudioContainer
2020-03-17 15:59:34 +08:00
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
2020-05-26 15:27:41 +08:00
Size = new Vector2(40),
Y = vertical_fudge,
Children = new Drawable[]
2020-03-17 15:59:34 +08:00
{
topLayerContainer = new Container
2020-03-17 15:59:34 +08:00
{
Name = "Top layer",
RelativeSizeAxes = Axes.X,
2020-05-26 15:27:41 +08:00
Alpha = 0,
Height = 120,
Children = new Drawable[]
2020-03-17 15:59:34 +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
{
Name = "Middle layer",
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
2020-03-17 15:59:34 +08:00
{
new Container
{
RelativeSizeAxes = Axes.Both,
CornerRadius = 20,
CornerExponent = 2.5f,
Masking = true,
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))
}
}
},
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();
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;
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);
}
}
protected override void Update()
{
base.Update();
2022-12-16 16:19:07 +08:00
audioContent.Balance.Value = ((ScreenSpaceDrawQuad.Centre.X / game.ScreenSpaceDrawQuad.Width) * 2 - 1) * OsuGameBase.SFX_STEREO_STRENGTH;
}
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:
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 });
middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, displayWithFlair) { Alpha = 0 });
// only the first expanded display should happen with flair.
displayWithFlair = false;
2020-03-17 15:59:34 +08:00
break;
case PanelState.Contracted:
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);
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;
}
audioContent.ResizeTo(Size, RESIZE_DURATION, Easing.OutQuint);
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
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.
if (trackingContainer != null)
trackingContainer.Size = value;
}
}
2020-05-20 22:46:47 +08:00
protected override bool OnClick(ClickEvent e)
{
if (State == PanelState.Contracted)
{
2020-06-19 20:41:48 +08:00
State = PanelState.Expanded;
return true;
}
PostExpandAction?.Invoke();
2020-05-20 22:46:47 +08:00
return true;
}
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 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>
public ScorePanelTrackingContainer CreateTrackingContainer()
2020-06-17 21:29:00 +08:00
{
if (trackingContainer != null)
throw new InvalidOperationException("A score panel container has already been created.");
return trackingContainer = new ScorePanelTrackingContainer(this);
2020-06-17 21:29:00 +08:00
}
2020-03-17 15:59:34 +08:00
}
}