Search Unity

[FREE] Reorderable List

Discussion in 'UGUI & TextMesh Pro' started by Ziboo, Oct 28, 2015.

  1. Ziboo

    Ziboo

    Joined:
    Aug 30, 2011
    Posts:
    356
    Hey,
    I had some time and needed to work on a Reorderable List system for one of my project.
    Here is a preview:


    It's waiting a pull request from Unity-UI-Extensions

    Make sure to get it when it has been approuved :)

    Cheers
     
  2. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    Fantastic work @Ziboo , saw your PR, will get this reviewed and committed as soon as I can.

    Would be great to see this also working horizontally and in a grid as well. (maybe even in the radial layout??)
     
    MarkHelsinki likes this.
  3. Ziboo

    Ziboo

    Joined:
    Aug 30, 2011
    Posts:
    356
    Thanks, you're welcome :)

    Added support for HorizontalLayout and GridLayout too
     
    zacharyaghaizu and hopeful like this.
  4. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    Great work on the update, re-consuming that now.
    However I did note that you have a ContentSizeFitter on the child content, which according to the Unity UI Rules is invalid (gives you a warning as well). Although , I also note that be removing that, it causes some issues when dropping content back in to the list.

    Any ideas?
     
  5. Ziboo

    Ziboo

    Joined:
    Aug 30, 2011
    Posts:
    356
    I wanted to layout my test lists quickly so I made some dirty things in the scene (Lists>Column x)

    But, the list itself has a pretty standard setup:
    - ScrollRect
    -- Content (LayoutGroup + ContentSizeFitter)
    ---Element (LayoutElement)
    ---Element (LayoutElement)
    ---Element (LayoutElement)
     
  6. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    Just tweeted, but copy here as well, if I try this setup, dropping / reordering of elements doesn't work:
    - EmptyGO (with Reorderable Script)
    -- Content (LayoutGroup + ContentSizeFitter)
    ---Element (LayoutElement)
    ---Element (LayoutElement)
    ---Element (LayoutElement)
     
  7. Ziboo

    Ziboo

    Joined:
    Aug 30, 2011
    Posts:
    356
    Yep you're right !
    The object having the Reorderable Script needs to have a UI element.
    It's needed when doing a raycast to see which list is under the cursor
     
  8. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    Ahh right, ok continuing testing. I've done an initial checkin after consuming your code, so be sure to update your fork before any further updates.
    *edit = Yup, that works!
     
  9. Ziboo

    Ziboo

    Joined:
    Aug 30, 2011
    Posts:
    356
    sure thing ;)
     
  10. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    Just tweaking scripts and making updates to ensure that's clear.
     
  11. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    OK, hopefully last question.
    What is the purpose of the "Dragable Area"? It seems to just be the top level point to hold the "fakes" while dragging. Why not just use the GO with the script?
    Right, I see you use that to keep the Dragged component "on top" of the other elements. making a slight update to make this optional and use the top level canvas as a default.
     
    Last edited: Oct 29, 2015
  12. Ziboo

    Ziboo

    Joined:
    Aug 30, 2011
    Posts:
    356
    Because you can have a mask on the GO with the script. It's typicaly how I do my setup

    - ScrollRect (Image + Mask)
    -- Content (LayoutGroup + ContentSizeFitter)
    ---Element (LayoutElement)
    ---...

    Small improvement maybe would be the select the top parent canvas if Dragable Area is not set...
     
  13. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    Yes, that is what I've done. I've also added additional events for grabbing an item and removing an item from a list :D. Should have the update pushed up soon, then I can back port it to 4.6 and push it to the 5.3 release ;S
     
  14. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    #UIExtensions updated to V1.05, complete with the Re-Orderable Lists, editor extensions and a few of my own tweaks :D
    Thanks for the support!, Keep it coming.
     
    Cromfeli likes this.
  15. Maxter

    Maxter

    Joined:
    Mar 9, 2013
    Posts:
    7
    Hello, I tried to use reorderable list from UIExtensions, but stuck with a weird bug. I have several Text elements within a list element. After I drag&drop this element inside the same list, most of the text elements go blank.
    In the inspector:
    • their sizes appear correct
    • they have text
    • they are enabled and visible
    • hierarchy is correct
    But despite all that I can't see any text in 3 out of 4 text elements.

    UPD:
    While I was writing, I figured out what's wrong. After drag&drop Text elements became slightly smaller in height which caused text to disappear. I added Layout Element to them with Min Height set and it solved the problem.
     
  16. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    Another bug was raised on the ReOrderable list. If it is on a Screen Space - Camera (with camera set) or World Space Canvas, it produces some weird results. Drag and drop doesn't work, elements get merged, etc.

    I suspect it is simply to do with the different coordinate system used in these cases. Any chance you can have a look?
     
  17. Ziboo

    Ziboo

    Joined:
    Aug 30, 2011
    Posts:
    356
    Hi,

    I'm sorry but I really don't have the time...
    I shared the script as is, and it's very cool that it joined the collection, but I'm not sure I will work on it again, except if I need it in one of my project, then I will improve and share it again.

    Sorry guys, hope someone will have the time to check on it.

    Good luck

    Cheers
     
  18. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    Ok, no worries @Ziboo I'm in the same boat really. Only asked since you were the author if you had any thoughts on the picking / dragging coordinates and could offer support.

    I'll look again if I have the time while packaging this release, else the community can pitch in.

    Failing that, there's always adding a check to only allow it to be used in SS-Overlay for now :D
     
  19. Ziboo

    Ziboo

    Joined:
    Aug 30, 2011
    Posts:
    356
    SERRVIEX likes this.
  20. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
    Hi @SimonDarksideJ and @Ziboo

    I find UI extensions project interesting yet too complicated for me... learning... however took a look at Reorderable List code.

    DL'd and opened latest 5.3 package and opened "ReorderableList" test scene. And if I didn't mess anything right at start, it doesn't work with Screen Space Camera and World space camera at all. Dragged item will be offset from mouse cursor if editor game viewport is scaled.

    Seems like problem could be mostly on line 103, instead of 132-148.

    Code (csharp):
    1.  
    2. //Set dragging object on cursor
    3. _draggingObject.position = eventData.position;
    4.  
    Eventsystem position is used for dragged object position. IMHO It's not going to work based on screen position (many problems). Instead so far on my own projects, I've resorted to RectTransformUtility, and I've worked coordinates in respective parents space (or sometimes in worldspace). As dragged object seems to be always child of "Main" RT, and it seems to be available as _reorderableList.DraggableArea, it could be used as parent in this case:

    Code (csharp):
    1.  
    2. // Screen to rect space conversion
    3. Vector2 lPos = Vector2.zero;
    4. RectTransformUtility.ScreenPointToLocalPointInRectangle(_reorderableList.DraggableArea, eventData.position, eventData.enterEventCamera, out lPos);
    5. _draggingObject.localPosition = lPos;
    6.  
    With this, screen viewport scaling does not matter with Screen Space camera, World space camera works, it can be rotated, scaled, and Main RT too can be scaled, and Main can be of different size than whole canvas, dragged item should stay on cursor despite of these operations.

    There seems to be another issues with dragged item locking into center of canvas a some moments, when dragging near other rectTransforms, didn't try to figure it out...maybe it's caused by this change.

    Well, anyway, shoot this down if it's something else...
     
  21. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
    @SimonDarksideJ - Another strange choice seems to be the fact that in demo scene, ("ReorderableList" file) main canvas has Canvas Scaler, and it's set to "Constant Pixel Size" which doesn't result in anything meaningful... I've mostly only used "Scale with Screen Size" -> results are more predictable.

    So maybe this too has caused some false alarms?
     
  22. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    Nah because in the UI Extensions I extract all that so you can create it fro the menu, so it doesn't seem to make any difference there.
    From past experience it is the Coordinate system used between SS-O and SS-C/WS, one uses screenspace, the other world coord's
     
  23. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
    @SimonDarksideJ

    I'm not sure if you understood what I meant with my second post, I tried to say, if demo scene UI scales along Unity editor screen size, like it does now in provided demo scene, it can look really messed up, on smaller viewport sizes, you can barely see buttons inside scroll rects, so some people might think it's somehow broken...

    That dragged sprite offset is definitely offset based on editor viewport size.
     
  24. JellyfishUmbrella

    JellyfishUmbrella

    Joined:
    May 6, 2015
    Posts:
    5
    Should I be able to move things from one list to another in code? Right now, I'm trying to add a "reset' ability to put a list back to its original state after a user has dragged a bunch of things out of it, but once I reparent my elements back to their original list, the drag-n-drop ability gets munched a bit. When I drag something out of my list after a reset, the position snapping doesn't work until I drop the element in the new list, and then regrab it. So it seems like the lists don't realize I've moved things out of them if I do it through code. Is there a better way to send items from list to list, and make sure the lists realize what's going on?

    Thanks,
    -Rich
     
  25. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    It's all in the options. If you look at the demo scene, you can control how child elements are dragged from (cloned / move) and dropped (add / replace / etc) as well as whether a list supports dropping.
    If you want to reset a list, then your going to need either a prefab of it's original state, destroy the old and read the prefab. Or manage the children and rebuild the children for you. There is no state management as part of the control.

    Hope that helps.
     
  26. adamers

    adamers

    Joined:
    Apr 2, 2014
    Posts:
    5
    How can i save order of layout elements ?
     
  27. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    The simplest way I can think of, is to ensure each item is unique and has a unique name. Then simply walk through the Child list and serialise the list of names in that order. Then when restoring, just instantiate each child in that same order.

    When children are reordered, it's their order in the child transform hierarchy that is finally ordered.
     
  28. freedom667

    freedom667

    Joined:
    Sep 6, 2015
    Posts:
    425
    how can we make it animated? I mean, as slidely?
     
  29. Leslie-Young

    Leslie-Young

    Joined:
    Dec 24, 2008
    Posts:
    1,148
    Anyone tried this with variable size elements in the target list and know of a solution to the following problem?

    There seems to be jitter on the element to be move up or down when moving a dragged element over it and that list element is not the same size as the 1st, and dragged, element.

    I tested with the 2nd top row list having its red element's min height set to 50 rather than 30.
     
  30. duckburger

    duckburger

    Joined:
    Dec 7, 2016
    Posts:
    9
    Hey, awesome component! Would this potentially support a smooth movement of the items around the "Fake" cell (like in iOS)?
     
    HutomSw likes this.
  31. ComputerCables

    ComputerCables

    Joined:
    Jun 21, 2017
    Posts:
    5
    When you drag an item out and back in, it always resizes to 100x100, even if preferred width and height are changed. Can't get it to stay 64x64
     
  32. eskiroy

    eskiroy

    Joined:
    May 9, 2014
    Posts:
    11
  33. travlake

    travlake

    Joined:
    Oct 4, 2019
    Posts:
    50
    For people finding this thread looking for a fix to the issues with Screen Space - Camera canvases, the following worked for me:

    1) Be sure there is a Camera attached to the Canvas you are using
    2) Replace the for loop in the OneDrag method of ReorderableListElement.cs with the following:
    Code (CSharp):
    1. var screenPoint = new Vector3();
    2. if (canvas.renderMode == RenderMode.ScreenSpaceCamera)
    3. {
    4.     screenPoint = canvas.worldCamera.WorldToScreenPoint(_draggingObject.position);
    5. }
    6. for (var j = 0; j < _currentReorderableListRaycasted.Content.childCount; j++)
    7. {
    8.     var childJ = _currentReorderableListRaycasted.Content.GetChild(j).GetComponent<RectTransform>();
    9.     var childJScreenPoint = _camera.WorldToScreenPoint(childJ.position);
    10.  
    11.     // Debug.Log("Screen positions of child | event" + childJScreenPoint + " | " + screenPoint);
    12.  
    13.     if (canvas.renderMode == RenderMode.ScreenSpaceCamera)
    14.     {
    15.         switch (_currentReorderableListRaycasted.ContentLayout)
    16.         {
    17.             case VerticalLayoutGroup _:
    18.                 dist = Mathf.Abs(childJScreenPoint.y - screenPoint.y);
    19.                 break;
    20.             case HorizontalLayoutGroup _:
    21.                 dist = Mathf.Abs(childJScreenPoint.x - screenPoint.x);
    22.                 break;
    23.             case GridLayoutGroup _:
    24.                 dist = (Mathf.Abs(childJScreenPoint.x - screenPoint.x) +
    25.                         Mathf.Abs(childJScreenPoint.y - screenPoint.y));
    26.                 break;
    27.         }
    28.     }
    29.     else
    30.     {
    31.         if (_currentReorderableListRaycasted.ContentLayout is VerticalLayoutGroup)
    32.             dist = Mathf.Abs(childJ.position.y - worldPoint.y);
    33.         else if (_currentReorderableListRaycasted.ContentLayout is HorizontalLayoutGroup)
    34.             dist = Mathf.Abs(childJ.position.x - worldPoint.x);
    35.         else if (_currentReorderableListRaycasted.ContentLayout is GridLayoutGroup)
    36.             dist = (Mathf.Abs(childJ.position.x - worldPoint.x) + Mathf.Abs(childJ.position.y - worldPoint.y));
    37.     }
    38.  
    39.     if (!(dist < minDistance)) continue;
    40.     minDistance = dist;
    41.     targetIndex = j;
    42. }
     
  34. betaFlux

    betaFlux

    Joined:
    Jan 7, 2013
    Posts:
    112
    Hi, I'm trying to figure out how to drag the items on the spot I clicked on, so I created an offset variable and added the following code to the ReorderableListElement (pseudo):

    Code (CSharp):
    1. Vector3 _dragOffset;
    2.  
    3. void OnBeginDrag()
    4. {
    5.     ..dev code..
    6.     _dragOffset = draggingObject.position - Input.mousePosition;
    7. }
    8.  
    9. void OnDrag()
    10. {
    11.     ..dev code..
    12.     Vector3 worldPoint;
    13.  
    14.     RectTransformUtility.ScreenPointToWorldPointInRectangle(canvas.GetComponent<RectTransform>(), eventData.position, canvas.renderMode != RenderMode.ScreenSpaceOverlay ? canvas.worldCamera : null, out worldPoint);
    15.     _draggingObject.position = worldPoint + _dragOffset;
    16.     ..dev code..
    17. }
    Unfortunately this only works partly and something is still off. I'm not a pro coder, so I'd really appreciate some help.
     
    TheFastLane likes this.
  35. betaFlux

    betaFlux

    Joined:
    Jan 7, 2013
    Posts:
    112
    Okay, got it working.

    Just in case anyone needs this functionality (make the element stick to the cursor where it was clicked):

    Code (CSharp):
    1. /// Credit Ziboo, Andrew Quesenberry
    2. /// Sourced from - http://forum.unity3d.com/threads/free-reorderable-list.364600/
    3. /// Last Child Fix - https://bitbucket.org/SimonDarksideJ/unity-ui-extensions/issues/70/all-re-orderable-lists-cause-a-transform
    4.  
    5. using System;
    6. using System.Collections.Generic;
    7. using UnityEngine.EventSystems;
    8.  
    9. namespace UnityEngine.UI.Extensions
    10. {
    11.  
    12.     [RequireComponent(typeof(RectTransform), typeof(LayoutElement))]
    13.     public class ReorderableListElement : MonoBehaviour, IDragHandler, IBeginDragHandler, IEndDragHandler
    14.     {
    15.         [Tooltip("Can this element be dragged?")]
    16.         [SerializeField]
    17.         private bool IsGrabbable = true;
    18.  
    19.         [Tooltip("Can this element be transfered to another list")]
    20.         [SerializeField]
    21.         private bool _isTransferable = true;
    22.  
    23.         [Tooltip("Can this element be dropped in space?")]
    24.         [SerializeField]
    25.         private bool isDroppableInSpace = false;
    26.  
    27.         Vector3 _dragOffset;
    28.         Vector3 _startPos;
    29.         public bool IsTransferable
    30.         {
    31.             get { return _isTransferable; }
    32.             set
    33.             {
    34.                 _canvasGroup = gameObject.GetOrAddComponent<CanvasGroup>();
    35.                 _canvasGroup.blocksRaycasts = value;
    36.                 _isTransferable = value;
    37.             }
    38.         }
    39.  
    40.         private readonly List<RaycastResult> _raycastResults = new List<RaycastResult>();
    41.         private ReorderableList _currentReorderableListRaycasted;
    42.  
    43.         private int _fromIndex;
    44.         private RectTransform _draggingObject;
    45.         private LayoutElement _draggingObjectLE;
    46.         private Vector2 _draggingObjectOriginalSize;
    47.  
    48.         private RectTransform _fakeElement;
    49.         private LayoutElement _fakeElementLE;
    50.  
    51.         private int _displacedFromIndex;
    52.         private RectTransform _displacedObject;
    53.         private LayoutElement _displacedObjectLE;
    54.         private Vector2 _displacedObjectOriginalSize;
    55.         private ReorderableList _displacedObjectOriginList;
    56.  
    57.         private bool _isDragging;
    58.         private RectTransform _rect;
    59.         private ReorderableList _reorderableList;
    60.         private CanvasGroup _canvasGroup;
    61.         internal bool isValid;
    62.  
    63.  
    64.         #region IBeginDragHandler Members
    65.  
    66.         public void OnBeginDrag(PointerEventData eventData)
    67.         {
    68.             if (!_canvasGroup) { _canvasGroup = gameObject.GetOrAddComponent<CanvasGroup>(); }
    69.             _canvasGroup.blocksRaycasts = false;
    70.             isValid = true;
    71.             if (_reorderableList == null)
    72.                 return;
    73.  
    74.             //Can't drag, return...
    75.             if (!_reorderableList.IsDraggable || !this.IsGrabbable)
    76.             {
    77.                 _draggingObject = null;
    78.                 return;
    79.             }
    80.  
    81.             //If not CloneDraggedObject just set draggingObject to this gameobject
    82.             if (_reorderableList.CloneDraggedObject == false)
    83.             {
    84.                 _draggingObject = _rect;
    85.                 _fromIndex = _rect.GetSiblingIndex();
    86.                 _displacedFromIndex = -1;
    87.                 //Send OnElementRemoved Event
    88.                 if (_reorderableList.OnElementRemoved != null)
    89.                 {
    90.                     _reorderableList.OnElementRemoved.Invoke(new ReorderableList.ReorderableListEventStruct
    91.                         {
    92.                             DroppedObject = _draggingObject.gameObject,
    93.                             IsAClone = _reorderableList.CloneDraggedObject,
    94.                             SourceObject = _reorderableList.CloneDraggedObject ? gameObject : _draggingObject.gameObject,
    95.                             FromList = _reorderableList,
    96.                             FromIndex = _fromIndex,
    97.                         });
    98.                 }
    99.                 if (isValid == false)
    100.                 {
    101.                     _draggingObject = null;
    102.                     return;
    103.                 }
    104.             }
    105.             else
    106.             {
    107.                 //Else Duplicate
    108.                 GameObject clone = (GameObject)Instantiate(gameObject);
    109.                 _draggingObject = clone.GetComponent<RectTransform>();
    110.             }
    111.  
    112.  
    113.             //Put _dragging object into the dragging area
    114.             //_dragOffset = _draggingObject.position - Input.mousePosition;
    115.             //_dragOffset.y += 5;
    116.  
    117.             _draggingObjectOriginalSize = gameObject.GetComponent<RectTransform>().rect.size;
    118.             _draggingObjectLE = _draggingObject.GetComponent<LayoutElement>();
    119.             _draggingObject.SetParent(_reorderableList.DraggableArea, true);
    120.             _draggingObject.SetAsLastSibling();
    121.             _reorderableList.Refresh();
    122.             //Create a fake element for previewing placement
    123.             _fakeElement = new GameObject("Fake").AddComponent<RectTransform>();
    124.             _fakeElementLE = _fakeElement.gameObject.AddComponent<LayoutElement>();
    125.  
    126.             RefreshSizes();
    127.  
    128.             //Send OnElementGrabbed Event
    129.             if (_reorderableList.OnElementGrabbed != null)
    130.             {
    131.                 _reorderableList.OnElementGrabbed.Invoke(new ReorderableList.ReorderableListEventStruct
    132.                     {
    133.                         DroppedObject = _draggingObject.gameObject,
    134.                         IsAClone = _reorderableList.CloneDraggedObject,
    135.                         SourceObject = _reorderableList.CloneDraggedObject ? gameObject : _draggingObject.gameObject,
    136.                         FromList = _reorderableList,
    137.                         FromIndex = _fromIndex,
    138.                     });
    139.  
    140.                 if (!isValid)
    141.                 {
    142.                     CancelDrag();
    143.                     return;
    144.                 }
    145.             }
    146.  
    147.          
    148.        
    149.             _isDragging = true;
    150.             _dragOffset = /*new Vector3(_draggingObject.position.x, _draggingObject.position.y, 0) -*/ Input.mousePosition;
    151.             _startPos = _draggingObject.position;
    152.         }
    153.  
    154.         #endregion
    155.  
    156.  
    157.         #region IDragHandler Members
    158.  
    159.         public void OnDrag(PointerEventData eventData)
    160.         {
    161.             if (!_isDragging)
    162.                 return;
    163.             if (!isValid)
    164.             {
    165.                 CancelDrag();
    166.                 return;
    167.             }
    168.             //Set dragging object on cursor
    169.             var canvas = _draggingObject.GetComponentInParent<Canvas>();
    170.             Vector3 worldPoint;
    171.             RectTransformUtility.ScreenPointToWorldPointInRectangle(canvas.GetComponent<RectTransform>(), eventData.position,
    172.                 canvas.renderMode != RenderMode.ScreenSpaceOverlay ? canvas.worldCamera : null, out worldPoint);
    173.             Vector3 diff = Input.mousePosition - _dragOffset;
    174.             Vector3 pos = _startPos + diff;
    175.             _draggingObject.position = pos; // worldPoint + Input.mousePosition;
    176.  
    177.             ReorderableList _oldReorderableListRaycasted = _currentReorderableListRaycasted;
    178.  
    179.             //Check everything under the cursor to find a ReorderableList
    180.             EventSystem.current.RaycastAll(eventData, _raycastResults);
    181.             for (int i = 0; i < _raycastResults.Count; i++)
    182.             {
    183.                 _currentReorderableListRaycasted = _raycastResults[i].gameObject.GetComponent<ReorderableList>();
    184.                 if (_currentReorderableListRaycasted != null)
    185.                 {
    186.                     break;
    187.                 }
    188.             }
    189.  
    190.             //If nothing found or the list is not dropable, put the fake element outside
    191.             if (_currentReorderableListRaycasted == null || _currentReorderableListRaycasted.IsDropable == false
    192. //                || (_oldReorderableListRaycasted != _reorderableList && !IsTransferable)
    193.                 || ((_fakeElement.parent == _currentReorderableListRaycasted.Content
    194.                     ? _currentReorderableListRaycasted.Content.childCount - 1
    195.                     : _currentReorderableListRaycasted.Content.childCount) >= _currentReorderableListRaycasted.maxItems && !_currentReorderableListRaycasted.IsDisplacable)
    196.                 || _currentReorderableListRaycasted.maxItems <= 0)
    197.             {
    198.                 RefreshSizes();
    199.                 _fakeElement.transform.SetParent(_reorderableList.DraggableArea, false);
    200.                 // revert the displaced element when not hovering over its list
    201.                 if (_displacedObject != null)
    202.                 {
    203.                     revertDisplacedElement();
    204.                 }
    205.             }
    206.             //Else find the best position on the list and put fake element on the right index
    207.             else
    208.             {
    209.                 if (_currentReorderableListRaycasted.Content.childCount < _currentReorderableListRaycasted.maxItems && _fakeElement.parent != _currentReorderableListRaycasted.Content)
    210.                 {
    211.                     _fakeElement.SetParent(_currentReorderableListRaycasted.Content, false);
    212.                 }
    213.  
    214.                 float minDistance = float.PositiveInfinity;
    215.                 int targetIndex = 0;
    216.                 float dist = 0;
    217.                 for (int j = 0; j < _currentReorderableListRaycasted.Content.childCount; j++)
    218.                 {
    219.                     var c = _currentReorderableListRaycasted.Content.GetChild(j).GetComponent<RectTransform>();
    220.  
    221.                     if (_currentReorderableListRaycasted.ContentLayout is VerticalLayoutGroup)
    222.                         dist = Mathf.Abs(c.position.y - pos.y);
    223.                     else if (_currentReorderableListRaycasted.ContentLayout is HorizontalLayoutGroup)
    224.                         dist = Mathf.Abs(c.position.x - pos.x);
    225.                     else if (_currentReorderableListRaycasted.ContentLayout is GridLayoutGroup)
    226.                         dist = (Mathf.Abs(c.position.x - pos.x) + Mathf.Abs(c.position.y - pos.y));
    227.  
    228.                     if (dist < minDistance)
    229.                     {
    230.                         minDistance = dist;
    231.                         targetIndex = j;
    232.                     }
    233.                 }
    234.                 if ((_currentReorderableListRaycasted != _oldReorderableListRaycasted || targetIndex != _displacedFromIndex)
    235.                     && _currentReorderableListRaycasted.Content.childCount == _currentReorderableListRaycasted.maxItems)
    236.                 {
    237.                     Transform toDisplace = _currentReorderableListRaycasted.Content.GetChild(targetIndex);
    238.                     if (_displacedObject != null)
    239.                     {
    240.                         revertDisplacedElement();
    241.                         if (_currentReorderableListRaycasted.Content.childCount > _currentReorderableListRaycasted.maxItems)
    242.                         {
    243.                             displaceElement(targetIndex, toDisplace);
    244.                         }
    245.                     }
    246.                     else if (_fakeElement.parent != _currentReorderableListRaycasted.Content)
    247.                     {
    248.                         _fakeElement.SetParent(_currentReorderableListRaycasted.Content, false);
    249.                         displaceElement(targetIndex, toDisplace);
    250.                     }
    251.                 }
    252.                 RefreshSizes();
    253.                 _fakeElement.SetSiblingIndex(targetIndex);
    254.                 _fakeElement.gameObject.SetActive(true);
    255.  
    256.             }
    257.         }
    258.  
    259.         #endregion
    260.  
    261.  
    262.         #region Displacement
    263.  
    264.         private void displaceElement(int targetIndex, Transform displaced)
    265.         {
    266.             _displacedFromIndex = targetIndex;
    267.             _displacedObjectOriginList = _currentReorderableListRaycasted;
    268.             _displacedObject = displaced.GetComponent<RectTransform>();
    269.             _displacedObjectLE = _displacedObject.GetComponent<LayoutElement>();
    270.             _displacedObjectOriginalSize = _displacedObject.rect.size;
    271.  
    272.             var args = new ReorderableList.ReorderableListEventStruct
    273.             {
    274.                 DroppedObject = _displacedObject.gameObject,
    275.                 FromList = _currentReorderableListRaycasted,
    276.                 FromIndex = targetIndex,
    277.             };
    278.  
    279.  
    280.             int c = _fakeElement.parent == _reorderableList.Content
    281.                 ? _reorderableList.Content.childCount - 1
    282.                 : _reorderableList.Content.childCount;
    283.  
    284.             if (_reorderableList.IsDropable && c < _reorderableList.maxItems && _displacedObject.GetComponent<ReorderableListElement>().IsTransferable)
    285.             {
    286.                 _displacedObjectLE.preferredWidth = _draggingObjectOriginalSize.x;
    287.                 _displacedObjectLE.preferredHeight = _draggingObjectOriginalSize.y;
    288.                 _displacedObject.SetParent(_reorderableList.Content, false);
    289.                 _displacedObject.rotation = _reorderableList.transform.rotation;
    290.                 _displacedObject.SetSiblingIndex(_fromIndex);
    291.                 // Force refreshing both lists because otherwise we get inappropriate FromList in ReorderableListEventStruct
    292.                 _reorderableList.Refresh();
    293.                 _currentReorderableListRaycasted.Refresh();
    294.  
    295.                 args.ToList = _reorderableList;
    296.                 args.ToIndex = _fromIndex;
    297.                 _reorderableList.OnElementDisplacedTo.Invoke(args);
    298.                 _reorderableList.OnElementAdded.Invoke(args);
    299.             }
    300.             else if (_displacedObject.GetComponent<ReorderableListElement>().isDroppableInSpace)
    301.             {
    302.                 _displacedObject.SetParent(_currentReorderableListRaycasted.DraggableArea, true);
    303.                 _currentReorderableListRaycasted.Refresh();
    304.                 _displacedObject.position += new Vector3(_draggingObjectOriginalSize.x / 2, _draggingObjectOriginalSize.y / 2, 0);
    305.             }
    306.             else
    307.             {
    308.                 _displacedObject.SetParent(null, true);
    309.                 _displacedObjectOriginList.Refresh();
    310.                 _displacedObject.gameObject.SetActive(false);
    311.             }
    312.             _displacedObjectOriginList.OnElementDisplacedFrom.Invoke(args);
    313.             _reorderableList.OnElementRemoved.Invoke(args);
    314.         }
    315.  
    316.         private void revertDisplacedElement()
    317.         {
    318.             var args = new ReorderableList.ReorderableListEventStruct
    319.             {
    320.                 DroppedObject = _displacedObject.gameObject,
    321.                 FromList = _displacedObjectOriginList,
    322.                 FromIndex = _displacedFromIndex,
    323.             };
    324.             if (_displacedObject.parent != null)
    325.             {
    326.                 args.ToList = _reorderableList;
    327.                 args.ToIndex = _fromIndex;
    328.             }
    329.  
    330.             _displacedObjectLE.preferredWidth = _displacedObjectOriginalSize.x;
    331.             _displacedObjectLE.preferredHeight = _displacedObjectOriginalSize.y;
    332.             _displacedObject.SetParent(_displacedObjectOriginList.Content, false);
    333.             _displacedObject.rotation = _displacedObjectOriginList.transform.rotation;
    334.             _displacedObject.SetSiblingIndex(_displacedFromIndex);
    335.             _displacedObject.gameObject.SetActive(true);
    336.  
    337.             // Force refreshing both lists because otherwise we get inappropriate FromList in ReorderableListEventStruct
    338.             _reorderableList.Refresh();
    339.             _displacedObjectOriginList.Refresh();
    340.  
    341.             if (args.ToList != null)
    342.             {
    343.                 _reorderableList.OnElementDisplacedToReturned.Invoke(args);
    344.                 _reorderableList.OnElementRemoved.Invoke(args);
    345.             }
    346.             _displacedObjectOriginList.OnElementDisplacedFromReturned.Invoke(args);
    347.             _displacedObjectOriginList.OnElementAdded.Invoke(args);
    348.  
    349.             _displacedFromIndex = -1;
    350.             _displacedObjectOriginList = null;
    351.             _displacedObject = null;
    352.             _displacedObjectLE = null;
    353.  
    354.         }
    355.  
    356.  
    357.         public void finishDisplacingElement()
    358.         {
    359.             if (_displacedObject.parent == null)
    360.             {
    361.                 Destroy(_displacedObject.gameObject);
    362.             }
    363.             _displacedFromIndex = -1;
    364.             _displacedObjectOriginList = null;
    365.             _displacedObject = null;
    366.             _displacedObjectLE = null;
    367.         }
    368.  
    369.         #endregion
    370.  
    371.  
    372.         #region IEndDragHandler Members
    373.  
    374.         public void OnEndDrag(PointerEventData eventData)
    375.         {
    376.             _isDragging = false;
    377.  
    378.             if (_draggingObject != null)
    379.             {
    380.                 //If we have a ReorderableList that is dropable
    381.                 //Put the dragged object into the content and at the right index
    382.                 if (_currentReorderableListRaycasted != null && _fakeElement.parent == _currentReorderableListRaycasted.Content)
    383.                 {
    384.                     var args = new ReorderableList.ReorderableListEventStruct
    385.                     {
    386.                         DroppedObject = _draggingObject.gameObject,
    387.                         IsAClone = _reorderableList.CloneDraggedObject,
    388.                         SourceObject = _reorderableList.CloneDraggedObject ? gameObject : _draggingObject.gameObject,
    389.                         FromList = _reorderableList,
    390.                         FromIndex = _fromIndex,
    391.                         ToList = _currentReorderableListRaycasted,
    392.                         ToIndex = _fakeElement.GetSiblingIndex()
    393.                     };
    394.                     //Send OnelementDropped Event
    395.                     if (_reorderableList && _reorderableList.OnElementDropped != null)
    396.                     {
    397.                         _reorderableList.OnElementDropped.Invoke(args);
    398.                     }
    399.                     if (!isValid)
    400.                     {
    401.                         CancelDrag();
    402.                         return;
    403.                     }
    404.                     RefreshSizes();
    405.                     _draggingObject.SetParent(_currentReorderableListRaycasted.Content, false);
    406.                     _draggingObject.rotation = _currentReorderableListRaycasted.transform.rotation;
    407.                     _draggingObject.SetSiblingIndex(_fakeElement.GetSiblingIndex());
    408.  
    409.                     //If the item is transferable, it can be dragged out again
    410.                     if (IsTransferable)
    411.                     {
    412.                         var cg = _draggingObject.GetComponent<CanvasGroup>();
    413.                         cg.blocksRaycasts = true;
    414.                     }
    415.                     // Force refreshing both lists because otherwise we get inappropriate FromList in ReorderableListEventStruct
    416.                     _reorderableList.Refresh();
    417.                     _currentReorderableListRaycasted.Refresh();
    418.  
    419.                     _reorderableList.OnElementAdded.Invoke(args);
    420.          
    421.                     if (_displacedObject != null)
    422.                     {
    423.                         finishDisplacingElement();
    424.                     }
    425.  
    426.                     if (!isValid)
    427.                         throw new Exception("It's too late to cancel the Transfer! Do so in OnElementDropped!");
    428.                 }
    429.              
    430.                 else
    431.                 {
    432.                     //We don't have an ReorderableList
    433.                     if (this.isDroppableInSpace)
    434.                     {
    435.                         _reorderableList.OnElementDropped.Invoke(new ReorderableList.ReorderableListEventStruct
    436.                             {
    437.                                 DroppedObject = _draggingObject.gameObject,
    438.                                 IsAClone = _reorderableList.CloneDraggedObject,
    439.                                 SourceObject =
    440.                                     _reorderableList.CloneDraggedObject ? gameObject : _draggingObject.gameObject,
    441.                                 FromList = _reorderableList,
    442.                                 FromIndex = _fromIndex
    443.                             });
    444.                     }
    445.                     else
    446.                     {
    447.                         CancelDrag();
    448.                     }
    449.                  
    450.                     //If there is no more room for the element in the target list, notify it (OnElementDroppedWithMaxItems event)
    451.                     if (_currentReorderableListRaycasted != null)
    452.                     {
    453.                         if ((_currentReorderableListRaycasted.Content.childCount >=
    454.                              _currentReorderableListRaycasted.maxItems &&
    455.                              !_currentReorderableListRaycasted.IsDisplacable)
    456.                             || _currentReorderableListRaycasted.maxItems <= 0)
    457.                         {
    458.                             GameObject o = _draggingObject.gameObject;
    459.                             _reorderableList.OnElementDroppedWithMaxItems.Invoke(
    460.                                 new ReorderableList.ReorderableListEventStruct
    461.                                 {
    462.                                     DroppedObject = o,
    463.                                     IsAClone = _reorderableList.CloneDraggedObject,
    464.                                     SourceObject = _reorderableList.CloneDraggedObject ? gameObject : o,
    465.                                     FromList = _reorderableList,
    466.                                     ToList = _currentReorderableListRaycasted,
    467.                                     FromIndex = _fromIndex
    468.                                 });
    469.                         }
    470.                     }
    471.                  
    472.                 }
    473.             }
    474.  
    475.             //Delete fake element
    476.             if (_fakeElement != null)
    477.             {
    478.                 Destroy(_fakeElement.gameObject);
    479.                 _fakeElement = null;
    480.             }
    481.             _canvasGroup.blocksRaycasts = true;
    482.         }
    483.  
    484.         #endregion
    485.  
    486.  
    487.         void CancelDrag()
    488.         {
    489.             _isDragging = false;
    490.             //If it's a clone, delete it
    491.             if (_reorderableList.CloneDraggedObject)
    492.             {
    493.                 Destroy(_draggingObject.gameObject);
    494.             }
    495.             //Else replace the draggedObject to his first place
    496.             else
    497.             {
    498.                 RefreshSizes();
    499.                 _draggingObject.SetParent(_reorderableList.Content, false);
    500.                 _draggingObject.rotation = _reorderableList.Content.transform.rotation;
    501.                 _draggingObject.SetSiblingIndex(_fromIndex);
    502.  
    503.  
    504.                 var args = new ReorderableList.ReorderableListEventStruct
    505.                 {
    506.                     DroppedObject = _draggingObject.gameObject,
    507.                     IsAClone = _reorderableList.CloneDraggedObject,
    508.                     SourceObject = _reorderableList.CloneDraggedObject ? gameObject : _draggingObject.gameObject,
    509.                     FromList = _reorderableList,
    510.                     FromIndex = _fromIndex,
    511.                     ToList = _reorderableList,
    512.                     ToIndex = _fromIndex
    513.                 };
    514.  
    515.                 _reorderableList.Refresh();
    516.  
    517.                 _reorderableList.OnElementAdded.Invoke(args);
    518.  
    519.                 if (!isValid)
    520.                     throw new Exception("Transfer is already Canceled.");
    521.  
    522.             }
    523.  
    524.             //Delete fake element
    525.             if (_fakeElement != null)
    526.             {
    527.                 Destroy(_fakeElement.gameObject);
    528.                 _fakeElement = null;
    529.             }
    530.             if (_displacedObject != null)
    531.             {
    532.                 revertDisplacedElement();
    533.             }
    534.             _canvasGroup.blocksRaycasts = true;
    535.         }
    536.  
    537.         private void RefreshSizes()
    538.         {
    539.             Vector2 size = _draggingObjectOriginalSize;
    540.  
    541.             if (_currentReorderableListRaycasted != null
    542.                 && _currentReorderableListRaycasted.IsDropable
    543.                 && _currentReorderableListRaycasted.Content.childCount > 0
    544.                 && _currentReorderableListRaycasted.EqualizeSizesOnDrag)
    545.             {
    546.                 var firstChild = _currentReorderableListRaycasted.Content.GetChild(0);
    547.                 if (firstChild != null)
    548.                 {
    549.                     size = firstChild.GetComponent<RectTransform>().rect.size;
    550.                 }
    551.             }
    552.  
    553.             _draggingObject.sizeDelta = size;
    554.             _fakeElementLE.preferredHeight = _draggingObjectLE.preferredHeight = size.y;
    555.             _fakeElementLE.preferredWidth = _draggingObjectLE.preferredWidth = size.x;
    556.             _fakeElement.GetComponent<RectTransform>().sizeDelta = size;
    557.         }
    558.  
    559.         public void Init(ReorderableList reorderableList)
    560.         {
    561.             _reorderableList = reorderableList;
    562.             _rect = GetComponent<RectTransform>();
    563.             _canvasGroup = gameObject.GetOrAddComponent<CanvasGroup>();
    564.         }
    565.     }
    566. }
    567.  
     
    travlake likes this.
  36. zacharyaghaizu

    zacharyaghaizu

    Joined:
    Aug 14, 2020
    Posts:
    65
    Hi, When I setup the Re-orderable Vertical ScrollRect, All the elements shrink each time they are released from dragging. Anyone know the solution?

    I tried loading it at runtime and it works well with Constant Pixel Size in Canvas Settings
     
    Last edited: Jul 3, 2021
  37. thecloudkeeper

    thecloudkeeper

    Joined:
    Sep 21, 2021
    Posts:
    28
    Cheers to you, Ziboo, and everyone else who may have had a hand in this. Thank you for sharing! This is incredible.
     
  38. betaFlux

    betaFlux

    Joined:
    Jan 7, 2013
    Posts:
    112
    Thank you so much for this great extension!

    I have found a little visual bug. Neighbour objects flicker for a split second when starting to drag an object. I've created a new project with the latest uiextensions pack and added a horizontal ReorderableList via GameObject menu and it's happening quite often. It doesn't interfere with the functionality but it looks glitchy.

    I looked at the "ReorderableListElement" script and I think it's because the neighbour objects reorder themselves during the frame when the fake object is not placed on the content panel yet, but the dragged object is gone at this point.

    Does anyone had this issue and solved it somehow, or does anyone have an idea what could be done?

    I'm using Unity 2022.3.31f1 with UIExtensions 2019.6
     
  39. thecloudkeeper

    thecloudkeeper

    Joined:
    Sep 21, 2021
    Posts:
    28
    I'm also experiencing this bug. I have not found a fix/workaround yet. I'm suspicious it is because I'm using it in conjunction with a ContentSizeFitter but I haven't tested this yet. If I find a solution, I'll definitely share here though.
     
  40. betaFlux

    betaFlux

    Joined:
    Jan 7, 2013
    Posts:
    112
    It happens in a fresh project with the default ReorderableList setup from Ziboo, which has a ContentSizeFitter. I have tried all possible combinations of settings on the fitter, LayoutGroup, LayoutElements, etc. to no avail. If I understood the ReorderableListElement script well enough I would fix it and post the script here, but I just can't wrap my head around it.
     
  41. thecloudkeeper

    thecloudkeeper

    Joined:
    Sep 21, 2021
    Posts:
    28
    Yeah, it's definitely not the most readable code.

    For some reason, removing the RectTransform that I had assigned to the
    ReorderableList.DraggableArea
    field did the trick for me. If it's null, it defaults to using the root Canvas RectTransform. I had assigned a parent UI element's RT. But as soon as I removed it, the glitchy behavior stopped.
     
  42. betaFlux

    betaFlux

    Joined:
    Jan 7, 2013
    Posts:
    112
    I'm glad you got it solved! Unfortunately after removing the DraggableArea from the field, it is still glitchy for me.
    The most depressing part is, that this happens out of the box, in my case at least.

    What I did:

    - Create new URP project
    - Import UIExtensions 2019-6 package via "Assets > Import Package > Custom Package..."
    - Create Screen Space Overlay Canvas
    - Add Reorderable Horizontal List template from "GameObject > UI > Extensions" menu
    - Hit Play

    The behaviour I observed:

    When dragging the first (green) square, no matter how many neighbour squares there are, there is no issue:
    dragok.jpg

    When dragging any other square but the first, all squares to the right will move one slot to the left and back, during the first OnDrag frame I guess, which looks like this when I drag the second (red) square:
    dragissue.jpg

    I guess I'll have another look at the ReorderableListElement code.

    Ideas are extremely welcome!
     
  43. atnicoconut

    atnicoconut

    Joined:
    Apr 8, 2022
    Posts:
    11
    I was also experiencing this problem, and I found a solution. It's probably not the best one, but it works.

    I've narrowed down the problem using Gizmos to the last "else" on the "OnDrag" function in ReoderableListElement. Every time the function is called, the object's parent gets recalculated and the smallest change in movement can trigger a reordering. It's a bit complicated and I don't understand it fully, but since we know the change is happening there and it doesn't need to happen that often, we can put a simple timer to prevent it from happening every frame.

    So, at the top, I created a bool called "canChange", and set it to true by default.
    In the said else function, I created a condition for that bool, like so:

    Code (CSharp):
    1. else
    2.             {
    3.                 if (canChange)
    4.                 {
    5.                     canChange = false;
    6.                     StartCoroutine(ChangeCountdown());
    Then the coroutine is simply
    Code (CSharp):
    1. private IEnumerator ChangeCountdown()
    2.         {
    3.             yield return new WaitForSeconds(.2f);
    4.  
    5.             canChange = true;
    6.         }
    You can tweak the delay to what feels best for you.
    Hope that helped!
     
  44. HD43

    HD43

    Joined:
    Jul 10, 2018
    Posts:
    8
    anyone know how to block drag element when scrolling the list
     
  45. mkr67n

    mkr67n

    Joined:
    Apr 20, 2020
    Posts:
    1
    I'm having the same problem. The reason is that the LayoutGroup is not updated immediately within the frame where the FakeElement is added.
    So the solution is to force rebuild layout while setting the parent of the FakeElement

    It's pretty simple. Find this code in OnDrag method of ReorderableListElement.cs
    Code (CSharp):
    1. if (_currentReorderableListRaycasted.Content.childCount < _currentReorderableListRaycasted.maxItems && _fakeElement.parent != _currentReorderableListRaycasted.Content)
    2. {
    3.     _fakeElement.SetParent(_currentReorderableListRaycasted.Content, false);
    4. }
    And change it into this one:
    Code (CSharp):
    1. if (_currentReorderableListRaycasted.Content.childCount < _currentReorderableListRaycasted.maxItems && _fakeElement.parent != _currentReorderableListRaycasted.Content)
    2. {
    3.     _fakeElement.SetParent(_currentReorderableListRaycasted.Content, false);
    4.     LayoutRebuilder.ForceRebuildLayoutImmediate(_currentReorderableListRaycasted.ContentLayout.transform as RectTransform); // Add this line to force rebuild layout
    5. }
     
    seancheno likes this.