/// <summary>
        /// Exits the current <see cref="IScreen"/>.
        /// </summary>
        /// <param name="source">The <see cref="IScreen"/> which last exited.</param>
        /// <param name="onExiting">An action that is invoked when the current screen allows the exit to continue.</param>
        private void exitFrom([CanBeNull] IScreen source, Action onExiting = null)
        {
            // The current screen is at the top of the stack, it will be the one that is exited
            var toExit = stack.Pop();

            // The next current screen will be resumed
            if (toExit.OnExiting(CurrentScreen))
            {
                stack.Push(toExit);
                return;
            }

            // we will probably want to change this logic when we support returning to a screen after exiting.
            toExit.ValidForResume = false;
            toExit.ValidForPush   = false;

            onExiting?.Invoke();

            if (source == null)
            {
                // This is the first screen that exited
                toExit.AsDrawable().Expire();
            }
            else
            {
                // This screen exited via a recursive-exit chain. Lifetime is propagated from the parent.
                toExit.AsDrawable().LifetimeEnd = ((Drawable)source).LifetimeEnd;
            }

            ScreenExited?.Invoke(toExit, CurrentScreen);

            // Resume the next current screen from the exited one
            resumeFrom(toExit);
        }
        private bool exitFrom([CanBeNull] IScreen source, bool shouldFireExitEvent = true, bool shouldFireResumeEvent = true, IScreen destination = null)
        {
            if (stack.Count == 0)
            {
                return(false);
            }

            // The current screen is at the top of the stack, it will be the one that is exited
            var toExit = stack.Pop();

            // The next current screen will be resumed
            if (shouldFireExitEvent && toExit.AsDrawable().IsLoaded)
            {
                var next = CurrentScreen;

                // Add the screen back on the stack to allow pushing screens in OnExiting.
                stack.Push(toExit);

                // if a screen is !ValidForResume, it should not be allowed to block unless it is the current screen (source == null)
                // OnExiting should still be called regardless.
                bool blockRequested = toExit.OnExiting(new ScreenExitEvent(toExit, next, destination ?? next));

                if ((source == null || toExit.ValidForResume) && blockRequested)
                {
                    return(true);
                }

                stack.Pop();
            }

            // we will probably want to change this logic when we support returning to a screen after exiting.
            toExit.ValidForResume = false;
            toExit.ValidForPush   = false;

            if (source == null)
            {
                // This is the first screen that exited
                toExit.AsDrawable().Expire();
            }

            exited.Add(toExit.AsDrawable());

            log($"exit from {getTypeString(toExit)}");
            log($"resume to {getTypeString(CurrentScreen)}");

            ScreenExited?.Invoke(toExit, CurrentScreen);

            // Resume the next current screen from the exited one
            if (shouldFireResumeEvent)
            {
                resumeFrom(toExit);
            }

            return(false);
        }
        /// <summary>
        /// Exits the current <see cref="IScreen"/>.
        /// </summary>
        /// <param name="source">The <see cref="IScreen"/> which last exited.</param>
        /// <param name="onExiting">An action that is invoked when the current screen allows the exit to continue.</param>
        /// <param name="shouldFireExitEvent">Whether <see cref="IScreen.OnExiting"/> should be fired on the exiting screen.</param>
        /// <param name="shouldFireResumeEvent">Whether <see cref="IScreen.OnResuming"/> should be fired on the resuming screen.</param>
        private void exitFrom([CanBeNull] IScreen source, Action onExiting = null, bool shouldFireExitEvent = true, bool shouldFireResumeEvent = true)
        {
            if (stack.Count == 0)
            {
                return;
            }

            // The current screen is at the top of the stack, it will be the one that is exited
            var toExit = stack.Pop();

            // The next current screen will be resumed
            if (shouldFireExitEvent && toExit.AsDrawable().IsLoaded&& toExit.OnExiting(CurrentScreen))
            {
                // If the exit event gets cancelled, add the screen back on the stack.
                stack.Push(toExit);
                return;
            }

            // we will probably want to change this logic when we support returning to a screen after exiting.
            toExit.ValidForResume = false;
            toExit.ValidForPush   = false;

            onExiting?.Invoke();

            if (source == null)
            {
                // This is the first screen that exited
                toExit.AsDrawable().Expire();
            }

            exited.Add(toExit.AsDrawable());

            ScreenExited?.Invoke(toExit, CurrentScreen);

            // Resume the next current screen from the exited one
            if (shouldFireResumeEvent)
            {
                resumeFrom(toExit);
            }
        }