Search Unity

  1. Improved Prefab workflow (includes Nested Prefabs!), 2D isometric Tilemap and more! Get the 2018.3 Beta now.
    Dismiss Notice
  2. The Unity Pro & Visual Studio Professional Bundle gives you the tools you need to develop faster & collaborate more efficiently. Learn more.
    Dismiss Notice
  3. Let us know a bit about your interests, and if you'd like to become more directly involved. Take our survey!
    Dismiss Notice
  4. Improve your Unity skills with a certified instructor in a private, interactive classroom. Watch the overview now.
    Dismiss Notice
  5. 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:
    396
    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. }