2023-01-08 06:40:02 +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.
2023-01-08 08:47:22 +08:00
using System ;
2023-01-08 06:40:02 +08:00
using System.Collections.Generic ;
2023-01-08 08:47:22 +08:00
using System.Linq ;
2023-01-08 06:40:02 +08:00
using System.Threading ;
using osuTK ;
using osu.Framework.Allocation ;
using osu.Framework.Bindables ;
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
2023-01-08 22:42:42 +08:00
using osu.Framework.Graphics.Effects ;
2023-01-08 06:40:02 +08:00
using osu.Game.Beatmaps ;
using osu.Game.Beatmaps.Drawables ;
using osu.Game.Graphics ;
using osu.Game.Graphics.Sprites ;
using osu.Framework.Graphics.Shapes ;
using osu.Framework.Localisation ;
using osu.Game.Configuration ;
2023-01-08 08:47:22 +08:00
using osu.Game.Graphics.UserInterface ;
2023-01-08 06:40:02 +08:00
using osu.Game.Rulesets ;
using osu.Game.Rulesets.Mods ;
namespace osu.Game.Screens.Select
{
public partial class BeatmapInfoWedgeV2 : VisibilityContainer
{
2023-01-08 08:47:22 +08:00
private const float shear_width = 21 ;
2023-01-08 22:42:42 +08:00
private const float wedge_height = 120 ;
2023-01-08 06:40:02 +08:00
private const float transition_duration = 250 ;
2023-01-08 22:42:42 +08:00
private const float corner_radius = 10 ;
2023-01-17 05:46:18 +08:00
private const float colour_bar_width = 30 ;
2023-01-08 06:40:02 +08:00
2023-01-11 00:34:47 +08:00
/// Todo: move this const out to song select when more new design elements are implemented for the beatmap details area, since it applies to text alignment of various elements
private const float text_margin = 62 ;
2023-01-08 08:47:22 +08:00
private static readonly Vector2 wedged_container_shear = new Vector2 ( shear_width / wedge_height , 0 ) ;
2023-01-08 06:40:02 +08:00
[Resolved]
2023-01-08 19:56:32 +08:00
private IBindable < RulesetInfo > ruleset { get ; set ; } = null ! ;
2023-01-08 06:53:19 +08:00
2023-01-18 20:55:52 +08:00
[Resolved]
private OsuColour colours { get ; set ; } = null ! ;
2023-01-08 19:56:32 +08:00
protected Container ? DisplayedContent { get ; private set ; }
2023-01-08 06:40:02 +08:00
2023-01-08 19:56:32 +08:00
protected WedgeInfoText ? Info { get ; private set ; }
2023-01-08 06:53:19 +08:00
2023-01-08 08:24:47 +08:00
private readonly Container difficultyColourBar ;
2023-01-08 08:47:22 +08:00
private readonly StarCounter starCounter ;
2023-01-24 00:00:46 +08:00
private readonly BufferedContainer bufferedContent ;
2023-01-08 08:24:47 +08:00
2023-01-08 06:40:02 +08:00
public BeatmapInfoWedgeV2 ( )
{
2023-01-17 05:46:18 +08:00
Height = wedge_height ;
2023-01-08 06:40:02 +08:00
Shear = wedged_container_shear ;
Masking = true ;
2023-01-08 22:42:42 +08:00
EdgeEffect = new EdgeEffectParameters
{
Colour = Colour4 . Black . Opacity ( . 25f ) ,
Type = EdgeEffectType . Shadow ,
Radius = corner_radius ,
Roundness = corner_radius
} ;
CornerRadius = corner_radius ;
2023-01-08 08:47:22 +08:00
2023-01-24 00:00:46 +08:00
// We want to buffer the wedge to avoid weird transparency overlaps between the colour bar and the background.
Child = bufferedContent = new BufferedContainer
2023-01-08 06:40:02 +08:00
{
2023-01-24 00:00:46 +08:00
RelativeSizeAxes = Axes . Both ,
Children = new Drawable [ ]
2023-01-08 08:47:22 +08:00
{
2023-01-24 00:00:46 +08:00
// These elements can't be grouped with the rest of the content, due to being present either outside or under the backgrounds area
difficultyColourBar = new Container
{
Colour = Colour4 . Transparent ,
Depth = float . MaxValue ,
Anchor = Anchor . TopRight ,
Origin = Anchor . TopRight ,
RelativeSizeAxes = Axes . Y ,
// By limiting the width we avoid this box showing up as an outline around the drawables that are on top of it.
Width = colour_bar_width + corner_radius ,
Child = new Box { RelativeSizeAxes = Axes . Both }
} ,
starCounter = new StarCounter
{
Colour = Colour4 . Transparent ,
Anchor = Anchor . CentreRight ,
Origin = Anchor . Centre ,
Scale = new Vector2 ( 0.35f ) ,
Shear = - wedged_container_shear ,
X = - colour_bar_width / 2 ,
Direction = FillDirection . Vertical
}
2023-01-08 08:47:22 +08:00
}
2023-01-08 06:40:02 +08:00
} ;
}
[BackgroundDependencyLoader]
private void load ( )
{
ruleset . BindValueChanged ( _ = > updateDisplay ( ) ) ;
2023-01-08 08:47:22 +08:00
float starAngle = ( float ) ( Math . Atan ( shear_width / wedge_height ) * ( 180 / Math . PI ) ) ;
2023-01-08 22:42:42 +08:00
// Applying the rotation directly to the StarCounter distorts the stars, hence it is applied to the child container
2023-01-08 08:47:22 +08:00
starCounter . Children . First ( ) . Rotation = starAngle ;
2023-01-08 22:42:42 +08:00
// Makes sure the stars center themselves properly in the colour bar
2023-01-08 08:47:22 +08:00
starCounter . Children . First ( ) . Anchor = Anchor . Centre ;
starCounter . Children . First ( ) . Origin = Anchor . Centre ;
2023-01-08 06:40:02 +08:00
}
2023-01-30 20:13:57 +08:00
private const double animation_duration = 600 ;
2023-01-08 06:40:02 +08:00
protected override void PopIn ( )
{
this . MoveToX ( 0 , animation_duration , Easing . OutQuint ) ;
2023-01-30 20:13:57 +08:00
this . FadeIn ( 200 , Easing . In ) ;
2023-01-08 06:40:02 +08:00
}
protected override void PopOut ( )
{
2023-01-30 20:13:57 +08:00
this . MoveToX ( - 150 , animation_duration , Easing . OutQuint ) ;
this . FadeOut ( 200 , Easing . OutQuint ) ;
2023-01-08 06:40:02 +08:00
}
2023-01-08 19:56:32 +08:00
private WorkingBeatmap ? beatmap ;
2023-01-08 06:40:02 +08:00
2023-01-08 19:56:32 +08:00
public WorkingBeatmap ? Beatmap
2023-01-08 06:40:02 +08:00
{
get = > beatmap ;
set
{
if ( beatmap = = value ) return ;
beatmap = value ;
updateDisplay ( ) ;
}
}
public override bool IsPresent = > base . IsPresent | | DisplayedContent = = null ; // Visibility is updated in the LoadComponentAsync callback
2023-01-08 19:56:32 +08:00
private Container ? loadingInfo ;
2023-01-08 06:53:19 +08:00
2023-01-08 06:40:02 +08:00
private void updateDisplay ( )
{
Scheduler . AddOnce ( perform ) ;
void perform ( )
{
void removeOldInfo ( )
{
State . Value = beatmap = = null ? Visibility . Hidden : Visibility . Visible ;
DisplayedContent ? . FadeOut ( transition_duration ) ;
DisplayedContent ? . Expire ( ) ;
DisplayedContent = null ;
}
if ( beatmap = = null )
{
removeOldInfo ( ) ;
return ;
}
LoadComponentAsync ( loadingInfo = new Container
{
2023-01-08 08:24:47 +08:00
Masking = true ,
2023-01-11 00:34:47 +08:00
// We offset this by the portion of the colour bar underneath we wish to show
2023-01-17 05:46:18 +08:00
X = - colour_bar_width ,
2023-01-08 22:42:42 +08:00
CornerRadius = corner_radius ,
2023-01-08 06:40:02 +08:00
RelativeSizeAxes = Axes . Both ,
Depth = DisplayedContent ? . Depth + 1 ? ? 0 ,
Children = new Drawable [ ]
{
2023-01-11 00:34:47 +08:00
// TODO: New wedge design uses a coloured horizontal gradient for its background, however this lacks implementation information in the figma draft.
// pending https://www.figma.com/file/DXKwqZhD5yyb1igc3mKo1P?node-id=2980:3361#340801912 being answered.
2023-01-08 08:24:47 +08:00
new BeatmapInfoWedgeBackground ( beatmap ) { Shear = - Shear } ,
2023-01-08 22:42:42 +08:00
Info = new WedgeInfoText ( beatmap ) { Shear = - Shear }
2023-01-08 06:40:02 +08:00
}
} , loaded = >
{
2023-01-11 00:34:47 +08:00
// Ensure we are the most recent loaded wedge.
2023-01-08 06:40:02 +08:00
if ( loaded ! = loadingInfo ) return ;
removeOldInfo ( ) ;
2023-01-24 00:00:46 +08:00
bufferedContent . Add ( DisplayedContent = loaded ) ;
2023-01-18 20:55:52 +08:00
2023-01-24 00:00:46 +08:00
Info . DisplayedStars . BindValueChanged ( s = >
2023-01-18 20:55:52 +08:00
{
starCounter . Current = ( float ) s . NewValue ;
starCounter . Colour = s . NewValue > = 6.5 ? colours . Orange1 : Colour4 . Black . Opacity ( 0.75f ) ;
difficultyColourBar . FadeColour ( colours . ForStarDifficulty ( s . NewValue ) ) ;
} , true ) ;
2023-01-08 06:40:02 +08:00
} ) ;
}
}
public partial class WedgeInfoText : Container
{
2023-01-08 19:56:32 +08:00
public OsuSpriteText TitleLabel { get ; private set ; } = null ! ;
public OsuSpriteText ArtistLabel { get ; private set ; } = null ! ;
2023-01-24 00:00:46 +08:00
private StarRatingDisplay starRatingDisplay = null ! ;
2023-01-08 06:40:02 +08:00
2023-01-08 19:56:32 +08:00
private ILocalisedBindableString titleBinding = null ! ;
private ILocalisedBindableString artistBinding = null ! ;
2023-01-08 06:40:02 +08:00
2023-01-08 22:42:42 +08:00
private readonly WorkingBeatmap working ;
2023-01-08 06:40:02 +08:00
2023-01-24 00:00:46 +08:00
public IBindable < double > DisplayedStars = > starRatingDisplay . DisplayedStars ;
2023-01-08 06:40:02 +08:00
[Resolved]
2023-01-08 22:42:42 +08:00
private IBindable < IReadOnlyList < Mod > > mods { get ; set ; } = null ! ;
2023-01-08 06:40:02 +08:00
[Resolved]
2023-01-08 19:56:32 +08:00
private BeatmapDifficultyCache difficultyCache { get ; set ; } = null ! ;
private ModSettingChangeTracker ? settingChangeTracker ;
2023-01-08 06:40:02 +08:00
2023-01-08 19:56:32 +08:00
private IBindable < StarDifficulty ? > ? starDifficulty ;
private CancellationTokenSource ? cancellationSource ;
2023-01-08 06:40:02 +08:00
2023-01-08 22:42:42 +08:00
public WedgeInfoText ( WorkingBeatmap working )
{
this . working = working ;
}
2023-01-08 06:40:02 +08:00
[BackgroundDependencyLoader]
private void load ( LocalisationManager localisation )
{
2023-01-08 22:42:42 +08:00
var beatmapInfo = working . BeatmapInfo ;
var metadata = working . Metadata ;
2023-01-08 06:40:02 +08:00
RelativeSizeAxes = Axes . Both ;
titleBinding = localisation . GetLocalisedBindableString ( new RomanisableString ( metadata . TitleUnicode , metadata . Title ) ) ;
artistBinding = localisation . GetLocalisedBindableString ( new RomanisableString ( metadata . ArtistUnicode , metadata . Artist ) ) ;
Children = new Drawable [ ]
{
new FillFlowContainer
{
Name = "Topright-aligned metadata" ,
Anchor = Anchor . TopRight ,
Origin = Anchor . TopRight ,
Direction = FillDirection . Vertical ,
2023-01-11 01:03:28 +08:00
Padding = new MarginPadding { Top = 3 , Right = 8 } ,
2023-01-08 06:40:02 +08:00
AutoSizeAxes = Axes . Both ,
Shear = wedged_container_shear ,
Spacing = new Vector2 ( 0f , 5f ) ,
Children = new Drawable [ ]
{
2023-01-24 00:00:46 +08:00
starRatingDisplay = new StarRatingDisplay ( default , animated : true )
2023-01-08 06:40:02 +08:00
{
Anchor = Anchor . TopRight ,
Origin = Anchor . TopRight ,
Shear = - wedged_container_shear ,
Alpha = 0f ,
} ,
new BeatmapSetOnlineStatusPill
{
AutoSizeAxes = Axes . Both ,
Anchor = Anchor . TopRight ,
Origin = Anchor . TopRight ,
Shear = - wedged_container_shear ,
TextSize = 11 ,
TextPadding = new MarginPadding { Horizontal = 8 , Vertical = 2 } ,
Status = beatmapInfo . Status ,
Alpha = string . IsNullOrEmpty ( beatmapInfo . DifficultyName ) ? 0 : 1
}
}
} ,
new FillFlowContainer
{
2023-01-08 08:24:47 +08:00
Name = "Top-left aligned metadata" ,
2023-01-08 06:40:02 +08:00
Direction = FillDirection . Vertical ,
2023-01-17 05:46:18 +08:00
Padding = new MarginPadding { Horizontal = text_margin + shear_width , Top = 12 } ,
2023-01-08 06:40:02 +08:00
AutoSizeAxes = Axes . Y ,
RelativeSizeAxes = Axes . X ,
Children = new Drawable [ ]
{
TitleLabel = new OsuSpriteText
{
2023-01-08 19:03:02 +08:00
Shadow = true ,
2023-01-08 06:40:02 +08:00
Current = { BindTarget = titleBinding } ,
2023-01-08 08:24:47 +08:00
Font = OsuFont . TorusAlternate . With ( size : 40 , weight : FontWeight . SemiBold ) ,
2023-01-08 06:40:02 +08:00
RelativeSizeAxes = Axes . X ,
2023-01-08 08:24:47 +08:00
Truncate = true
2023-01-08 06:40:02 +08:00
} ,
ArtistLabel = new OsuSpriteText
{
2023-01-11 00:34:47 +08:00
// TODO : figma design has a diffused shadow, instead of the solid one present here, not possible currently as far as i'm aware.
2023-01-08 19:03:02 +08:00
Shadow = true ,
2023-01-08 06:40:02 +08:00
Current = { BindTarget = artistBinding } ,
2023-01-11 00:34:47 +08:00
// Not sure if this should be semi bold or medium
2023-01-08 08:24:47 +08:00
Font = OsuFont . Torus . With ( size : 20 , weight : FontWeight . SemiBold ) ,
2023-01-08 06:40:02 +08:00
RelativeSizeAxes = Axes . X ,
2023-01-08 08:24:47 +08:00
Truncate = true
2023-01-08 06:40:02 +08:00
}
}
}
} ;
}
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
2023-01-08 22:42:42 +08:00
starDifficulty = difficultyCache . GetBindableDifficulty ( working . BeatmapInfo , ( cancellationSource = new CancellationTokenSource ( ) ) . Token ) ;
2023-01-08 19:56:32 +08:00
starDifficulty . BindValueChanged ( s = >
2023-01-08 06:40:02 +08:00
{
2023-01-24 00:00:46 +08:00
starRatingDisplay . Current . Value = s . NewValue ? ? default ;
2023-01-08 06:40:02 +08:00
// Don't roll the counter on initial display (but still allow it to roll on applying mods etc.)
2023-01-24 00:00:46 +08:00
if ( ! starRatingDisplay . IsPresent )
starRatingDisplay . FinishTransforms ( true ) ;
2023-01-08 06:40:02 +08:00
2023-01-24 00:00:46 +08:00
starRatingDisplay . FadeIn ( transition_duration ) ;
2023-01-08 06:40:02 +08:00
} ) ;
mods . BindValueChanged ( m = >
{
settingChangeTracker ? . Dispose ( ) ;
settingChangeTracker = new ModSettingChangeTracker ( m . NewValue ) ;
} , true ) ;
}
protected override void Dispose ( bool isDisposing )
{
base . Dispose ( isDisposing ) ;
2023-01-08 19:56:32 +08:00
cancellationSource ? . Cancel ( ) ;
2023-01-08 06:40:02 +08:00
settingChangeTracker ? . Dispose ( ) ;
}
}
}
}