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