Search Unity

  1. Tune in to get all the latest Unity news live from Berlin on June 19, 6pm CEST. Set up a YouTube reminder!
    Dismiss Notice
  2. Unity 2018.1 has arrived! Read about it here
    Dismiss Notice
  3. Scriptable Render Pipeline improvements, Texture Mipmap Streaming, and more! Check out what we have in store for you in the 2018.2 Beta.
    Dismiss Notice
  4. Improve your Unity skills with a certified instructor in a private, interactive classroom. Learn more.
    Dismiss Notice
  5. ARCore is out of developer preview! Read about it here.
    Dismiss Notice
  6. Magic Leap’s Lumin SDK Technical Preview for Unity lets you get started creating content for Magic Leap One™. Find more information on our blog!
    Dismiss Notice
  7. Want to see the most recent patch releases? Take a peek at the patch release page.
    Dismiss Notice

Unity UI Selection problem of Toggles/Buttons in a draggable ScrollRect

Discussion in 'Unity UI & TextMesh Pro' started by Kaijobu, Dec 26, 2017.

  1. Kaijobu

    Kaijobu

    Joined:
    Dec 26, 2017
    Posts:
    4
    Hello all,

    I've searched for a while but haven't found a solution for me.

    I am creating a mobile application with 2D elements on a canvas.
    On some panels I will have a ScrollRect with dynamic content, meaning the amount of items in there varies. I already managed to scale the content size with the content size fitter. What I now wanted to achieve is the possibility to drag this ScrollRect even if I touch a Button or Toggle.

    I achieved this also by attaching a script which implements IPointerDownHandler, IPointerUpHandler, IBeginDragHandler, IDragHandler, IEndDragHandler.

    I have also implemented the ability to press or hold either Toggles or Buttons. When I click my Toggles, they work as intended and change their color, when they are turned on.

    The problem I am facing is:

    Whenever I want to drag the ScrollRect view, the Selectable Object keeps getting selected when I re-enter the gameobject. With Buttons it was acceptable, but using toggles introduces the issue of changing the value of isOn, when I release the touch after a drag on this same Toggle. What I want is:
    The moment I begin to drag the content area, I want to deselect the pressed item and simply continue with dragging, regardless where I stop. If I release the touch outside of the starting element, nothing is changed like it should be.

    I hope the following images make this more understandable. The mouse pointer's position is shown with the star symbol.

    Beginning to hold down over a Toggle.


    Beginning to drag.


    Moving the mouse outside of the selected object.


    Reentering the Toggle. (This should react like any other Toggle being hovered over.)


    Releasing the touch/press over this Toggle. (Should not happen!)


    The code of this toggle is basically this:
    Code (CSharp):
    1.  
    2. [RequireComponent(typeof(Toggle))]
    3. public class HoldButtonOrderTableItem : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IBeginDragHandler, IDragHandler, IEndDragHandler
    4. {
    5.     private Toggle thisToggle;
    6.  
    7.     private bool isDown;
    8.     private bool isDrag;
    9.  
    10.     private float downTime;
    11.     private ScrollRect parentScrollRect;
    12.  
    13.     [...]
    14.  
    15.     void Update()
    16.     {
    17.         if (!isDown) return;
    18.         if (!isDrag && Time.realtimeSinceStartup - downTime > 1f)
    19.         {
    20.             HandleLongTap();
    21.         }
    22.     }
    23.  
    24.     private void HandleLongTap()
    25.     {
    26.         print("Handle Long Tap");
    27.         isDown = false;
    28.         // some functionality ...
    29.     }
    30.  
    31.     private void HandleShortTap()
    32.     {
    33.         Debug.Log("Handle Short Tap");
    34.         // some functionality ...
    35.     }
    36.  
    37.     public void OnPointerDown(PointerEventData eventData)
    38.     {
    39.         Debug.Log("Pointer Down!");
    40.  
    41.         isDrag = false;
    42.         isDown = true;
    43.         downTime = Time.realtimeSinceStartup;
    44.     }
    45.  
    46.     public void OnPointerUp(PointerEventData eventData)
    47.     {
    48.         Debug.Log("Pointer Up!");
    49.  
    50.         isDown = false;
    51.  
    52.         if (!isDrag && Time.realtimeSinceStartup - downTime <= 1f)
    53.         {
    54.             HandleShortTap();
    55.         }
    56.     }
    57.  
    58.     public void OnBeginDrag(PointerEventData eventData)
    59.     {
    60.         Debug.Log("Begin Drag!");
    61.         isDrag = true;
    62.  
    63.         // thisToggle.OnPointerExit(eventData);  <-- I tried different things here, nothing worked yet.
    64.  
    65.         parentScrollRect.OnBeginDrag(eventData);
    66.     }
    67.  
    68.     public void OnDrag(PointerEventData eventData)
    69.     {
    70.         parentScrollRect.OnDrag(eventData);
    71.     }
    72.  
    73.     public void OnEndDrag(PointerEventData eventData)
    74.     {
    75.         Debug.Log("End Drag.");
    76.  
    77.         parentScrollRect.OnEndDrag(eventData);
    78.     }
    79.  
    The received console output until the last image is this one, which shows it doesn't call any of my methods (HandleLong/ShortTap). So the Toggle is using the OnPointerUp Event and changes its ON-state.


    I somehow need to reset this toggle's state. Does anyone have any idea where to begin or how to achieve this?

    I need to use Unity 5.5.2f1.
     
    Last edited: Dec 27, 2017
  2. Kaijobu

    Kaijobu

    Joined:
    Dec 26, 2017
    Posts:
    4
    Sadly, I haven't been able to solve this yet.

    Does anyone have an idea for a possible solution?
     
  3. Hosnkobf

    Hosnkobf

    Joined:
    Aug 23, 2016
    Posts:
    204
    Because I also searched a while and couldn't find a proper solution, I dig out this old thread and present my solution for the next poor guy.

    I extended the StandaloneInputModule and applied some magic to it... works pretty good:
    Code (CSharp):
    1. public class MyInputModule : StandaloneInputModule
    2. {
    3.     ScrollRect _draggedScrollRect;
    4.     PointerEventData _lastDragPointer;
    5.  
    6.     public override void Process()
    7.     {
    8.        
    9.         base.Process();
    10.  
    11.         _mouseState = GetMousePointerEventData();
    12.  
    13.         // detect release of dragged scroll rect
    14.         if(_mouseState != null && _mouseState.AnyReleasesThisFrame() && _draggedScrollRect != null)
    15.         {
    16.             // only left button is handled by scroll rect (but it is sometimes not detected as left)
    17.             _lastDragPointer.button = PointerEventData.InputButton.Left;
    18.            
    19.             ExecuteEvents.Execute(_draggedScrollRect.gameObject, _lastDragPointer, ExecuteEvents.endDragHandler);
    20.             eventSystem.SetSelectedGameObject(null);
    21.  
    22.             _draggedScrollRect = null;
    23.         }
    24.     }
    25.  
    26.     protected override void ProcessDrag(PointerEventData pointerEvent)
    27.     {
    28.         _lastDragPointer = pointerEvent;
    29.         if (pointerEvent.IsPointerMoving() && Cursor.lockState != CursorLockMode.Locked && pointerEvent.pointerDrag != null)
    30.         {
    31.             if (!pointerEvent.dragging
    32.                 && ShouldStartDrag(pointerEvent.pressPosition,     pointerEvent.position,
    33.                                    eventSystem.pixelDragThreshold, pointerEvent.useDragThreshold))
    34.             {
    35.                 #region Try to find a Scroll Rect
    36.                 _draggedScrollRect = pointerEvent.pointerDrag.GetComponentInParent<ScrollRect>();
    37.                 if(_draggedScrollRect == null)
    38.                 {
    39.                     List<RaycastResult> raycast = new List<RaycastResult>();
    40.                     eventSystem.RaycastAll(pointerEvent, raycast);
    41.                     foreach(var hit in raycast)
    42.                     {
    43.                         if (!hit.isValid)
    44.                             continue;
    45.  
    46.                         var draggable = hit.gameObject.GetComponentInParent<ScrollRect>();
    47.                         if(draggable != null)
    48.                         {
    49.                             _draggedScrollRect = draggable;
    50.                             break;
    51.                         }
    52.                     }
    53.                 }
    54.  
    55.                 eventSystem.SetSelectedGameObject(null);
    56.                 #endregion
    57.  
    58.                 GameObject obj = (_draggedScrollRect != null)
    59.                     ? _draggedScrollRect.gameObject
    60.                     : pointerEvent.pointerDrag;
    61.  
    62.                 ExecuteEvents.Execute(obj, pointerEvent, ExecuteEvents.beginDragHandler);
    63.                 pointerEvent.dragging = true;
    64.             }
    65.  
    66.             if (pointerEvent.dragging)
    67.             {
    68.                 if (_draggedScrollRect != null)
    69.                 {
    70.                     pointerEvent.eligibleForClick = false;
    71.                     ExecuteEvents.Execute(_draggedScrollRect.gameObject, pointerEvent, ExecuteEvents.dragHandler);
    72.                 }
    73.                 else
    74.                 {
    75.                     // if dragging something different than scroll rect, use the original logic
    76.                     base.ProcessDrag(pointerEvent);
    77.                 }
    78.             }
    79.         }
    80.     }
    81.    
    82.     private static bool ShouldStartDrag(Vector2 pressPos, Vector2 currentPos, float threshold, bool useDragThreshold)
    83.     {
    84.         if (useDragThreshold)
    85.         {
    86.             Vector2 vector2 = pressPos - currentPos;
    87.             return vector2.sqrMagnitude >= threshold * threshold;
    88.         }
    89.  
    90.         return true;
    91.     }
    92. }