// Copyright (c) ppy Pty Ltd . 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 { /// /// A container which adds a common "hold-to-perform" pattern to a container. /// /// /// This container does not handle triggering the hold/abort operations. /// To use this class, please call and when necessary. /// /// The 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. /// public abstract partial class HoldToConfirmContainer : Container { public const double DANGEROUS_HOLD_ACTIVATION_DELAY = 500; private const int fadeout_delay = 200; /// /// Whether the associated action is considered dangerous, warranting a longer hold. /// public bool IsDangerousAction { get; } /// /// The action to perform when a hold successfully completes. /// public Action Action; /// /// Whether currently in a fired state (and the confirm has been sent). /// public bool Fired { get; private set; } private bool confirming; /// /// The current activation delay for this control. /// public IBindable HoldActivationDelay => holdActivationDelay; /// /// The progress of any ongoing hold operation. 0 means no hold has started; 1 means a hold has been completed. /// public IBindable Progress => progress; /// /// Whether the overlay should be allowed to return from a fired state. /// protected virtual bool AllowMultipleFires => false; private readonly Bindable progress = new BindableDouble(); private readonly Bindable holdActivationDelay = new Bindable(); [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); } /// /// Begin a new confirmation. Should be called when the container is interacted with (ie. the user presses a key). /// /// /// Calling this method when already in the process of confirming has no effect. /// protected void BeginConfirm() { if (confirming || (!AllowMultipleFires && Fired)) return; confirming = true; this.TransformBindableTo(progress, 1, holdActivationDelay.Value * (1 - progress.Value), Easing.Out).OnComplete(_ => Confirm()); } /// /// Abort any ongoing confirmation. Should be called when the container's interaction is no longer valid (ie. the user releases a key). /// 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); } /// /// A method which is invoked when the confirmation sequence completes successfully. /// By default, will fire the associated . /// protected virtual void Confirm() { Action?.Invoke(); Fired = true; } } }