Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ppy
GitHub Repository: ppy/osu
Path: blob/master/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs
4430 views
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

#nullable disable

using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Configuration;

namespace osu.Game.Graphics.Containers
{
    /// <summary>
    /// A container which adds a common "hold-to-perform" pattern to a container.
    /// </summary>
    /// <remarks>
    /// This container does not handle triggering the hold/abort operations.
    /// To use this class, please call <see cref="BeginConfirm"/> and <see cref="AbortConfirm"/> when necessary.
    ///
    /// The <see cref="Progress"/> is exposed as a transforming bindable which smoothly tracks the progress of a hold operation.
    /// It can be used for animating and displaying progress directly.
    /// </remarks>
    public abstract partial class HoldToConfirmContainer : Container
    {
        public const double DANGEROUS_HOLD_ACTIVATION_DELAY = 500;

        private const int fadeout_delay = 200;

        /// <summary>
        /// Whether the associated action is considered dangerous, warranting a longer hold.
        /// </summary>
        public bool IsDangerousAction { get; }

        /// <summary>
        /// The action to perform when a hold successfully completes.
        /// </summary>
        public Action Action;

        /// <summary>
        /// Whether currently in a fired state (and the confirm <see cref="Action"/> has been sent).
        /// </summary>
        public bool Fired { get; private set; }

        private bool confirming;

        /// <summary>
        /// The current activation delay for this control.
        /// </summary>
        public IBindable<double> HoldActivationDelay => holdActivationDelay;

        /// <summary>
        /// The progress of any ongoing hold operation. 0 means no hold has started; 1 means a hold has been completed.
        /// </summary>
        public IBindable<double> Progress => progress;

        /// <summary>
        /// Whether the overlay should be allowed to return from a fired state.
        /// </summary>
        protected virtual bool AllowMultipleFires => false;

        private readonly Bindable<double> progress = new BindableDouble();

        private readonly Bindable<double> holdActivationDelay = new Bindable<double>();

        [Resolved]
        private OsuConfigManager config { get; set; }

        protected HoldToConfirmContainer(bool isDangerousAction = false)
        {
            IsDangerousAction = isDangerousAction;
        }

        protected override void LoadComplete()
        {
            base.LoadComplete();

            if (IsDangerousAction)
                holdActivationDelay.Value = DANGEROUS_HOLD_ACTIVATION_DELAY;
            else
                config.BindWith(OsuSetting.UIHoldActivationDelay, holdActivationDelay);
        }

        /// <summary>
        /// Begin a new confirmation. Should be called when the container is interacted with (ie. the user presses a key).
        /// </summary>
        /// <remarks>
        /// Calling this method when already in the process of confirming has no effect.
        /// </remarks>
        protected void BeginConfirm()
        {
            if (confirming || (!AllowMultipleFires && Fired)) return;

            confirming = true;

            this.TransformBindableTo(progress, 1, holdActivationDelay.Value * (1 - progress.Value), Easing.Out).OnComplete(_ => Confirm());
        }

        /// <summary>
        /// Abort any ongoing confirmation. Should be called when the container's interaction is no longer valid (ie. the user releases a key).
        /// </summary>
        protected virtual void AbortConfirm()
        {
            if (!confirming || (!AllowMultipleFires && Fired)) return;

            confirming = false;
            Fired = false;

            this
                .TransformBindableTo(progress, progress.Value)
                .Delay(200)
                .TransformBindableTo(progress, 0, fadeout_delay, Easing.InSine);
        }

        /// <summary>
        /// A method which is invoked when the confirmation sequence completes successfully.
        /// By default, will fire the associated <see cref="Action"/>.
        /// </summary>
        protected virtual void Confirm()
        {
            Action?.Invoke();
            Fired = true;
        }
    }
}