UI Scroll using XR controller thumbstick

Using the thumbsticks to control the UI Scroll Bar would be great. It doesn’t seem to be built in.
I’ve mentioned it before but it never got picked up I guess.

1 Like

+1

+1

+1

Since it still isn’t implemented as far as I can tell with the latest XRI 2.3.1
This is an own implementation example for anyone who needs it.
It’s quite straightforward and simple.

public class ThumbstickScrollListener : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
    public InputActionProperty ScrollActionProperty;
    private int pointerID = int.MinValue;

    [SerializeField]
    private Scrollbar VerticalScrollbar;

    [SerializeField]
    private Scrollbar HorizontalScrollbar;

    public void OnPointerEnter(PointerEventData eventData)
    {
        if (pointerID != int.MinValue) return;

        // Store the pointer ID as reference
        pointerID = eventData.pointerId;
        ScrollActionProperty.action?.Enable();
    }

    public void OnPointerExit(PointerEventData eventData)
    {
        // Only if the stored pointer ID is the same as this one
        if (eventData.pointerId != pointerID) return;

        // Reset pointer ID reference
        pointerID = int.MinValue;
        ScrollActionProperty.action?.Disable();
    }

    public void Update()
    {
        if (pointerID == int.MinValue)
            return;

        if (ScrollActionProperty.action is null or { enabled: false })
            return;

        var scrollDelta = ScrollActionProperty.action.ReadValue<Vector2>();
        AddScrollDelta(HorizontalScrollbar, scrollDelta.x);
        AddScrollDelta(VerticalScrollbar, scrollDelta.y);
    }

    private void AddScrollDelta(Scrollbar bar, float value)
    {
        if (bar == null) return;
        bar.value = Mathf.Clamp(bar.value + value, 0f, 1f);
    }
}

These are the settings of the input action.

6 Likes

Thanks a lot

I found it necessary to relate scroll speed to the pixel size of the scroll area rather than on scroll bar percentage to ensure the same behavior across multiple scroll areas in my UI. I had a massive text wall that was scrolling super fast since 0.02 of a mile is much farther than 0.02 of a foot.

All you really need to relate the two is a pixel measure of the overflow that the scrollbar is represents. The scrollbar is always (0, 1), so moving it its full length will travel that pixel distance.

Start by getting the available size of the scroll area by checking the RectTransform on the same object as the ScrollRect. I inserted this in my initialization because my scroll area’s display bounds never change.

scrollRect = this.gameObject.GetComponent<ScrollRect>();
displayHeight = scrollRect.gameObject.GetComponent<RectTransform>().rect.height;

Next, calculate the desired size of the content and use it to find the overflow size. This can be done in at least two ways depending on what’s reliable and available. The Content object’s RectTransform includes its layout’s buffers and spacing as well as all elements beneath it. The scrollbar’s size is a simple ratio of the desired size to the display size.

if (horizontalScrollbar != null && horizontalScrollbar.size < 1f)
{
    // These should produce the same width.
    float contentWidth = scrollRect.content.rect.width;
    //float contentWidth = (1f / horizontalScrollbar.size) * displayWidth;

    // The scrollbar's range (0, 1) will move the display by this many pixels.
    float scrollWidth = contentWidth - displayWidth; // scrollbar.size was confirmed < 1 so the content is larger.

    // The (0, 1) range of the bar represents a travel distance of scrollWidth pixels. 100px was arbitrary.
    float actualScrollSpeed = Mathf.Clamp((100f / scrollWidth) * relativeScrollSpeed, 0f, 1f);

    // Use the thumbstick with the heighest magnitude.
    if (Mathf.Abs(leftThumbStickValues.x) >= Mathf.Abs(rightThumbStickValues.x))
    {
        AddScrollDelta(horizontalScrollbar, leftThumbStickValues.x, actualScrollSpeed);
    }
    else
    {
        AddScrollDelta(horizontalScrollbar, rightThumbStickValues.x, actualScrollSpeed);
    }
}

The last change was to add scroll speed as a parameter to the AddScrollDelta method.

void AddScrollDelta(Scrollbar bar, float thumbstickValue, float scrollSpeed)
{
    if (bar == null) { return; }
    bar.value = Mathf.Clamp(bar.value + (thumbstickValue * scrollSpeed), 0f, 1f);
}

For VR, I swapped out the pointerID in the original examples with a List<int> pointerList so that I could track both controllers entering and exiting, and recognize which one was hovering. Assuming their IDs are unique and consistent across loads (mine appear to be so far), you can set it up to only read thumbstick data from a controller that is hovering the area, meaning each controller could conceivably scroll different UI elements at the same time. It also means both controllers could try to scroll the same element, though, so you’ll need to resolve that before you store your scroll amount. I chose to take the controller with the highest magnitude on its thumbstick.

1 Like