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.
///