// 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. using System; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Input.Events; using osu.Framework.Threading; namespace osu.Game.Screens.Edit.Timing { /// <summary> /// Represents a component that provides the behaviour of triggering button clicks repeatedly while holding with mouse. /// </summary> public partial class RepeatingButtonBehaviour : Component { private const double initial_delay = 300; private const double minimum_delay = 80; private readonly Drawable button; private Sample? sample; public Action? RepeatBegan; public Action? RepeatEnded; /// <summary> /// An additive modifier for the frequency of the sample played on next actuation. /// This can be adjusted during the button's <see cref="Drawable.OnClick"/> event to affect the repeat sample playback of that click. /// </summary> public double SampleFrequencyModifier { get; set; } public RepeatingButtonBehaviour(Drawable button) { this.button = button; RelativeSizeAxes = Axes.Both; } [BackgroundDependencyLoader] private void load(AudioManager audio) { sample = audio.Samples.Get(@"UI/notch-tick"); } protected override bool OnMouseDown(MouseDownEvent e) { RepeatBegan?.Invoke(); beginRepeat(); return true; } protected override void OnMouseUp(MouseUpEvent e) { adjustDelegate?.Cancel(); RepeatEnded?.Invoke(); base.OnMouseUp(e); } private ScheduledDelegate? adjustDelegate; private double adjustDelay = initial_delay; private void beginRepeat() { adjustDelegate?.Cancel(); adjustDelay = initial_delay; adjustNext(); void adjustNext() { if (IsHovered) { button.TriggerClick(); adjustDelay = Math.Max(minimum_delay, adjustDelay * 0.9f); var channel = sample?.GetChannel(); if (channel != null) { double repeatModifier = 0.05f * (Math.Abs(adjustDelay - initial_delay) / minimum_delay); channel.Frequency.Value = 1 + repeatModifier + SampleFrequencyModifier; channel.Play(); } } else { adjustDelay = initial_delay; } adjustDelegate = Scheduler.AddDelayed(adjustNext, adjustDelay); } } } }