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
using System ;
2018-06-27 11:57:26 +08:00
using System.Linq ;
2018-04-13 17:19:50 +08:00
using osu.Framework.Allocation ;
2019-02-21 18:04:31 +08:00
using osu.Framework.Bindables ;
2018-04-13 17:19:50 +08:00
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
using osu.Framework.Timing ;
using osu.Game.Graphics ;
2018-11-20 15:51:59 +08:00
using osuTK.Graphics ;
2018-04-13 17:19:50 +08:00
namespace osu.Game.Screens.Play
{
/// <summary>
/// A container which handles pausing children, displaying a pause overlay with choices etc.
/// This alleviates a lot of the intricate pause logic from being in <see cref="Player"/>
/// </summary>
public class PauseContainer : Container
{
2018-07-11 16:01:27 +08:00
public readonly BindableBool IsPaused = new BindableBool ( ) ;
2018-04-13 17:19:50 +08:00
public Func < bool > CheckCanPause ;
private const double pause_cooldown = 1000 ;
private double lastPauseActionTime ;
private readonly PauseOverlay pauseOverlay ;
private readonly Container content ;
protected override Container < Drawable > Content = > content ;
2019-02-28 12:31:40 +08:00
public int Retries
{
2019-02-28 13:32:57 +08:00
set = > pauseOverlay . Retries = value ;
2019-02-28 12:31:40 +08:00
}
2018-04-13 17:19:50 +08:00
public bool CanPause = > ( CheckCanPause ? . Invoke ( ) ? ? true ) & & Time . Current > = lastPauseActionTime + pause_cooldown ;
public bool IsResuming { get ; private set ; }
public Action OnRetry ;
public Action OnQuit ;
2019-03-05 12:26:54 +08:00
private readonly FramedClock offsetClock ;
private readonly DecoupleableInterpolatingFramedClock adjustableClock ;
/// <summary>
/// The final clock which is exposed to underlying components.
/// </summary>
[Cached]
private readonly GameplayClock gameplayClock ;
2018-05-28 02:34:58 +08:00
/// <summary>
/// Creates a new <see cref="PauseContainer"/>.
/// </summary>
2019-03-05 12:26:54 +08:00
/// <param name="offsetClock">The gameplay clock. This is the clock that will process frames. Includes user/system offsets.</param>
/// <param name="adjustableClock">The seekable clock. This is the clock that will be paused and resumed. Should not be processed (it is processed automatically by <see cref="offsetClock"/>).</param>
public PauseContainer ( FramedClock offsetClock , DecoupleableInterpolatingFramedClock adjustableClock )
2018-04-13 17:19:50 +08:00
{
2019-03-05 12:26:54 +08:00
this . offsetClock = offsetClock ;
this . adjustableClock = adjustableClock ;
gameplayClock = new GameplayClock ( offsetClock ) ;
2018-04-13 17:19:50 +08:00
RelativeSizeAxes = Axes . Both ;
AddInternal ( content = new Container
{
2019-03-05 12:26:54 +08:00
Clock = this . offsetClock ,
2018-04-13 17:19:50 +08:00
ProcessCustomClock = false ,
RelativeSizeAxes = Axes . Both
} ) ;
AddInternal ( pauseOverlay = new PauseOverlay
{
OnResume = ( ) = >
{
IsResuming = true ;
this . Delay ( 400 ) . Schedule ( Resume ) ;
} ,
OnRetry = ( ) = > OnRetry ( ) ,
OnQuit = ( ) = > OnQuit ( ) ,
} ) ;
}
public void Pause ( bool force = false ) = > Schedule ( ( ) = > // Scheduled to ensure a stable position in execution order, no matter how it was called.
{
if ( ! CanPause & & ! force ) return ;
2019-02-21 17:56:34 +08:00
if ( IsPaused . Value ) return ;
2018-04-13 17:19:50 +08:00
// stop the seekable clock (stops the audio eventually)
2019-03-05 12:26:54 +08:00
adjustableClock . Stop ( ) ;
2018-07-11 16:01:27 +08:00
IsPaused . Value = true ;
2018-04-13 17:19:50 +08:00
pauseOverlay . Show ( ) ;
lastPauseActionTime = Time . Current ;
} ) ;
public void Resume ( )
{
2019-02-21 17:56:34 +08:00
if ( ! IsPaused . Value ) return ;
2018-04-13 17:19:50 +08:00
2018-07-11 16:01:27 +08:00
IsPaused . Value = false ;
2018-04-13 17:19:50 +08:00
IsResuming = false ;
lastPauseActionTime = Time . Current ;
2018-05-28 02:34:58 +08:00
// Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time
// This accounts for the audio clock source potentially taking time to enter a completely stopped state
2019-03-05 12:26:54 +08:00
adjustableClock . Seek ( adjustableClock . CurrentTime ) ;
adjustableClock . Start ( ) ;
2018-04-13 17:19:50 +08:00
pauseOverlay . Hide ( ) ;
}
private OsuGameBase game ;
[BackgroundDependencyLoader]
private void load ( OsuGameBase game )
{
this . game = game ;
}
protected override void Update ( )
{
// eagerly pause when we lose window focus (if we are locally playing).
2019-02-19 18:16:03 +08:00
if ( ! game . IsActive . Value & & CanPause )
2018-04-13 17:19:50 +08:00
Pause ( ) ;
2019-02-21 17:56:34 +08:00
if ( ! IsPaused . Value )
2019-03-05 12:26:54 +08:00
offsetClock . ProcessFrame ( ) ;
2018-04-13 17:19:50 +08:00
base . Update ( ) ;
}
2018-06-09 15:28:02 +08:00
public class PauseOverlay : GameplayMenuOverlay
2018-04-13 17:19:50 +08:00
{
public Action OnResume ;
public override string Header = > "paused" ;
public override string Description = > "you're not going to do what i think you're going to do, are ya?" ;
2018-09-18 17:05:25 +08:00
protected override Action BackAction = > ( ) = > InternalButtons . Children . First ( ) . Click ( ) ;
2018-06-27 11:57:26 +08:00
2018-06-09 15:14:52 +08:00
[BackgroundDependencyLoader]
private void load ( OsuColour colours )
{
AddButton ( "Continue" , colours . Green , ( ) = > OnResume ? . Invoke ( ) ) ;
AddButton ( "Retry" , colours . YellowDark , ( ) = > OnRetry ? . Invoke ( ) ) ;
AddButton ( "Quit" , new Color4 ( 170 , 27 , 39 , 255 ) , ( ) = > OnQuit ? . Invoke ( ) ) ;
}
2018-04-13 17:19:50 +08:00
}
}
2019-03-05 12:26:54 +08:00
/// <summary>
/// A clock which is used for gameplay elements that need to follow audio time 1:1.
/// Exposed via DI by <see cref="PauseContainer"/>.
/// <remarks>
/// THe main purpose of this clock is to stop components using it from accidentally processing the main
/// <see cref="IFrameBasedClock"/>, as this should only be done once to ensure accuracy.
/// </remarks>
/// </summary>
public class GameplayClock : IFrameBasedClock
{
private readonly IFrameBasedClock underlyingClock ;
public GameplayClock ( IFrameBasedClock underlyingClock )
{
this . underlyingClock = underlyingClock ;
}
public double CurrentTime = > underlyingClock . CurrentTime ;
public double Rate = > underlyingClock . Rate ;
public bool IsRunning = > underlyingClock . IsRunning ;
public void ProcessFrame ( )
{
// we do not want to process the underlying clock.
// this is handled by PauseContainer.
}
public double ElapsedFrameTime = > underlyingClock . ElapsedFrameTime ;
public double FramesPerSecond = > underlyingClock . FramesPerSecond ;
public FrameTimeInfo TimeInfo = > underlyingClock . TimeInfo ;
}
2018-04-13 17:19:50 +08:00
}