// Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Timing; using osu.Game.Graphics; using OpenTK.Graphics; using OpenTK.Input; namespace osu.Game.Screens.Play { /// /// 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 /// public class PauseContainer : Container { public bool IsPaused { get; private set; } public bool AllowExit => IsPaused && pauseOverlay.Alpha == 1; public Func CheckCanPause; private const double pause_cooldown = 1000; private double lastPauseActionTime; private readonly PauseOverlay pauseOverlay; private readonly Container content; protected override Container Content => content; public int Retries { set { pauseOverlay.Retries = value; } } public bool CanPause => (CheckCanPause?.Invoke() ?? true) && Time.Current >= lastPauseActionTime + pause_cooldown; public Action OnRetry; public Action OnQuit; public Action OnResume; public Action OnPause; public IAdjustableClock AudioClock; public FramedClock FramedClock; public PauseContainer() { RelativeSizeAxes = Axes.Both; AddInternal(content = new Container { RelativeSizeAxes = Axes.Both }); AddInternal(pauseOverlay = new PauseOverlay { OnResume = delegate { Delay(400); Schedule(Resume); }, OnRetry = () => OnRetry(), OnQuit = () => OnQuit(), }); } public void Pause(bool force = false) { if (!CanPause && !force) return; if (IsPaused) return; // stop the decoupled clock (stops the audio eventually) AudioClock.Stop(); // stop processing updatess on the offset clock (instantly freezes time for all our components) FramedClock.ProcessSourceClockFrames = false; IsPaused = true; // we need to do a final check after all of our children have processed up to the paused clock time. // this is to cover cases where, for instance, the player fails in the current processing frame. Schedule(() => { if (!CanPause) return; lastPauseActionTime = Time.Current; OnPause?.Invoke(); pauseOverlay.Show(); }); } public void Resume() { if (!IsPaused) return; IsPaused = false; FramedClock.ProcessSourceClockFrames = true; lastPauseActionTime = Time.Current; OnResume?.Invoke(); pauseOverlay.Hide(); AudioClock.Start(); } 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). if (!game.IsActive && CanPause) Pause(); base.Update(); } public class PauseOverlay : MenuOverlay { 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?"; protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) { if (!args.Repeat && args.Key == Key.Escape) { Buttons.Children.First().TriggerOnClick(); return true; } return base.OnKeyDown(state, args); } [BackgroundDependencyLoader] private void load(OsuColour colours) { AddButton("Continue", colours.Green, OnResume); AddButton("Retry", colours.YellowDark, OnRetry); AddButton("Quit", new Color4(170, 27, 39, 255), OnQuit); } } } }