2019-01-24 16:43:03 +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.
2018-04-13 17:19:50 +08:00
2019-03-06 19:30:14 +08:00
using System ;
2019-04-08 17:32:05 +08:00
using System.Collections.Generic ;
2018-04-13 17:19:50 +08:00
using osu.Framework.Allocation ;
2019-02-21 18:04:31 +08:00
using osu.Framework.Bindables ;
2019-12-12 14:05:29 +08:00
using osu.Framework.Extensions.IEnumerableExtensions ;
2018-04-13 17:19:50 +08:00
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
2020-10-30 13:19:40 +08:00
using osu.Framework.Input.Bindings ;
2018-10-02 11:02:47 +08:00
using osu.Framework.Input.Events ;
2018-04-13 17:19:50 +08:00
using osu.Game.Configuration ;
2020-10-30 13:19:40 +08:00
using osu.Game.Input.Bindings ;
2018-04-13 17:19:50 +08:00
using osu.Game.Overlays ;
using osu.Game.Overlays.Notifications ;
2019-04-08 17:32:05 +08:00
using osu.Game.Rulesets.Mods ;
2018-04-13 17:19:50 +08:00
using osu.Game.Rulesets.Scoring ;
using osu.Game.Rulesets.UI ;
using osu.Game.Screens.Play.HUD ;
2018-11-20 15:51:59 +08:00
using osuTK ;
using osuTK.Input ;
2018-04-13 17:19:50 +08:00
namespace osu.Game.Screens.Play
{
2020-10-14 16:21:56 +08:00
[Cached]
2020-10-30 13:19:40 +08:00
public class HUDOverlay : Container , IKeyBindingHandler < GlobalAction >
2018-04-13 17:19:50 +08:00
{
2020-10-15 15:56:05 +08:00
public const float FADE_DURATION = 400 ;
public const Easing FADE_EASING = Easing . Out ;
2018-04-13 17:19:50 +08:00
2019-03-26 10:28:43 +08:00
public readonly KeyCounterDisplay KeyCounter ;
2020-10-14 15:46:13 +08:00
public readonly SkinnableComboCounter ComboCounter ;
2020-10-15 15:32:20 +08:00
public readonly SkinnableScoreCounter ScoreCounter ;
2020-10-15 16:48:50 +08:00
public readonly SkinnableAccuracyCounter AccuracyCounter ;
2020-10-16 12:42:50 +08:00
public readonly SkinnableHealthDisplay HealthDisplay ;
2018-04-13 17:19:50 +08:00
public readonly SongProgress Progress ;
public readonly ModDisplay ModDisplay ;
2019-08-30 17:46:42 +08:00
public readonly HitErrorDisplay HitErrorDisplay ;
2018-11-07 00:47:02 +08:00
public readonly HoldForMenuButton HoldToQuit ;
2018-04-13 17:19:50 +08:00
public readonly PlayerSettingsOverlay PlayerSettingsOverlay ;
2020-04-09 13:31:25 +08:00
public readonly FailingLayer FailingLayer ;
2018-04-13 17:19:50 +08:00
2019-07-07 04:30:53 +08:00
public Bindable < bool > ShowHealthbar = new Bindable < bool > ( true ) ;
2019-05-09 17:06:11 +08:00
private readonly ScoreProcessor scoreProcessor ;
2019-12-19 19:03:14 +08:00
private readonly HealthProcessor healthProcessor ;
2019-05-09 17:06:11 +08:00
private readonly DrawableRuleset drawableRuleset ;
private readonly IReadOnlyList < Mod > mods ;
2019-12-12 14:05:29 +08:00
/// <summary>
/// Whether the elements that can optionally be hidden should be visible.
/// </summary>
public Bindable < bool > ShowHud { get ; } = new BindableBool ( ) ;
2020-07-22 11:41:06 +08:00
private Bindable < HUDVisibilityMode > configVisibilityMode ;
2019-12-12 14:05:29 +08:00
2019-01-17 15:00:11 +08:00
private readonly Container visibilityContainer ;
2019-12-12 14:05:29 +08:00
2018-04-13 17:19:50 +08:00
private readonly BindableBool replayLoaded = new BindableBool ( ) ;
private static bool hasShownNotificationOnce ;
2019-03-06 19:30:14 +08:00
public Action < double > RequestSeek ;
2020-10-14 18:16:25 +08:00
private readonly FillFlowContainer bottomRightElements ;
2020-10-15 16:11:02 +08:00
private readonly FillFlowContainer topRightElements ;
2019-07-07 04:30:53 +08:00
2020-07-22 11:41:06 +08:00
internal readonly IBindable < bool > IsBreakTime = new Bindable < bool > ( ) ;
2020-10-30 13:19:40 +08:00
private bool holdingForHUD ;
2019-12-12 14:05:29 +08:00
private IEnumerable < Drawable > hideTargets = > new Drawable [ ] { visibilityContainer , KeyCounter } ;
2019-12-19 19:03:14 +08:00
public HUDOverlay ( ScoreProcessor scoreProcessor , HealthProcessor healthProcessor , DrawableRuleset drawableRuleset , IReadOnlyList < Mod > mods )
2018-04-13 17:19:50 +08:00
{
2019-05-09 17:06:11 +08:00
this . scoreProcessor = scoreProcessor ;
2019-12-19 19:03:14 +08:00
this . healthProcessor = healthProcessor ;
2019-05-09 17:06:11 +08:00
this . drawableRuleset = drawableRuleset ;
this . mods = mods ;
2018-04-13 17:19:50 +08:00
RelativeSizeAxes = Axes . Both ;
2019-01-17 15:00:11 +08:00
Children = new Drawable [ ]
2018-04-13 17:19:50 +08:00
{
2020-04-09 13:31:25 +08:00
FailingLayer = CreateFailingLayer ( ) ,
2019-01-23 13:51:13 +08:00
visibilityContainer = new Container
{
2019-01-17 15:00:11 +08:00
RelativeSizeAxes = Axes . Both ,
2020-10-14 17:15:29 +08:00
Child = new GridContainer
2019-01-23 13:51:13 +08:00
{
2020-10-14 17:15:29 +08:00
RelativeSizeAxes = Axes . Both ,
Content = new [ ]
2019-01-23 13:51:13 +08:00
{
2020-10-14 17:15:29 +08:00
new Drawable [ ]
2019-01-23 13:51:13 +08:00
{
2020-10-20 03:32:16 +08:00
new Container
2020-10-14 17:15:29 +08:00
{
RelativeSizeAxes = Axes . Both ,
Children = new Drawable [ ]
{
HealthDisplay = CreateHealthDisplay ( ) ,
2020-10-15 15:56:05 +08:00
AccuracyCounter = CreateAccuracyCounter ( ) ,
ScoreCounter = CreateScoreCounter ( ) ,
2020-10-14 17:15:29 +08:00
ComboCounter = CreateComboCounter ( ) ,
HitErrorDisplay = CreateHitErrorDisplayOverlay ( ) ,
}
} ,
2019-01-21 16:03:06 +08:00
} ,
2020-10-14 17:15:29 +08:00
new Drawable [ ]
{
Progress = CreateProgress ( ) ,
}
2019-01-21 16:03:06 +08:00
} ,
2020-10-14 17:15:29 +08:00
RowDimensions = new [ ]
{
2020-10-14 17:51:53 +08:00
new Dimension ( ) ,
2020-10-14 17:15:29 +08:00
new Dimension ( GridSizeMode . AutoSize )
}
} ,
2019-01-17 15:00:11 +08:00
} ,
2020-10-15 16:11:02 +08:00
topRightElements = new FillFlowContainer
{
Anchor = Anchor . TopRight ,
Origin = Anchor . TopRight ,
Margin = new MarginPadding ( 10 ) ,
Spacing = new Vector2 ( 10 ) ,
AutoSizeAxes = Axes . Both ,
Direction = FillDirection . Vertical ,
Children = new Drawable [ ]
{
2019-01-17 15:00:11 +08:00
ModDisplay = CreateModsContainer ( ) ,
2019-12-12 13:19:04 +08:00
PlayerSettingsOverlay = CreatePlayerSettingsOverlay ( ) ,
2019-01-17 15:00:11 +08:00
}
} ,
2020-10-14 17:51:53 +08:00
bottomRightElements = new FillFlowContainer
2018-04-13 17:19:50 +08:00
{
2019-01-17 15:00:11 +08:00
Anchor = Anchor . BottomRight ,
Origin = Anchor . BottomRight ,
2020-10-15 16:11:02 +08:00
Margin = new MarginPadding ( 10 ) ,
Spacing = new Vector2 ( 10 ) ,
2019-01-17 15:00:11 +08:00
AutoSizeAxes = Axes . Both ,
2020-10-15 15:56:05 +08:00
LayoutDuration = FADE_DURATION / 2 ,
LayoutEasing = FADE_EASING ,
2019-01-17 15:00:11 +08:00
Direction = FillDirection . Vertical ,
Children = new Drawable [ ]
2018-05-22 15:45:42 +08:00
{
2019-03-05 12:26:54 +08:00
KeyCounter = CreateKeyCounter ( ) ,
2019-01-17 15:00:11 +08:00
HoldToQuit = CreateHoldForMenuButton ( ) ,
2018-05-22 15:45:42 +08:00
}
2018-04-13 17:19:50 +08:00
}
2019-01-17 15:00:11 +08:00
} ;
2019-05-09 17:06:11 +08:00
}
2018-04-13 17:19:50 +08:00
2019-05-09 17:06:11 +08:00
[BackgroundDependencyLoader(true)]
private void load ( OsuConfigManager config , NotificationOverlay notificationOverlay )
{
2019-12-12 15:09:42 +08:00
if ( scoreProcessor ! = null )
2019-12-19 19:03:14 +08:00
BindScoreProcessor ( scoreProcessor ) ;
if ( healthProcessor ! = null )
BindHealthProcessor ( healthProcessor ) ;
2018-04-13 17:19:50 +08:00
2019-12-12 15:09:42 +08:00
if ( drawableRuleset ! = null )
{
BindDrawableRuleset ( drawableRuleset ) ;
Progress . Objects = drawableRuleset . Objects ;
Progress . RequestSeek = time = > RequestSeek ( time ) ;
Progress . ReferenceClock = drawableRuleset . FrameStableClock ;
}
2018-04-13 17:19:50 +08:00
2019-04-08 17:32:05 +08:00
ModDisplay . Current . Value = mods ;
2018-04-13 17:19:50 +08:00
2020-07-22 11:41:06 +08:00
configVisibilityMode = config . GetBindable < HUDVisibilityMode > ( OsuSetting . HUDVisibilityMode ) ;
2019-12-12 14:05:29 +08:00
2020-07-22 11:41:06 +08:00
if ( configVisibilityMode . Value = = HUDVisibilityMode . Never & & ! hasShownNotificationOnce )
2019-12-12 14:05:29 +08:00
{
hasShownNotificationOnce = true ;
notificationOverlay ? . Post ( new SimpleNotification
{
Text = @"The score overlay is currently disabled. You can toggle this by pressing Shift+Tab."
} ) ;
}
// start all elements hidden
hideTargets . ForEach ( d = > d . Hide ( ) ) ;
}
public override void Hide ( ) = > throw new InvalidOperationException ( $"{nameof(HUDOverlay)} should not be hidden as it will remove the ability of a user to quit. Use {nameof(ShowHud)} instead." ) ;
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
2020-10-15 15:56:05 +08:00
ShowHealthbar . BindValueChanged ( healthBar = > HealthDisplay . FadeTo ( healthBar . NewValue ? 1 : 0 , FADE_DURATION , FADE_EASING ) , true ) ;
ShowHud . BindValueChanged ( visible = > hideTargets . ForEach ( d = > d . FadeTo ( visible . NewValue ? 1 : 0 , FADE_DURATION , FADE_EASING ) ) ) ;
2018-04-13 17:19:50 +08:00
2020-07-22 11:41:06 +08:00
IsBreakTime . BindValueChanged ( _ = > updateVisibility ( ) ) ;
configVisibilityMode . BindValueChanged ( _ = > updateVisibility ( ) , true ) ;
2018-04-13 17:19:50 +08:00
2019-05-10 14:39:25 +08:00
replayLoaded . BindValueChanged ( replayLoadedValueChanged , true ) ;
2018-04-13 17:19:50 +08:00
}
2020-10-14 17:51:53 +08:00
protected override void Update ( )
{
base . Update ( ) ;
2020-10-15 16:11:02 +08:00
2020-10-20 11:43:57 +08:00
// HACK: for now align with the accuracy counter.
// this is done for the sake of hacky legacy skins which extend the health bar to take up the full screen area.
// it only works with the default skin due to padding offsetting it *just enough* to coexist.
2020-10-20 03:04:23 +08:00
topRightElements . Y = ToLocalSpace ( AccuracyCounter . Drawable . ScreenSpaceDrawQuad . BottomRight ) . Y ;
2020-10-15 17:30:44 +08:00
2020-10-14 17:51:53 +08:00
bottomRightElements . Y = - Progress . Height ;
}
2020-07-22 11:41:06 +08:00
private void updateVisibility ( )
{
if ( ShowHud . Disabled )
return ;
2020-10-30 13:19:40 +08:00
if ( holdingForHUD )
{
ShowHud . Value = true ;
return ;
}
2020-07-22 11:41:06 +08:00
switch ( configVisibilityMode . Value )
{
case HUDVisibilityMode . Never :
ShowHud . Value = false ;
break ;
2020-10-20 13:19:04 +08:00
case HUDVisibilityMode . HideDuringGameplay :
// always show during replay as we want the seek bar to be visible.
ShowHud . Value = replayLoaded . Value | | IsBreakTime . Value ;
break ;
2020-07-22 11:41:06 +08:00
case HUDVisibilityMode . Always :
ShowHud . Value = true ;
break ;
}
}
2019-02-21 17:56:34 +08:00
private void replayLoadedValueChanged ( ValueChangedEvent < bool > e )
2018-04-13 17:19:50 +08:00
{
2019-02-21 17:56:34 +08:00
PlayerSettingsOverlay . ReplayLoaded = e . NewValue ;
2018-04-13 17:19:50 +08:00
2019-02-21 17:56:34 +08:00
if ( e . NewValue )
2018-04-13 17:19:50 +08:00
{
PlayerSettingsOverlay . Show ( ) ;
ModDisplay . FadeIn ( 200 ) ;
2018-10-30 20:32:12 +08:00
KeyCounter . Margin = new MarginPadding ( 10 ) { Bottom = 30 } ;
2018-04-13 17:19:50 +08:00
}
else
{
PlayerSettingsOverlay . Hide ( ) ;
ModDisplay . Delay ( 2000 ) . FadeOut ( 200 ) ;
2018-10-30 20:32:12 +08:00
KeyCounter . Margin = new MarginPadding ( 10 ) ;
2018-04-13 17:19:50 +08:00
}
2020-07-22 11:41:06 +08:00
updateVisibility ( ) ;
2018-04-13 17:19:50 +08:00
}
2019-03-19 22:44:15 +08:00
protected virtual void BindDrawableRuleset ( DrawableRuleset drawableRuleset )
2018-04-13 17:19:50 +08:00
{
2019-03-19 22:44:15 +08:00
( drawableRuleset as ICanAttachKeyCounter ) ? . Attach ( KeyCounter ) ;
2018-04-13 17:19:50 +08:00
2019-03-19 22:44:15 +08:00
replayLoaded . BindTo ( drawableRuleset . HasReplayLoaded ) ;
2018-04-13 17:19:50 +08:00
2019-03-20 13:49:33 +08:00
Progress . BindDrawableRuleset ( drawableRuleset ) ;
2018-04-13 17:19:50 +08:00
}
2018-10-02 11:02:47 +08:00
protected override bool OnKeyDown ( KeyDownEvent e )
2018-04-13 17:19:50 +08:00
{
2018-10-02 11:02:47 +08:00
if ( e . Repeat ) return false ;
2018-04-13 17:19:50 +08:00
2018-10-02 11:44:14 +08:00
if ( e . ShiftPressed )
2018-04-13 17:19:50 +08:00
{
2018-10-02 11:02:47 +08:00
switch ( e . Key )
2018-04-13 17:19:50 +08:00
{
case Key . Tab :
2020-10-30 12:09:22 +08:00
switch ( configVisibilityMode . Value )
{
case HUDVisibilityMode . Never :
configVisibilityMode . Value = HUDVisibilityMode . HideDuringGameplay ;
break ;
case HUDVisibilityMode . HideDuringGameplay :
configVisibilityMode . Value = HUDVisibilityMode . Always ;
break ;
case HUDVisibilityMode . Always :
configVisibilityMode . Value = HUDVisibilityMode . Never ;
break ;
}
2018-04-13 17:19:50 +08:00
return true ;
}
}
2018-10-02 11:02:47 +08:00
return base . OnKeyDown ( e ) ;
2018-04-13 17:19:50 +08:00
}
2020-10-15 16:48:50 +08:00
protected virtual SkinnableAccuracyCounter CreateAccuracyCounter ( ) = > new SkinnableAccuracyCounter ( ) ;
2018-04-13 17:19:50 +08:00
2020-10-15 15:32:20 +08:00
protected virtual SkinnableScoreCounter CreateScoreCounter ( ) = > new SkinnableScoreCounter ( ) ;
2019-01-21 16:03:06 +08:00
2020-10-14 15:46:13 +08:00
protected virtual SkinnableComboCounter CreateComboCounter ( ) = > new SkinnableComboCounter ( ) ;
2018-04-13 17:19:50 +08:00
2020-10-16 12:42:50 +08:00
protected virtual SkinnableHealthDisplay CreateHealthDisplay ( ) = > new SkinnableHealthDisplay ( ) ;
2018-04-13 17:19:50 +08:00
2020-06-27 01:22:30 +08:00
protected virtual FailingLayer CreateFailingLayer ( ) = > new FailingLayer
{
ShowHealth = { BindTarget = ShowHealthbar }
} ;
2020-04-09 13:31:25 +08:00
2019-03-26 10:28:43 +08:00
protected virtual KeyCounterDisplay CreateKeyCounter ( ) = > new KeyCounterDisplay
2018-04-13 17:19:50 +08:00
{
Anchor = Anchor . BottomRight ,
Origin = Anchor . BottomRight ,
} ;
protected virtual SongProgress CreateProgress ( ) = > new SongProgress
{
Anchor = Anchor . BottomLeft ,
Origin = Anchor . BottomLeft ,
RelativeSizeAxes = Axes . X ,
} ;
2018-11-07 00:47:02 +08:00
protected virtual HoldForMenuButton CreateHoldForMenuButton ( ) = > new HoldForMenuButton
2018-04-21 23:24:31 +08:00
{
Anchor = Anchor . BottomRight ,
Origin = Anchor . BottomRight ,
} ;
2018-04-13 17:19:50 +08:00
protected virtual ModDisplay CreateModsContainer ( ) = > new ModDisplay
{
Anchor = Anchor . TopRight ,
Origin = Anchor . TopRight ,
AutoSizeAxes = Axes . Both ,
} ;
2019-12-12 15:09:42 +08:00
protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay ( ) = > new HitErrorDisplay ( scoreProcessor , drawableRuleset ? . FirstAvailableHitWindows ) ;
2019-08-20 13:45:51 +08:00
2018-04-13 17:19:50 +08:00
protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay ( ) = > new PlayerSettingsOverlay ( ) ;
2019-12-19 19:03:14 +08:00
protected virtual void BindScoreProcessor ( ScoreProcessor processor )
2018-04-13 17:19:50 +08:00
{
ScoreCounter ? . Current . BindTo ( processor . TotalScore ) ;
AccuracyCounter ? . Current . BindTo ( processor . Accuracy ) ;
ComboCounter ? . Current . BindTo ( processor . Combo ) ;
2020-10-16 13:49:05 +08:00
if ( HealthDisplay is IHealthDisplay shd )
{
processor . NewJudgement + = judgement = >
{
2020-10-16 16:24:43 +08:00
if ( judgement . IsHit & & judgement . Type ! = HitResult . IgnoreHit )
2020-10-16 13:49:05 +08:00
shd . Flash ( judgement ) ;
} ;
}
2018-04-13 17:19:50 +08:00
}
2019-12-19 19:03:14 +08:00
protected virtual void BindHealthProcessor ( HealthProcessor processor )
{
2020-04-09 13:31:25 +08:00
HealthDisplay ? . BindHealthProcessor ( processor ) ;
FailingLayer ? . BindHealthProcessor ( processor ) ;
2019-12-19 19:03:14 +08:00
}
2020-10-30 13:19:40 +08:00
public bool OnPressed ( GlobalAction action )
{
switch ( action )
{
case GlobalAction . HoldForHUD :
holdingForHUD = true ;
updateVisibility ( ) ;
return true ;
}
return false ;
}
public void OnReleased ( GlobalAction action )
{
switch ( action )
{
case GlobalAction . HoldForHUD :
holdingForHUD = false ;
updateVisibility ( ) ;
break ;
}
}
2018-04-13 17:19:50 +08:00
}
}