Skip to content
Permalink
2019.4
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License
using UnityEngine;
using UnityEditor;
using System;
using System.Collections;
using System.Collections.Generic;
using Object = UnityEngine.Object;
namespace UnityEditorInternal
{
//TODO: better handling for serializedObjects with mixed values
//TODO: make it not rely on GUILayout at all, so its safe to use under PropertyDrawers.
public class ReorderableList
{
public delegate void HeaderCallbackDelegate(Rect rect);
public delegate void FooterCallbackDelegate(Rect rect);
public delegate void ElementCallbackDelegate(Rect rect, int index, bool isActive, bool isFocused);
public delegate float ElementHeightCallbackDelegate(int index);
public delegate void DrawNoneElementCallback(Rect rect);
public delegate void ReorderCallbackDelegateWithDetails(ReorderableList list, int oldIndex, int newIndex);
public delegate void ReorderCallbackDelegate(ReorderableList list);
public delegate void SelectCallbackDelegate(ReorderableList list);
public delegate void AddCallbackDelegate(ReorderableList list);
public delegate void AddDropdownCallbackDelegate(Rect buttonRect, ReorderableList list);
public delegate void RemoveCallbackDelegate(ReorderableList list);
public delegate void ChangedCallbackDelegate(ReorderableList list);
public delegate bool CanRemoveCallbackDelegate(ReorderableList list);
public delegate bool CanAddCallbackDelegate(ReorderableList list);
public delegate void DragCallbackDelegate(ReorderableList list);
// draw callbacks
public HeaderCallbackDelegate drawHeaderCallback;
public FooterCallbackDelegate drawFooterCallback = null;
public ElementCallbackDelegate drawElementCallback;
public ElementCallbackDelegate drawElementBackgroundCallback;
public DrawNoneElementCallback drawNoneElementCallback = null;
// layout callbacks
// if supplying own element heights, try to cache the results as this may be called frequently
public ElementHeightCallbackDelegate elementHeightCallback;
// interaction callbacks
public ReorderCallbackDelegateWithDetails onReorderCallbackWithDetails;
public ReorderCallbackDelegate onReorderCallback;
public SelectCallbackDelegate onSelectCallback;
public AddCallbackDelegate onAddCallback;
public AddDropdownCallbackDelegate onAddDropdownCallback;
public RemoveCallbackDelegate onRemoveCallback;
public DragCallbackDelegate onMouseDragCallback;
public SelectCallbackDelegate onMouseUpCallback;
public CanRemoveCallbackDelegate onCanRemoveCallback;
public CanAddCallbackDelegate onCanAddCallback;
public ChangedCallbackDelegate onChangedCallback;
private int m_ActiveElement = -1;
private float m_DragOffset = 0;
private GUISlideGroup m_SlideGroup;
private SerializedObject m_SerializedObject;
private SerializedProperty m_Elements;
private IList m_ElementList;
private bool m_Draggable;
private float m_DraggedY;
private bool m_Dragging;
private List<int> m_NonDragTargetIndices;
private bool m_DisplayHeader;
public bool displayAdd;
public bool displayRemove;
private int id = -1;
// class for default rendering and behavior of reorderable list - stores styles and is statically available as s_Defaults
public class Defaults
{
public GUIContent iconToolbarPlus = EditorGUIUtility.TrIconContent("Toolbar Plus", "Add to list");
public GUIContent iconToolbarPlusMore = EditorGUIUtility.TrIconContent("Toolbar Plus More", "Choose to add to list");
public GUIContent iconToolbarMinus = EditorGUIUtility.TrIconContent("Toolbar Minus", "Remove selection from list");
public readonly GUIStyle draggingHandle = "RL DragHandle";
public readonly GUIStyle headerBackground = "RL Header";
private readonly GUIStyle emptyHeaderBackground = "RL Empty Header";
public readonly GUIStyle footerBackground = "RL Footer";
public readonly GUIStyle boxBackground = "RL Background";
public readonly GUIStyle preButton = "RL FooterButton";
public readonly GUIStyle elementBackground = "RL Element";
public const int padding = 6;
public const int dragHandleWidth = 20;
private static GUIContent s_ListIsEmpty = EditorGUIUtility.TrTextContent("List is Empty");
// draw the default footer
public void DrawFooter(Rect rect, ReorderableList list)
{
float rightEdge = rect.xMax - 10f;
float leftEdge = rightEdge - 8f;
if (list.displayAdd)
leftEdge -= 25;
if (list.displayRemove)
leftEdge -= 25;
rect = new Rect(leftEdge, rect.y, rightEdge - leftEdge, rect.height);
Rect addRect = new Rect(leftEdge + 4, rect.y, 25, 16);
Rect removeRect = new Rect(rightEdge - 29, rect.y, 25, 16);
if (Event.current.type == EventType.Repaint)
{
footerBackground.Draw(rect, false, false, false, false);
}
if (list.displayAdd)
{
using (new EditorGUI.DisabledScope(
list.onCanAddCallback != null && !list.onCanAddCallback(list)))
{
if (GUI.Button(addRect, list.onAddDropdownCallback != null ? iconToolbarPlusMore : iconToolbarPlus, preButton))
{
if (list.onAddDropdownCallback != null)
list.onAddDropdownCallback(addRect, list);
else if (list.onAddCallback != null)
list.onAddCallback(list);
else
DoAddButton(list);
if (list.onChangedCallback != null)
list.onChangedCallback(list);
}
}
}
if (list.displayRemove)
{
using (new EditorGUI.DisabledScope(
list.index < 0 || list.index >= list.count ||
(list.onCanRemoveCallback != null && !list.onCanRemoveCallback(list))))
{
if (GUI.Button(removeRect, iconToolbarMinus, preButton))
{
if (list.onRemoveCallback == null)
DoRemoveButton(list);
else
list.onRemoveCallback(list);
if (list.onChangedCallback != null)
list.onChangedCallback(list);
}
}
}
}
// default add button behavior
public void DoAddButton(ReorderableList list)
{
if (list.serializedProperty != null)
{
list.serializedProperty.arraySize += 1;
list.index = list.serializedProperty.arraySize - 1;
}
else
{
// this is ugly but there are a lot of cases like null types and default constructors
Type elementType = list.list.GetType().GetElementType();
if (elementType == typeof(string))
list.index = list.list.Add("");
else if (elementType != null && elementType.GetConstructor(Type.EmptyTypes) == null)
Debug.LogError("Cannot add element. Type " + elementType.ToString() + " has no default constructor. Implement a default constructor or implement your own add behaviour.");
else if (list.list.GetType().GetGenericArguments()[0] != null)
list.index = list.list.Add(Activator.CreateInstance(list.list.GetType().GetGenericArguments()[0]));
else if (elementType != null)
list.index = list.list.Add(Activator.CreateInstance(elementType));
else
Debug.LogError("Cannot add element of type Null.");
}
}
// default remove button behavior
public void DoRemoveButton(ReorderableList list)
{
if (list.serializedProperty != null)
{
list.serializedProperty.DeleteArrayElementAtIndex(list.index);
if (list.index >= list.serializedProperty.arraySize - 1)
list.index = list.serializedProperty.arraySize - 1;
}
else
{
list.list.RemoveAt(list.index);
if (list.index >= list.list.Count - 1)
list.index = list.list.Count - 1;
}
}
// draw the default header background
public void DrawHeaderBackground(Rect headerRect)
{
if (Event.current.type == EventType.Repaint)
{
// We assume that a height smaller than 5px means a header with no content
if (headerRect.height < 5f)
{
emptyHeaderBackground.Draw(headerRect, false, false, false, false);
}
else
{
headerBackground.Draw(headerRect, false, false, false, false);
}
}
}
// draw the default header
public void DrawHeader(Rect headerRect, SerializedObject serializedObject, SerializedProperty element, IList elementList)
{
EditorGUI.LabelField(headerRect, EditorGUIUtility.TempContent((element != null) ? "Serialized Property" : "IList"));
}
// draw the default element background
public void DrawElementBackground(Rect rect, int index, bool selected, bool focused, bool draggable)
{
if (Event.current.type == EventType.Repaint)
{
elementBackground.Draw(rect, false, selected, selected, focused);
}
}
public void DrawElementDraggingHandle(Rect rect, int index, bool selected, bool focused, bool draggable)
{
if (Event.current.type == EventType.Repaint)
{
if (draggable)
draggingHandle.Draw(new Rect(rect.x + 5, rect.y + 8, 10, 6), false, false, false, false);
}
}
// draw the default element
public void DrawElement(Rect rect, SerializedProperty element, System.Object listItem, bool selected, bool focused, bool draggable)
{
EditorGUI.LabelField(rect, EditorGUIUtility.TempContent((element != null) ? element.displayName : listItem.ToString()));
}
// draw the default element
public void DrawNoneElement(Rect rect, bool draggable)
{
EditorGUI.LabelField(rect, Defaults.s_ListIsEmpty);
}
}
static Defaults s_Defaults;
public static Defaults defaultBehaviours
{
get
{
return s_Defaults;
}
}
// constructors
public ReorderableList(IList elements, Type elementType)
{
InitList(null, null, elements, true, true, true, true);
}
public ReorderableList(IList elements, Type elementType, bool draggable, bool displayHeader, bool displayAddButton, bool displayRemoveButton)
{
InitList(null, null, elements, draggable, displayHeader, displayAddButton, displayRemoveButton);
}
public ReorderableList(SerializedObject serializedObject, SerializedProperty elements)
{
InitList(serializedObject, elements, null, true, true, true, true);
}
public ReorderableList(SerializedObject serializedObject, SerializedProperty elements, bool draggable, bool displayHeader, bool displayAddButton, bool displayRemoveButton)
{
InitList(serializedObject, elements, null, draggable, displayHeader, displayAddButton, displayRemoveButton);
}
private void InitList(SerializedObject serializedObject, SerializedProperty elements, IList elementList, bool draggable, bool displayHeader, bool displayAddButton, bool displayRemoveButton)
{
id = GUIUtility.GetPermanentControlID();
m_SerializedObject = serializedObject;
m_Elements = elements;
m_ElementList = elementList;
m_Draggable = draggable;
m_Dragging = false;
m_SlideGroup = new GUISlideGroup();
displayAdd = displayAddButton;
m_DisplayHeader = displayHeader;
displayRemove = displayRemoveButton;
if (m_Elements != null && m_Elements.editable == false)
m_Draggable = false;
if (m_Elements != null && m_Elements.isArray == false)
Debug.LogError("Input elements should be an Array SerializedProperty");
}
public SerializedProperty serializedProperty
{
get { return m_Elements; }
set { m_Elements = value; }
}
public IList list
{
get { return m_ElementList; }
set { m_ElementList = value; }
}
// active element index accessor
public int index
{
get { return m_ActiveElement; }
set { m_ActiveElement = value; }
}
// individual element height accessor
public float elementHeight = 21;
// header height accessor
public float headerHeight = 20;
// footer height accessor
public float footerHeight = 20;
// show default background
public bool showDefaultBackground = true;
private float listElementTopPadding => headerHeight > 5 ? 4 : 1; // headerHeight is usually set to 3px when there is no header content. Therefore, we add a 1px top margin to match the 4px bottom margin
private const float kListElementBottomPadding = 4;
// draggable accessor
public bool draggable
{
get { return m_Draggable; }
set { m_Draggable = value; }
}
private Rect GetContentRect(Rect rect)
{
Rect r = rect;
if (draggable)
r.xMin += Defaults.dragHandleWidth;
else
r.xMin += Defaults.padding;
r.xMax -= Defaults.padding;
return r;
}
private float GetElementYOffset(int index)
{
return GetElementYOffset(index, -1);
}
private float GetElementYOffset(int index, int skipIndex)
{
if (elementHeightCallback == null)
return index * elementHeight;
float offset = 0;
for (int i = 0; i < index; i++)
{
if (i != skipIndex)
offset += elementHeightCallback(i);
}
return offset;
}
private float GetElementHeight(int index)
{
if (elementHeightCallback == null)
return elementHeight;
return elementHeightCallback(index);
}
private Rect GetRowRect(int index, Rect listRect)
{
return new Rect(listRect.x, listRect.y + GetElementYOffset(index), listRect.width, GetElementHeight(index));
}
public int count
{
get
{
if (m_Elements != null)
{
if (!m_Elements.hasMultipleDifferentValues)
return m_Elements.arraySize;
int smallerArraySize = m_Elements.arraySize;
foreach (Object targetObject in m_Elements.serializedObject.targetObjects)
{
SerializedObject serializedObject = new SerializedObject(targetObject);
SerializedProperty property = serializedObject.FindProperty(m_Elements.propertyPath);
smallerArraySize = Math.Min(property.arraySize, smallerArraySize);
}
return smallerArraySize;
}
return m_ElementList.Count;
}
}
public void DoLayoutList() //TODO: better API?
{
if (s_Defaults == null)
s_Defaults = new Defaults();
// do the custom or default header GUI
Rect headerRect = GUILayoutUtility.GetRect(0, headerHeight, GUILayout.ExpandWidth(true));
//Elements area
Rect listRect = GUILayoutUtility.GetRect(10, GetListElementHeight(), GUILayout.ExpandWidth(true));
// do the footer GUI
Rect footerRect = GUILayoutUtility.GetRect(4, footerHeight, GUILayout.ExpandWidth(true));
// do the parts of our list
DoListHeader(headerRect);
DoListElements(listRect);
DoListFooter(footerRect);
}
public void DoList(Rect rect) //TODO: better API?
{
if (s_Defaults == null)
s_Defaults = new Defaults();
// do the custom or default header GUI
Rect headerRect = new Rect(rect.x, rect.y, rect.width, headerHeight);
//Elements area
Rect listRect = new Rect(rect.x, headerRect.y + headerRect.height, rect.width, GetListElementHeight());
// do the footer GUI
Rect footerRect = new Rect(rect.x, listRect.y + listRect.height, rect.width, footerHeight);
// do the parts of our list
DoListHeader(headerRect);
DoListElements(listRect);
DoListFooter(footerRect);
}
public float GetHeight()
{
float totalheight = 0f;
totalheight += GetListElementHeight();
totalheight += headerHeight;
totalheight += footerHeight;
return totalheight;
}
private float GetListElementHeight()
{
float listElementPadding = kListElementBottomPadding + listElementTopPadding;
int arraySize = count;
if (arraySize == 0)
{
return elementHeight + listElementPadding;
}
if (elementHeightCallback != null)
{
return GetElementYOffset(arraySize - 1) + GetElementHeight(arraySize - 1) + listElementPadding;
}
return (elementHeight * arraySize) + listElementPadding;
}
private void DoListElements(Rect listRect)
{
// How many elements? If none, make space for showing default line that shows no elements are present
int arraySize = count;
// draw the background in repaint
if (showDefaultBackground && Event.current.type == EventType.Repaint)
s_Defaults.boxBackground.Draw(listRect, false, false, false, false);
// resize to the area that we want to draw our elements into
listRect.yMin += listElementTopPadding; listRect.yMax -= kListElementBottomPadding;
if (showDefaultBackground)
{
listRect.xMin += 1;
listRect.xMax -= 1;
}
// create the rect for individual elements in the list
Rect elementRect = listRect;
elementRect.height = elementHeight;
// the content rect is what we will actually draw into -- it doesn't include the drag handle or padding
Rect elementContentRect = elementRect;
if (((m_Elements != null && m_Elements.isArray == true) || m_ElementList != null) && arraySize > 0)
{
// If there are elements, we need to draw them -- we will do this differently depending on if we are dragging or not
if (IsDragging() && Event.current.type == EventType.Repaint)
{
// we are dragging, so we need to build the new list of target indices
int targetIndex = CalculateRowIndex();
m_NonDragTargetIndices.Clear();
for (int i = 0; i < arraySize; i++)
{
if (i != m_ActiveElement)
m_NonDragTargetIndices.Add(i);
}
m_NonDragTargetIndices.Insert(targetIndex, -1);
// now draw each element in the list (excluding the active element)
bool targetSeen = false;
for (int i = 0; i < m_NonDragTargetIndices.Count; i++)
{
if (m_NonDragTargetIndices[i] != -1)
{
elementRect.height = GetElementHeight(i);
// update the position of the rect (based on element position and accounting for sliding)
if (elementHeightCallback == null)
{
elementRect.y = listRect.y + GetElementYOffset(i, m_ActiveElement);
}
else
{
elementRect.y = listRect.y + GetElementYOffset(m_NonDragTargetIndices[i], m_ActiveElement);
if (targetSeen)
{
elementRect.y += elementHeightCallback(m_ActiveElement);
}
}
elementRect = m_SlideGroup.GetRect(m_NonDragTargetIndices[i], elementRect);
// actually draw the element
if (drawElementBackgroundCallback == null)
s_Defaults.DrawElementBackground(elementRect, i, false, false, m_Draggable);
else
drawElementBackgroundCallback(elementRect, i, false, false);
s_Defaults.DrawElementDraggingHandle(elementRect, i, false, false, m_Draggable);
elementContentRect = GetContentRect(elementRect);
if (drawElementCallback == null)
{
if (m_Elements != null)
s_Defaults.DrawElement(elementContentRect, m_Elements.GetArrayElementAtIndex(m_NonDragTargetIndices[i]), null, false, false, m_Draggable);
else
s_Defaults.DrawElement(elementContentRect, null, m_ElementList[m_NonDragTargetIndices[i]], false, false, m_Draggable);
}
else
{
drawElementCallback(elementContentRect, m_NonDragTargetIndices[i], false, false);
}
}
else
{
targetSeen = true;
}
}
// finally get the position of the active element
elementRect.y = m_DraggedY - m_DragOffset + listRect.y;
// actually draw the element
if (drawElementBackgroundCallback == null)
s_Defaults.DrawElementBackground(elementRect, m_ActiveElement, true, true, m_Draggable);
else
drawElementBackgroundCallback(elementRect, m_ActiveElement, true, true);
s_Defaults.DrawElementDraggingHandle(elementRect, m_ActiveElement, true, true, m_Draggable);
elementContentRect = GetContentRect(elementRect);
// draw the active element
if (drawElementCallback == null)
{
if (m_Elements != null)
s_Defaults.DrawElement(elementContentRect, m_Elements.GetArrayElementAtIndex(m_ActiveElement), null, true, true, m_Draggable);
else
s_Defaults.DrawElement(elementContentRect, null, m_ElementList[m_ActiveElement], true, true, m_Draggable);
}
else
{
drawElementCallback(elementContentRect, m_ActiveElement, true, true);
}
}
else
{
// if we aren't dragging, we just draw all of the elements in order
for (int i = 0; i < arraySize; i++)
{
bool activeElement = (i == m_ActiveElement);
bool focusedElement = (i == m_ActiveElement && HasKeyboardControl());
// update the position of the element
elementRect.height = GetElementHeight(i);
elementRect.y = listRect.y + GetElementYOffset(i);
// draw the background
if (drawElementBackgroundCallback == null)
s_Defaults.DrawElementBackground(elementRect, i, activeElement, focusedElement, m_Draggable);
else
drawElementBackgroundCallback(elementRect, i, activeElement, focusedElement);
s_Defaults.DrawElementDraggingHandle(elementRect, i, activeElement, focusedElement, m_Draggable);
elementContentRect = GetContentRect(elementRect);
// do the callback for the element
if (drawElementCallback == null)
{
if (m_Elements != null)
s_Defaults.DrawElement(elementContentRect, m_Elements.GetArrayElementAtIndex(i), null, activeElement, focusedElement, m_Draggable);
else
s_Defaults.DrawElement(elementContentRect, null, m_ElementList[i], activeElement, focusedElement, m_Draggable);
}
else
{
drawElementCallback(elementContentRect, i, activeElement, focusedElement);
}
}
}
// handle the interaction
DoDraggingAndSelection(listRect);
}
else
{
// there was no content, so we will draw an empty element
elementRect.y = listRect.y;
// draw the background
if (drawElementBackgroundCallback == null)
s_Defaults.DrawElementBackground(elementRect, -1, false, false, false);
else
drawElementBackgroundCallback(elementRect, -1, false, false);
s_Defaults.DrawElementDraggingHandle(elementRect, -1, false, false, false);
elementContentRect = elementRect;
elementContentRect.xMin += Defaults.padding;
elementContentRect.xMax -= Defaults.padding;
if (drawNoneElementCallback == null)
s_Defaults.DrawNoneElement(elementContentRect, m_Draggable);
else
drawNoneElementCallback(elementContentRect);
}
}
private void DoListHeader(Rect headerRect)
{
// draw the background on repaint
if (showDefaultBackground && Event.current.type == EventType.Repaint)
s_Defaults.DrawHeaderBackground(headerRect);
// apply the padding to get the internal rect
headerRect.xMin += Defaults.padding;
headerRect.xMax -= Defaults.padding;
headerRect.height -= 2;
headerRect.y += 1;
// perform the default or overridden callback
if (drawHeaderCallback != null)
drawHeaderCallback(headerRect);
else if (m_DisplayHeader)
s_Defaults.DrawHeader(headerRect, m_SerializedObject, m_Elements, m_ElementList);
}
private void DoListFooter(Rect footerRect)
{
// perform callback or the default footer
if (drawFooterCallback != null)
drawFooterCallback(footerRect);
else if (displayAdd || displayRemove)
s_Defaults.DrawFooter(footerRect, this); // draw the footer if the add or remove buttons are required
}
private void DoDraggingAndSelection(Rect listRect)
{
Event evt = Event.current;
int oldIndex = m_ActiveElement;
bool clicked = false;
switch (evt.GetTypeForControl(id))
{
case EventType.KeyDown:
if (GUIUtility.keyboardControl != id)
return;
// if we have keyboard focus, arrow through the list
if (evt.keyCode == KeyCode.DownArrow)
{
m_ActiveElement += 1;
evt.Use();
}
if (evt.keyCode == KeyCode.UpArrow)
{
m_ActiveElement -= 1;
evt.Use();
}
if (evt.keyCode == KeyCode.Escape && GUIUtility.hotControl == id)
{
GUIUtility.hotControl = 0;
m_Dragging = false;
evt.Use();
}
// don't allow arrowing through the ends of the list
m_ActiveElement = Mathf.Clamp(m_ActiveElement, 0, (m_Elements != null) ? m_Elements.arraySize - 1 : m_ElementList.Count - 1);
break;
case EventType.MouseDown:
if (!listRect.Contains(Event.current.mousePosition) || (Event.current.button != 0 && Event.current.button != 1))
break;
// clicking on the list should end editing any existing edits
EditorGUI.EndEditingActiveTextField();
// pick the active element based on click position
m_ActiveElement = GetRowIndex(Event.current.mousePosition.y - listRect.y);
if (m_Draggable && Event.current.button == 0)
{
// if we can drag, set the hot control and start dragging (storing the offset)
m_DragOffset = (Event.current.mousePosition.y - listRect.y) - GetElementYOffset(m_ActiveElement);
UpdateDraggedY(listRect);
GUIUtility.hotControl = id;
m_SlideGroup.Reset();
m_NonDragTargetIndices = new List<int>();
}
GrabKeyboardFocus();
// Prevent consuming the right mouse event in order to enable the context menu
if (Event.current.button == 1)
break;
evt.Use();
clicked = true;
break;
case EventType.MouseDrag:
if (!m_Draggable || GUIUtility.hotControl != id)
break;
// Set m_Dragging state on first MouseDrag event after we got hotcontrol (to prevent animating elements when deleting elements by context menu)
m_Dragging = true;
if (onMouseDragCallback != null)
onMouseDragCallback(this);
// if we are dragging, update the position
UpdateDraggedY(listRect);
evt.Use();
break;
case EventType.MouseUp:
if (!m_Draggable)
{
// if mouse up was on the same index as mouse down we fire a mouse up callback (useful if for beginning renaming on mouseup)
if (onMouseUpCallback != null && IsMouseInsideActiveElement(listRect))
{
// set the keyboard control
onMouseUpCallback(this);
}
break;
}
// hotcontrol is only set when list is draggable
if (GUIUtility.hotControl != id)
break;
evt.Use();
m_Dragging = false;
try
{
// What will be the index of this if we release?
int targetIndex = CalculateRowIndex();
if (m_ActiveElement != targetIndex)
{
// if the target index is different than the current index...
if (m_SerializedObject != null && m_Elements != null)
{
// if we are working with Serialized Properties, we can handle it for you
m_Elements.MoveArrayElement(m_ActiveElement, targetIndex);
m_SerializedObject.ApplyModifiedProperties();
m_SerializedObject.Update();
}
else if (m_ElementList != null)
{
// we are working with the IList, which is probably of a fixed length
System.Object tempObject = m_ElementList[m_ActiveElement];
for (int i = 0; i < m_ElementList.Count - 1; i++)
{
if (i >= m_ActiveElement)
m_ElementList[i] = m_ElementList[i + 1];
}
for (int i = m_ElementList.Count - 1; i > 0; i--)
{
if (i > targetIndex)
m_ElementList[i] = m_ElementList[i - 1];
}
m_ElementList[targetIndex] = tempObject;
}
var oldActiveElement = m_ActiveElement;
var newActiveElement = targetIndex;
// update the active element, now that we've moved it
m_ActiveElement = targetIndex;
// give the user a callback
if (onReorderCallbackWithDetails != null)
onReorderCallbackWithDetails(this, oldActiveElement, newActiveElement);
else if (onReorderCallback != null)
onReorderCallback(this);
if (onChangedCallback != null)
onChangedCallback(this);
}
else
{
// if mouse up was on the same index as mouse down we fire a mouse up callback (useful if for beginning renaming on mouseup)
if (onMouseUpCallback != null)
onMouseUpCallback(this);
}
}
finally
{
// It's quite possible a call to EndGUI was made in one of our callbacks
// (and thus an ExitGUIException thrown). We still need to cleanup before
// we exitGUI proper.
GUIUtility.hotControl = 0;
m_NonDragTargetIndices = null;
}
break;
}
// if the index has changed and there is a selected callback, call it
if ((m_ActiveElement != oldIndex || clicked) && onSelectCallback != null)
onSelectCallback(this);
}
bool IsMouseInsideActiveElement(Rect listRect)
{
int mouseRowIndex = GetRowIndex(Event.current.mousePosition.y - listRect.y);
return mouseRowIndex == m_ActiveElement && GetRowRect(mouseRowIndex, listRect).Contains(Event.current.mousePosition);
}
private void UpdateDraggedY(Rect listRect)
{
m_DraggedY = Mathf.Clamp(Event.current.mousePosition.y - listRect.y, m_DragOffset, listRect.height - (GetElementHeight(m_ActiveElement) - m_DragOffset));
}
private int CalculateRowIndex()
{
return GetRowIndex(m_DraggedY);
}
private int GetRowIndex(float localY)
{
if (elementHeightCallback == null)
return Mathf.Clamp(Mathf.FloorToInt(localY / elementHeight), 0, count - 1);
float rowYOffset = 0;
for (int i = 0; i < count; i++)
{
float rowYHeight = elementHeightCallback(i);
float rowYEnd = rowYOffset + rowYHeight;
if (localY >= rowYOffset && localY < rowYEnd)
{
return i;
}
rowYOffset += rowYHeight;
}
return count - 1;
}
private bool IsDragging()
{
return m_Dragging;
}
public void GrabKeyboardFocus()
{
GUIUtility.keyboardControl = id;
}
public void ReleaseKeyboardFocus()
{
if (GUIUtility.keyboardControl == id)
{
GUIUtility.keyboardControl = 0;
}
}
public bool HasKeyboardControl()
{
return GUIUtility.keyboardControl == id && EditorGUIUtility.HasCurrentWindowKeyFocus();
}
}
}