diff --git a/Basis/Packages/com.basis.framework/Device Management/Devices/Base/BasisActionDriver.cs b/Basis/Packages/com.basis.framework/Device Management/Devices/Base/BasisActionDriver.cs index 084707070..5565210ab 100644 --- a/Basis/Packages/com.basis.framework/Device Management/Devices/Base/BasisActionDriver.cs +++ b/Basis/Packages/com.basis.framework/Device Management/Devices/Base/BasisActionDriver.cs @@ -394,21 +394,76 @@ public static void TickMovementSpeed(ref BasisInputState current, ref BasisInput controller.UpdateMovementSpeed(true); } + /// Continuous hold (seconds) on the secondary button required to toggle the hamburger menu. + public const float HamburgerHoldSeconds = 1f; + + // VR controllers have very few buttons, so the same button often serves more than one purpose. + // A hold gate lets a button's quick tap stay free for another consumer while the hold drives this + // action — here the secondary button's tap is left for the VR fly camera launch/recall. To add + // another hold-activated action (e.g. a future quick menu on the other hand): add a new ActionId, + // give it its OWN HoldGesture field (the action delegates are static, so a shared field would race + // across hands), then Bind it to the desired role. + private static HoldGesture s_hamburgerHold; + /// - /// Toggles the hamburger menu on secondary button release. + /// Toggles the hamburger menu once the secondary button has been held for + /// . A quick tap does nothing, which leaves the tap free for other + /// consumers of the same button (e.g. the VR fly camera launch/recall). /// /// Current input snapshot. /// Previous input snapshot. + // Enum/method name kept as "…OnSecondaryRelease" so saved bindings (BasisActionBindingsV1.json, + // which serialises actions by name) keep resolving; the trigger is now a hold, not a release. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ToggleHamburgerOnSecondaryRelease(ref BasisInputState current, ref BasisInputState last) { - if (current.SecondaryButtonGetState == false && last.SecondaryButtonGetState) + if (s_hamburgerHold.Tick(current.SecondaryButtonGetState, HamburgerHoldSeconds)) { - Basis.BasisUI.BasisMainMenu.Toggle(); } } + /// + /// Tracks a press-and-hold on a single button. returns true on the one frame the + /// button has been held continuously for the given duration; releasing early cancels without firing + /// and re-arms on the next press. One instance per logical button — do not share across hands. + /// + public struct HoldGesture + { + private double pressStartTime; + private bool isPressing; + private bool fired; + + /// Advances the gesture; returns true once per press when the hold threshold is met. + /// Whether the button is held this frame. + /// Continuous hold duration required to fire. + public bool Tick(bool buttonDown, float holdSeconds) + { + if (!buttonDown) + { + isPressing = false; + fired = false; + return false; + } + + if (!isPressing) + { + isPressing = true; + fired = false; + pressStartTime = Time.unscaledTimeAsDouble; + return false; + } + + if (!fired && Time.unscaledTimeAsDouble - pressStartTime >= holdSeconds) + { + fired = true; + return true; + } + + return false; + } + } + /// /// Toggles the microphone pause state on primary button release when not hovering UI. ///