// Copyright (c) 2012-2013 Rotorz Limited. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using InControl.ReorderableList.Internal;


namespace InControl.ReorderableList
{

	/// <summary>
	/// Arguments which are passed to <see cref="ItemInsertedEventHandler"/>.
	/// </summary>
	public sealed class ItemInsertedEventArgs : EventArgs
	{

		/// <summary>
		/// Gets adaptor to reorderable list container which contains element.
		/// </summary>
		public IReorderableListAdaptor adaptor { get; private set; }
		/// <summary>
		/// Gets zero-based index of item which was inserted.
		/// </summary>
		public int itemIndex { get; private set; }

		/// <summary>
		/// Indicates if inserted item was duplicated from another item.
		/// </summary>
		public bool wasDuplicated { get; private set; }

		/// <summary>
		/// Initializes a new instance of <see cref="ItemInsertedEventArgs"/>.
		/// </summary>
		/// <param name="adaptor">Reorderable list adaptor.</param>
		/// <param name="itemIndex">Zero-based index of item.</param>
		/// <param name="wasDuplicated">Indicates if inserted item was duplicated from another item.</param>
		public ItemInsertedEventArgs( IReorderableListAdaptor adaptor, int itemIndex, bool wasDuplicated )
		{
			this.adaptor = adaptor;
			this.itemIndex = itemIndex;
			this.wasDuplicated = wasDuplicated;
		}

	}

	/// <summary>
	/// An event handler which is invoked after new list item is inserted.
	/// </summary>
	/// <param name="sender">Object which raised event.</param>
	/// <param name="args">Event arguments.</param>
	public delegate void ItemInsertedEventHandler( object sender, ItemInsertedEventArgs args );

	/// <summary>
	/// Arguments which are passed to <see cref="ItemRemovingEventHandler"/>.
	/// </summary>
	public sealed class ItemRemovingEventArgs : CancelEventArgs
	{

		/// <summary>
		/// Gets adaptor to reorderable list container which contains element.
		/// </summary>
		public IReorderableListAdaptor adaptor { get; private set; }
		/// <summary>
		/// Gets zero-based index of item which was inserted.
		/// </summary>
		public int itemIndex { get; internal set; }

		/// <summary>
		/// Initializes a new instance of <see cref="ItemInsertedEventArgs"/>.
		/// </summary>
		/// <param name="adaptor">Reorderable list adaptor.</param>
		/// <param name="itemIndex">Zero-based index of item.</param>
		public ItemRemovingEventArgs( IReorderableListAdaptor adaptor, int itemIndex )
		{
			this.adaptor = adaptor;
			this.itemIndex = itemIndex;
		}

	}

	/// <summary>
	/// An event handler which is invoked before a list item is removed.
	/// </summary>
	/// <remarks>
	/// <para>Item removal can be cancelled by setting <see cref="CancelEventArgs.Cancel"/>
	/// to <c>true</c>.</para>
	/// </remarks>
	/// <param name="sender">Object which raised event.</param>
	/// <param name="args">Event arguments.</param>
	public delegate void ItemRemovingEventHandler( object sender, ItemRemovingEventArgs args );

	/// <summary>
	/// Base class for custom reorderable list control.
	/// </summary>
	[Serializable]
	public class ReorderableListControl
	{

		/// <summary>
		/// Invoked to draw list item.
		/// </summary>
		/// <remarks>
		/// <para>GUI controls must be positioned absolutely within the given rectangle since
		/// list items must be sized consistently.</para>
		/// </remarks>
		/// <example>
		/// <para>The following listing presents a text field for each list item:</para>
		/// <code language="csharp"><![CDATA[
		/// using UnityEngine;
		/// using UnityEditor;
		///
		/// using System.Collections.Generic;
		///
		/// public class ExampleWindow : EditorWindow {
		///     public List<string> wishlist = new List<string>();
		///
		///     private void OnGUI() {
		///         ReorderableListGUI.ListField(wishlist, DrawListItem);
		///     }
		///
		///     private string DrawListItem(Rect position, string value) {
		///         // Text fields do not like `null` values!
		///         if (value == null)
		///             value = "";
		///         return EditorGUI.TextField(position, value);
		///     }
		/// }
		/// ]]></code>
		/// <code language="unityscript"><![CDATA[
		/// import System.Collections.Generic;
		///
		/// class ExampleWindow extends EditorWindow {
		///     var wishlist:List.<String>;
		///
		///     function OnGUI() {
		///         ReorderableListGUI.ListField(wishlist, DrawListItem);
		///     }
		///
		///     function DrawListItem(position:Rect, value:String):String {
		///         // Text fields do not like `null` values!
		///         if (value == null)
		///             value = '';
		///         return EditorGUI.TextField(position, value);
		///     }
		/// }
		/// ]]></code>
		/// </example>
		/// <typeparam name="T">Type of item list.</typeparam>
		/// <param name="position">Position of list item.</param>
		/// <param name="item">The list item.</param>
		/// <returns>
		/// The modified value.
		/// </returns>
		public delegate T ItemDrawer<T>( Rect position, T item );

		/// <summary>
		/// Invoked to draw content for empty list.
		/// </summary>
		/// <remarks>
		/// <para>Callback should make use of <c>GUILayout</c> to present controls.</para>
		/// </remarks>
		/// <example>
		/// <para>The following listing displays a label for empty list control:</para>
		/// <code language="csharp"><![CDATA[
		/// using UnityEngine;
		/// using UnityEditor;
		///
		/// using System.Collections.Generic;
		///
		/// public class ExampleWindow : EditorWindow {
		///     private List<string> _list;
		///
		///     private void OnEnable() {
		///         _list = new List<string>();
		///     }
		///     private void OnGUI() {
		///         ReorderableListGUI.ListField(_list, ReorderableListGUI.TextFieldItemDrawer, DrawEmptyMessage);
		///     }
		///
		///     private string DrawEmptyMessage() {
		///         GUILayout.Label("List is empty!", EditorStyles.miniLabel);
		///     }
		/// }
		/// ]]></code>
		/// <code language="unityscript"><![CDATA[
		/// import System.Collections.Generic;
		///
		/// class ExampleWindow extends EditorWindow {
		///     private var _list:List.<String>;
		///
		///     function OnEnable() {
		///         _list = new List.<String>();
		///     }
		///     function OnGUI() {
		///         ReorderableListGUI.ListField(_list, ReorderableListGUI.TextFieldItemDrawer, DrawEmptyMessage);
		///     }
		///
		///     function DrawEmptyMessage() {
		///         GUILayout.Label('List is empty!', EditorStyles.miniLabel);
		///     }
		/// }
		/// ]]></code>
		/// </example>
		public delegate void DrawEmpty();
		/// <summary>
		/// Invoked to draw content for empty list with absolute positioning.
		/// </summary>
		/// <param name="position">Position of empty content.</param>
		public delegate void DrawEmptyAbsolute( Rect position );

		#region Custom Styles

		/// <summary>
		/// Background color of anchor list item.
		/// </summary>
		public static readonly Color AnchorBackgroundColor;
		/// <summary>
		/// Background color of target slot when dragging list item.
		/// </summary>
		public static readonly Color TargetBackgroundColor;

		/// <summary>
		/// Style for right-aligned label for element number prefix.
		/// </summary>
		private static GUIStyle s_RightAlignedLabelStyle;

		private static GUIContent s_RemoveButtonNormalContent;
		private static GUIContent s_RemoveButtonActiveContent;

		static ReorderableListControl()
		{
			s_CurrentItemIndex = new Stack<int>();
			s_CurrentItemIndex.Push( -1 );

			if (EditorGUIUtility.isProSkin)
			{
				AnchorBackgroundColor = new Color( 85f / 255f, 85f / 255f, 85f / 255f, 0.85f );
				TargetBackgroundColor = new Color( 0, 0, 0, 0.5f );
			}
			else
			{
				AnchorBackgroundColor = new Color( 225f / 255f, 225f / 255f, 225f / 255f, 0.85f );
				TargetBackgroundColor = new Color( 0, 0, 0, 0.5f );
			}

			s_RemoveButtonNormalContent = new GUIContent( ReorderableListResources.texRemoveButton );
			s_RemoveButtonActiveContent = new GUIContent( ReorderableListResources.texRemoveButtonActive );
		}

		#endregion

		#region Utility

		/// <summary>
		/// Generate and draw control from state object.
		/// </summary>
		/// <param name="adaptor">Reorderable list adaptor.</param>
		/// <param name="drawEmpty">Delegate for drawing empty list.</param>
		/// <param name="flags">Optional flags to pass into list field.</param>
		public static void DrawControlFromState( IReorderableListAdaptor adaptor, DrawEmpty drawEmpty, ReorderableListFlags flags )
		{
			int controlID = GUIUtility.GetControlID( FocusType.Passive );

			var control = GUIUtility.GetStateObject( typeof(ReorderableListControl), controlID ) as ReorderableListControl;
			control.flags = flags;
			control.Draw( controlID, adaptor, drawEmpty );
		}

		/// <summary>
		/// Generate and draw control from state object.
		/// </summary>
		/// <param name="position">Position of control.</param>
		/// <param name="adaptor">Reorderable list adaptor.</param>
		/// <param name="drawEmpty">Delegate for drawing empty list.</param>
		/// <param name="flags">Optional flags to pass into list field.</param>
		public static void DrawControlFromState( Rect position, IReorderableListAdaptor adaptor, DrawEmptyAbsolute drawEmpty, ReorderableListFlags flags )
		{
			int controlID = GUIUtility.GetControlID( FocusType.Passive );

			var control = GUIUtility.GetStateObject( typeof(ReorderableListControl), controlID ) as ReorderableListControl;
			control.flags = flags;
			control.Draw( position, controlID, adaptor, drawEmpty );
		}

		#endregion

		/// <summary>
		/// Position of mouse upon anchoring item for drag.
		/// </summary>
		private static float s_AnchorMouseOffset;
		/// <summary>
		/// Zero-based index of anchored list item.
		/// </summary>
		private static int s_AnchorIndex = -1;
		/// <summary>
		/// Zero-based index of target list item for reordering.
		/// </summary>
		private static int s_TargetIndex = -1;

		/// <summary>
		/// Unique ID of list control which should be automatically focused. A value
		/// of zero indicates that no control is to be focused.
		/// </summary>
		private static int s_AutoFocusControlID = 0;
		/// <summary>
		/// Zero-based index of item which should be focused.
		/// </summary>
		private static int s_AutoFocusIndex = -1;

		/// <summary>
		/// Zero-based index of list item which is currently being drawn.
		/// </summary>
		private static Stack<int> s_CurrentItemIndex;

		/// <summary>
		/// Gets zero-based index of list item which is currently being drawn;
		/// or a value of -1 if no item is currently being drawn.
		/// </summary>
		/// <remarks>
		/// <para>Use <see cref="ReorderableListGUI.currentItemIndex"/> instead.</para>
		/// </remarks>
		internal static int currentItemIndex
		{
			get { return s_CurrentItemIndex.Peek(); }
		}

		#region Properties

		[SerializeField]
		private ReorderableListFlags
			_flags;

		/// <summary>
		/// Gets or sets flags which affect behavior of control.
		/// </summary>
		public ReorderableListFlags flags
		{
			get { return _flags; }
			set { _flags = value; }
		}

		/// <summary>
		/// Gets a value indicating whether add button is shown.
		/// </summary>
		private bool hasAddButton
		{
			get { return (_flags & ReorderableListFlags.HideAddButton) == 0; }
		}
		/// <summary>
		/// Gets a value indicating whether remove buttons are shown.
		/// </summary>
		private bool hasRemoveButtons
		{
			get { return (_flags & ReorderableListFlags.HideRemoveButtons) == 0; }
		}

		[SerializeField]
		private GUIStyle
			_containerStyle;
		[SerializeField]
		private GUIStyle
			_addButtonStyle;
		[SerializeField]
		private GUIStyle
			_removeButtonStyle;

		/// <summary>
		/// Gets or sets style used to draw background of list control.
		/// </summary>
		/// <seealso cref="ReorderableListGUI.defaultContainerStyle"/>
		public GUIStyle containerStyle
		{
			get { return _containerStyle; }
			set { _containerStyle = value; }
		}
		/// <summary>
		/// Gets or sets style used to draw add button.
		/// </summary>
		/// <seealso cref="ReorderableListGUI.defaultAddButtonStyle"/>
		public GUIStyle addButtonStyle
		{
			get { return _addButtonStyle; }
			set { _addButtonStyle = value; }
		}
		/// <summary>
		/// Gets or sets style used to draw remove button.
		/// </summary>
		/// <seealso cref="ReorderableListGUI.defaultRemoveButtonStyle"/>
		public GUIStyle removeButtonStyle
		{
			get { return _removeButtonStyle; }
			set { _removeButtonStyle = value; }
		}

		#endregion

		#region Events

		/// <summary>
		/// Occurs after list item is inserted or duplicated.
		/// </summary>
		public event ItemInsertedEventHandler ItemInserted;

		/// <summary>
		/// Raises event after list item is inserted or duplicated.
		/// </summary>
		/// <param name="args">Event arguments.</param>
		protected virtual void OnItemInserted( ItemInsertedEventArgs args )
		{
			if (ItemInserted != null)
				ItemInserted( this, args );
		}

		/// <summary>
		/// Occurs before list item is removed and allows removal to be cancelled.
		/// </summary>
		public event ItemRemovingEventHandler ItemRemoving;

		/// <summary>
		/// Raises event before list item is removed and provides oppertunity to cancel.
		/// </summary>
		/// <param name="args">Event arguments.</param>
		protected virtual void OnItemRemoving( ItemRemovingEventArgs args )
		{
			if (ItemRemoving != null)
				ItemRemoving( this, args );
		}

		#endregion

		#region Construction

		/// <summary>
		/// Initializes a new instance of <see cref="ReorderableListControl"/>.
		/// </summary>
		public ReorderableListControl()
		{
			_containerStyle = ReorderableListGUI.defaultContainerStyle;
			_addButtonStyle = ReorderableListGUI.defaultAddButtonStyle;
			_removeButtonStyle = ReorderableListGUI.defaultRemoveButtonStyle;
		}

		/// <summary>
		/// Initializes a new instance of <see cref="ReorderableListControl"/>.
		/// </summary>
		/// <param name="flags">Optional flags which affect behavior of control.</param>
		public ReorderableListControl( ReorderableListFlags flags )
			: this()
		{
			this.flags = flags;
		}

		#endregion

		#region Control State

		/// <summary>
		/// Unique Id of control.
		/// </summary>
		private int _controlID;
		/// <summary>
		/// Visible rectangle of control.
		/// </summary>
		private Rect _visibleRect;
		/// <summary>
		/// Width of index label in pixels (zero indicates no label).
		/// </summary>
		private float _indexLabelWidth;
		/// <summary>
		/// Indicates whether item is currently being dragged within control.
		/// </summary>
		private bool _tracking;
		/// <summary>
		/// Indicates if reordering is allowed.
		/// </summary>
		private bool _allowReordering;

		/// <summary>
		/// Prepare initial state for list control.
		/// </summary>
		/// <param name="controlID">Unique ID of list control.</param>
		/// <param name="adaptor">Reorderable list adaptor.</param>
		private void PrepareState( int controlID, IReorderableListAdaptor adaptor )
		{
			_controlID = controlID;
			_visibleRect = Internal.GUIHelper.VisibleRect();

			if ((flags & ReorderableListFlags.ShowIndices) != 0)
			{
				int digitCount = Mathf.Max( 2, Mathf.CeilToInt( Mathf.Log10( (float) adaptor.Count ) ) );
				_indexLabelWidth = digitCount * 8 + 8;
			}
			else
			{
				_indexLabelWidth = 0;
			}

			_tracking = IsTrackingControl( controlID );

			_allowReordering = (flags & ReorderableListFlags.DisableReordering) == 0;
		}

		#endregion

		#region Event Handling

		// Keep track of previously known mouse position (in screen space).
		private static Vector2 s_MousePosition;

		/// <summary>
		/// Indicate that first control of list item should be automatically focused
		/// if possible.
		/// </summary>
		/// <param name="controlID">Unique ID of list control.</param>
		/// <param name="itemIndex">Zero-based index of list item.</param>
		private void AutoFocusItem( int controlID, int itemIndex )
		{
			if ((flags & ReorderableListFlags.DisableAutoFocus) == 0)
			{
				s_AutoFocusControlID = controlID;
				s_AutoFocusIndex = itemIndex;
			}
		}

		/// <summary>
		/// Draw add item button.
		/// </summary>
		/// <param name="position">Position of button.</param>
		/// <param name="controlID">Unique ID of list control.</param>
		/// <param name="adaptor">Reorderable list adaptor.</param>
		private void DoAddButton( Rect position, int controlID, IReorderableListAdaptor adaptor )
		{
			if (GUI.Button( position, GUIContent.none, addButtonStyle ))
			{
				// Append item to list.
				GUIUtility.keyboardControl = 0;
				AddItem( adaptor );
			}
		}

		/// <summary>
		/// Draw remove button.
		/// </summary>
		/// <param name="position">Position of button.</param>
		/// <param name="visible">Indicates if control is visible within GUI.</param>
		/// <returns>
		/// A value of <c>true</c> if clicked; otherwise <c>false</c>.
		/// </returns>
		private bool DoRemoveButton( Rect position, bool visible )
		{
			int controlID = GUIUtility.GetControlID( FocusType.Passive );
			Vector2 mousePosition = GUIUtility.ScreenToGUIPoint( s_MousePosition );

			switch (Event.current.GetTypeForControl( controlID ))
			{
				case EventType.MouseDown:
					// Do not allow button to be pressed using right mouse button since
					// context menu should be shown instead!
					if (GUI.enabled && Event.current.button != 1 && position.Contains( mousePosition ))
					{
						GUIUtility.hotControl = controlID;
						GUIUtility.keyboardControl = 0;
						Event.current.Use();
					}
					break;

				case EventType.MouseDrag:
					if (GUIUtility.hotControl == controlID)
						Event.current.Use();
					break;

				case EventType.MouseUp:
					if (GUIUtility.hotControl == controlID)
					{
						GUIUtility.hotControl = 0;

						if (position.Contains( mousePosition ))
						{
							Event.current.Use();
							return true;
						}
						else
						{
							Event.current.Use();
							return false;
						}
					}
					break;

				case EventType.Repaint:
					if (visible)
					{
						var content = (GUIUtility.hotControl == controlID && position.Contains( mousePosition ))
							? s_RemoveButtonActiveContent
							: s_RemoveButtonNormalContent;
						removeButtonStyle.Draw( position, content, controlID );
					}
					break;
			}

			return false;
		}

		private static bool s_TrackingCancelBlockContext;

		/// <summary>
		/// Begin tracking drag and drop within list.
		/// </summary>
		/// <param name="controlID">Unique ID of list control.</param>
		/// <param name="itemIndex">Zero-based index of item which is going to be dragged.</param>
		private static void BeginTrackingReorderDrag( int controlID, int itemIndex )
		{
			GUIUtility.hotControl = controlID;
			GUIUtility.keyboardControl = 0;
			s_AnchorIndex = itemIndex;
			s_TargetIndex = itemIndex;
			s_TrackingCancelBlockContext = false;
		}

		/// <summary>
		/// Stop tracking drag and drop.
		/// </summary>
		private static void StopTrackingReorderDrag()
		{
			GUIUtility.hotControl = 0;
			s_AnchorIndex = -1;
			s_TargetIndex = -1;
		}

		/// <summary>
		/// Gets a value indicating whether item in current list is currently being tracked.
		/// </summary>
		/// <param name="controlID">Unique ID of list control.</param>
		/// <returns>
		/// A value of <c>true</c> if item is being tracked; otherwise <c>false</c>.
		/// </returns>
		private static bool IsTrackingControl( int controlID )
		{
			return !s_TrackingCancelBlockContext && GUIUtility.hotControl == controlID;
		}

		/// <summary>
		/// Accept reordering.
		/// </summary>
		/// <param name="adaptor">Reorderable list adaptor.</param>
		private void AcceptReorderDrag( IReorderableListAdaptor adaptor )
		{
			try
			{
				// Reorder list as needed!
				s_TargetIndex = Mathf.Clamp( s_TargetIndex, 0, adaptor.Count + 1 );
				if (s_TargetIndex != s_AnchorIndex && s_TargetIndex != s_AnchorIndex + 1)
					MoveItem( adaptor, s_AnchorIndex, s_TargetIndex );
			}
			finally
			{
				StopTrackingReorderDrag();
			}
		}

		private static Rect s_DragItemPosition;

		// Micro-optimisation to avoid repeated construction.
		private static Rect s_RemoveButtonPosition;

		private void DrawListItem( EventType eventType, Rect position, IReorderableListAdaptor adaptor, int itemIndex )
		{
			bool visible = (position.y < _visibleRect.yMax && position.yMax > _visibleRect.y);
			bool draggable = _allowReordering && adaptor.CanDrag( itemIndex );

			Rect itemContentPosition = position;
			itemContentPosition.x = position.x + 2;
			itemContentPosition.y += 1;
			itemContentPosition.width = position.width - 4;
			itemContentPosition.height = position.height - 4;

			// Make space for grab handle?
			if (draggable)
			{
				itemContentPosition.x += 20;
				itemContentPosition.width -= 20;
			}

			// Make space for element index.
			if (_indexLabelWidth != 0)
			{
				itemContentPosition.width -= _indexLabelWidth;

				if (eventType == EventType.Repaint && visible)
					s_RightAlignedLabelStyle.Draw( new Rect( itemContentPosition.x, position.y, _indexLabelWidth, position.height - 4 ), itemIndex + ":", false, false, false, false );

				itemContentPosition.x += _indexLabelWidth;
			}

			// Make space for remove button?
			if (hasRemoveButtons)
				itemContentPosition.width -= removeButtonStyle.fixedWidth;

			if (eventType == EventType.Repaint && visible)
			{
				// Draw grab handle?
				if (draggable)
					GUI.DrawTexture( new Rect( position.x + 6, position.y + position.height / 2f - 3, 9, 5 ), ReorderableListResources.texGrabHandle );

				// Draw splitter between list items.
				if (!_tracking || itemIndex != s_AnchorIndex)
					GUI.DrawTexture( new Rect( position.x, position.y - 1, position.width, 1 ), ReorderableListResources.texItemSplitter );
			}

			// Allow control to be automatically focused.
			if (s_AutoFocusIndex == itemIndex)
				GUI.SetNextControlName( "AutoFocus_" + _controlID + "_" + itemIndex );

			try
			{
				s_CurrentItemIndex.Push( itemIndex );

				// Present actual control.
				EditorGUI.BeginChangeCheck();
				adaptor.DrawItem( itemContentPosition, itemIndex );
				if (EditorGUI.EndChangeCheck())
					ReorderableListGUI.indexOfChangedItem = itemIndex;

				// Draw remove button?
				if (hasRemoveButtons && adaptor.CanRemove( itemIndex ))
				{
					s_RemoveButtonPosition = position;
					s_RemoveButtonPosition.width = removeButtonStyle.fixedWidth;
					s_RemoveButtonPosition.x = itemContentPosition.xMax + 2;
					s_RemoveButtonPosition.height -= 2;

					if (DoRemoveButton( s_RemoveButtonPosition, visible ))
						RemoveItem( adaptor, itemIndex );
				}

				// Check for context click?
				if (eventType == EventType.ContextClick && position.Contains( Event.current.mousePosition ) && (flags & ReorderableListFlags.DisableContextMenu) == 0)
				{
					ShowContextMenu( _controlID, itemIndex, adaptor );
					Event.current.Use();
				}
			}
			finally
			{
				s_CurrentItemIndex.Pop();
			}
		}

		private void DrawFloatingListItem( EventType eventType, IReorderableListAdaptor adaptor, float targetSlotPosition )
		{
			if (eventType == EventType.Repaint)
			{
				Color restoreColor = GUI.color;

				// Fill background of target area.
				Rect targetPosition = s_DragItemPosition;
				targetPosition.y = targetSlotPosition - 1;
				targetPosition.height = 1;

				GUI.DrawTexture( targetPosition, ReorderableListResources.texItemSplitter );

				--targetPosition.x;
				++targetPosition.y;
				targetPosition.width += 2;
				targetPosition.height = s_DragItemPosition.height - 1;

				GUI.color = TargetBackgroundColor;
				GUI.DrawTexture( targetPosition, EditorGUIUtility.whiteTexture );

				// Fill background of item which is being dragged.
				--s_DragItemPosition.x;
				s_DragItemPosition.width += 2;
				--s_DragItemPosition.height;

				GUI.color = AnchorBackgroundColor;
				GUI.DrawTexture( s_DragItemPosition, EditorGUIUtility.whiteTexture );

				++s_DragItemPosition.x;
				s_DragItemPosition.width -= 2;
				++s_DragItemPosition.height;

				// Draw horizontal splitter above and below.
				GUI.color = new Color( 0f, 0f, 0f, 0.6f );
				targetPosition.y = s_DragItemPosition.y - 1;
				targetPosition.height = 1;
				GUI.DrawTexture( targetPosition, EditorGUIUtility.whiteTexture );

				targetPosition.y += s_DragItemPosition.height;
				GUI.DrawTexture( targetPosition, EditorGUIUtility.whiteTexture );

				GUI.color = restoreColor;
			}

			DrawListItem( eventType, s_DragItemPosition, adaptor, s_AnchorIndex );
		}

		/// <summary>
		/// Draw list container and items.
		/// </summary>
		/// <param name="position">Position of list control in GUI.</param>
		/// <param name="controlID">Unique ID of list control.</param>
		/// <param name="adaptor">Reorderable list adaptor.</param>
		private void DrawListContainerAndItems( Rect position, int controlID, IReorderableListAdaptor adaptor )
		{
			// Get local copy of event information for efficiency.
			EventType eventType = Event.current.GetTypeForControl( controlID );
			Vector2 mousePosition = Event.current.mousePosition;
			if (Event.current.isMouse)
				s_MousePosition = GUIUtility.GUIToScreenPoint( mousePosition );

			int newTargetIndex = s_TargetIndex;

			// Position of first item in list.
			float firstItemY = position.y + containerStyle.padding.top;

			switch (eventType)
			{
				case EventType.MouseDown:
					if (_tracking)
					{
						// Cancel drag when other mouse button is pressed.
						s_TrackingCancelBlockContext = true;
						Event.current.Use();
					}
					break;

				case EventType.MouseDrag:
					if (_tracking)
					{
						// Reset target index and adjust when looping through list items.
						if (mousePosition.y < firstItemY)
							newTargetIndex = 0;
						else
						if (mousePosition.y >= position.yMax)
							newTargetIndex = adaptor.Count;

						s_DragItemPosition.y = Mathf.Clamp( mousePosition.y + s_AnchorMouseOffset, firstItemY, position.yMax - s_DragItemPosition.height - 1 );
					}
					break;

				case EventType.MouseUp:
					if (controlID == GUIUtility.hotControl)
					{
						// Allow user code to change control over reordering during drag.
						if (!s_TrackingCancelBlockContext && _allowReordering)
							AcceptReorderDrag( adaptor );
						else
							StopTrackingReorderDrag();
						Event.current.Use();
					}
					break;

				case EventType.KeyDown:
					if (_tracking && Event.current.keyCode == KeyCode.Escape)
					{
						StopTrackingReorderDrag();
						Event.current.Use();
					}
					break;

				case EventType.ExecuteCommand:
					if (s_ContextControlID == controlID)
					{
						int itemIndex = s_ContextItemIndex;
						try
						{
							DoCommand( s_ContextCommandName, itemIndex, adaptor );
							Event.current.Use();
						}
						finally
						{
							s_ContextControlID = 0;
							s_ContextItemIndex = 0;
						}
					}
					break;

				case EventType.Repaint:
					// Draw caption area of list.
					containerStyle.Draw( position, GUIContent.none, false, false, false, false );
					break;
			}

			ReorderableListGUI.indexOfChangedItem = -1;

			// Draw list items!
			Rect itemPosition = new Rect( position.x + 2, firstItemY, position.width - 4, 0 );
			float targetSlotPosition = position.yMax - s_DragItemPosition.height - 1;

			float lastMidPoint = 0f;
			float lastHeight = 0f;

			int count = adaptor.Count;
			for (int i = 0; i < count; ++i)
			{
				itemPosition.y = itemPosition.yMax;
				itemPosition.height = 0;

				if (_tracking)
				{
					// Does this represent the target index?
					if (i == s_TargetIndex)
					{
						targetSlotPosition = itemPosition.y;
						itemPosition.y += s_DragItemPosition.height;
					}

					// Do not draw item if it is currently being dragged.
					// Draw later so that it is shown in front of other controls.
					if (i == s_AnchorIndex)
						continue;

					lastMidPoint = itemPosition.y - lastHeight / 2f;
				}

				// Update position for current item.
				itemPosition.height = adaptor.GetItemHeight( i ) + 4;
				lastHeight = itemPosition.height;

				if (_tracking && eventType == EventType.MouseDrag)
				{
					float midpoint = itemPosition.y + itemPosition.height / 2f;

					if (s_TargetIndex < i)
					{
						if (s_DragItemPosition.yMax > lastMidPoint && s_DragItemPosition.yMax < midpoint)
							newTargetIndex = i;
					}
					else
					if (s_TargetIndex > i)
					{
						if (s_DragItemPosition.y > lastMidPoint && s_DragItemPosition.y < midpoint)
							newTargetIndex = i;
					}

					/*if (s_DragItemPosition.y > itemPosition.y && s_DragItemPosition.y <= midpoint)
						newTargetIndex = i;
					else if (s_DragItemPosition.yMax > midpoint && s_DragItemPosition.yMax <= itemPosition.yMax)
						newTargetIndex = i + 1;*/
				}

				// The following may break use of tab key to navigate through controls :/
				if ((flags & ReorderableListFlags.DisableClipping) == 0)
				{
					// Clip list item? Performance boost!
					if (itemPosition.yMax < _visibleRect.y - itemPosition.height)
					{
						// Let's try and trick Unity into maintaining tab key support...
						GUIUtility.GetControlID( FocusType.Keyboard, itemPosition );
						continue;
					}
					if (itemPosition.y > _visibleRect.yMax + itemPosition.height)
						break;
				}

				// Draw list item.
				DrawListItem( eventType, itemPosition, adaptor, i );

				// Did list count change (i.e. item removed)?
				if (adaptor.Count < count)
				{
					// We assume that it was this item which was removed, so --i allows us
					// to process the next item as usual.
					count = adaptor.Count;
					--i;
					continue;
				}

				// Event has already been used, skip to next item.
				if (Event.current.type != EventType.Used)
				{
					switch (eventType)
					{
						case EventType.MouseDown:
							if (GUI.enabled && itemPosition.Contains( mousePosition ))
							{
								// Remove input focus from control before attempting a context click or drag.
								GUIUtility.keyboardControl = 0;

								if (_allowReordering && adaptor.CanDrag( i ) && Event.current.button == 0)
								{
									s_DragItemPosition = itemPosition;

									BeginTrackingReorderDrag( controlID, i );
									s_AnchorMouseOffset = itemPosition.y - mousePosition.y;
									s_TargetIndex = i;

									Event.current.Use();
								}
							}
							break;
/* DEBUG
						case EventType.Repaint:
							GUI.color = Color.red;
							GUI.DrawTexture(new Rect(0, lastMidPoint, 10, 1), EditorGUIUtility.whiteTexture);
							GUI.color = Color.yellow;
							GUI.DrawTexture(new Rect(5, itemPosition.y + itemPosition.height / 2f, 10, 1), EditorGUIUtility.whiteTexture);
							GUI.color = Color.white;
							break;
//*/
					}
				}
			}

			// Item which is being dragged should be shown on top of other controls!
			if (IsTrackingControl( controlID ))
			{
				lastMidPoint = position.yMax - lastHeight / 2f;

				if (eventType == EventType.MouseDrag)
				{
					if (s_DragItemPosition.yMax >= lastMidPoint)
						newTargetIndex = count;

					// Force repaint to occur so that dragging rectangle is visible.
					s_TargetIndex = newTargetIndex;
					Event.current.Use();
				}

				DrawFloatingListItem( eventType, adaptor, targetSlotPosition );
/* DEBUG
				if (eventType == EventType.Repaint) {
					GUI.color = Color.blue;
					GUI.DrawTexture(new Rect(100, lastMidPoint, 20, 1), EditorGUIUtility.whiteTexture);
					GUI.color = Color.white;
				}
//*/
			}

			// Fake control to catch input focus if auto focus was not possible.
			GUIUtility.GetControlID( FocusType.Keyboard );
		}

		/// <summary>
		/// Checks to see if list control needs to be automatically focused.
		/// </summary>
		/// <param name="controlID">Unique ID of list control.</param>
		private void CheckForAutoFocusControl( int controlID )
		{
			if (Event.current.type == EventType.Used)
				return;

			// Automatically focus control!
			if (s_AutoFocusControlID == controlID)
			{
				s_AutoFocusControlID = 0;
				Internal.GUIHelper.FocusTextInControl( "AutoFocus_" + controlID + "_" + s_AutoFocusIndex );
				s_AutoFocusIndex = -1;
			}
		}

		/// <summary>
		/// Draw additional controls below list control and highlight drop target.
		/// </summary>
		/// <param name="position">Position of list control in GUI.</param>
		/// <param name="controlID">Unique ID of list control.</param>
		/// <param name="adaptor">Reorderable list adaptor.</param>
		private void DrawFooterControls( Rect position, int controlID, IReorderableListAdaptor adaptor )
		{
			if (hasAddButton)
			{
				Rect addButtonRect = new Rect(
					                     position.xMax - addButtonStyle.fixedWidth,
					                     position.yMax - 1,
					                     addButtonStyle.fixedWidth,
					                     addButtonStyle.fixedHeight
				                     );
				DoAddButton( addButtonRect, controlID, adaptor );
			}
		}

		/// <summary>
		/// Cache of container heights mapped by control ID.
		/// </summary>
		private static Dictionary<int, float> s_ContainerHeightCache = new Dictionary<int, float>();

		/// <summary>
		/// Do layout version of list field.
		/// </summary>
		/// <param name="controlID">Unique ID of list control.</param>
		/// <param name="adaptor">Reorderable list adaptor.</param>
		/// <returns>
		/// Position of list container area in GUI (excludes footer area).
		/// </returns>
		private Rect DrawLayoutListField( int controlID, IReorderableListAdaptor adaptor )
		{
			float totalHeight;

			// Calculate position of list field using layout engine.
			if (Event.current.type == EventType.Layout)
			{
				totalHeight = CalculateListHeight( adaptor );
				s_ContainerHeightCache[controlID] = totalHeight;
			}
			else
			{
				totalHeight = s_ContainerHeightCache.ContainsKey( controlID )
					? s_ContainerHeightCache[controlID]
					: 0;
			}

			Rect position = GUILayoutUtility.GetRect( GUIContent.none, containerStyle, GUILayout.Height( totalHeight ) );

			// Make room for add button?
			if (hasAddButton)
				position.height -= addButtonStyle.fixedHeight;

			// Draw list as normal.
			DrawListContainerAndItems( position, controlID, adaptor );

			CheckForAutoFocusControl( controlID );

			return position;
		}

		/// <summary>
		/// Draw content for empty list (layout version).
		/// </summary>
		/// <param name="drawEmpty">Callback to draw empty content.</param>
		/// <returns>
		/// Position of list container area in GUI (excludes footer area).
		/// </returns>
		private Rect DrawLayoutEmptyList( DrawEmpty drawEmpty )
		{
			Rect r = EditorGUILayout.BeginVertical( containerStyle );
			{
				if (drawEmpty != null)
					drawEmpty();
				else
					GUILayout.Space( 5 );
			}
			EditorGUILayout.EndVertical();

			// Allow room for add button.
			GUILayoutUtility.GetRect( 0, addButtonStyle.fixedHeight - 1 );

			return r;
		}

		/// <summary>
		/// Draw content for empty list (layout version).
		/// </summary>
		/// <param name="position">Position of list control in GUI.</param>
		/// <param name="drawEmpty">Callback to draw empty content.</param>
		private void DrawEmptyListControl( Rect position, DrawEmptyAbsolute drawEmpty )
		{
			if (Event.current.type == EventType.Repaint)
				containerStyle.Draw( position, GUIContent.none, false, false, false, false );

			// Take padding into consideration when drawing empty content.
			position.x += containerStyle.padding.left;
			position.y += containerStyle.padding.top;
			position.width -= containerStyle.padding.horizontal;
			position.height -= containerStyle.padding.vertical;

			if (drawEmpty != null)
				drawEmpty( position );
		}

		/// <summary>
		/// Correct if for some reason one or more styles are missing!
		/// </summary>
		private void FixStyles()
		{
			containerStyle = containerStyle ?? ReorderableListGUI.defaultContainerStyle;
			addButtonStyle = addButtonStyle ?? ReorderableListGUI.defaultAddButtonStyle;
			removeButtonStyle = removeButtonStyle ?? ReorderableListGUI.defaultRemoveButtonStyle;

			if (s_RightAlignedLabelStyle == null)
			{
				s_RightAlignedLabelStyle = new GUIStyle( GUI.skin.label );
				s_RightAlignedLabelStyle.alignment = TextAnchor.MiddleRight;
				s_RightAlignedLabelStyle.padding.right = 4;
			}
		}

		/// <summary>
		/// Draw layout version of list control.
		/// </summary>
		/// <param name="controlID">Unique ID of list control.</param>
		/// <param name="adaptor">Reorderable list adaptor.</param>
		/// <param name="drawEmpty">Delegate for drawing empty list.</param>
		private void Draw( int controlID, IReorderableListAdaptor adaptor, DrawEmpty drawEmpty )
		{
			FixStyles();
			PrepareState( controlID, adaptor );

			Rect position;

			if (adaptor.Count > 0)
				position = DrawLayoutListField( controlID, adaptor );
			else
				position = DrawLayoutEmptyList( drawEmpty );

			DrawFooterControls( position, controlID, adaptor );
		}

		/// <inheritdoc cref="Draw(int, IReorderableListAdaptor, DrawEmpty)"/>
		public void Draw( IReorderableListAdaptor adaptor, DrawEmpty drawEmpty )
		{
			int controlID = GUIUtility.GetControlID( FocusType.Passive );
			Draw( controlID, adaptor, drawEmpty );
		}

		/// <inheritdoc cref="Draw(int, IReorderableListAdaptor, DrawEmpty)"/>
		public void Draw( IReorderableListAdaptor adaptor )
		{
			int controlID = GUIUtility.GetControlID( FocusType.Passive );
			Draw( controlID, adaptor, null );
		}

		/// <summary>
		/// Draw list control with absolute positioning.
		/// </summary>
		/// <param name="position">Position of list control in GUI.</param>
		/// <param name="controlID">Unique ID of list control.</param>
		/// <param name="adaptor">Reorderable list adaptor.</param>
		/// <param name="drawEmpty">Delegate for drawing empty list.</param>
		private void Draw( Rect position, int controlID, IReorderableListAdaptor adaptor, DrawEmptyAbsolute drawEmpty )
		{
			FixStyles();
			PrepareState( controlID, adaptor );

			// Allow for footer area.
			if (hasAddButton)
				position.height -= addButtonStyle.fixedHeight;

			if (adaptor.Count > 0)
			{
				DrawListContainerAndItems( position, controlID, adaptor );
				CheckForAutoFocusControl( controlID );
			}
			else
			{
				DrawEmptyListControl( position, drawEmpty );
			}

			DrawFooterControls( position, controlID, adaptor );
		}

		/// <summary>
		/// Draw list control with absolute positioning.
		/// </summary>
		/// <param name="position">Position of list control in GUI.</param>
		/// <param name="adaptor">Reorderable list adaptor.</param>
		/// <param name="drawEmpty">Delegate for drawing empty list.</param>
		public void Draw( Rect position, IReorderableListAdaptor adaptor, DrawEmptyAbsolute drawEmpty )
		{
			int controlID = GUIUtility.GetControlID( FocusType.Passive );
			Draw( position, controlID, adaptor, drawEmpty );
		}

		/// <inheritdoc cref="Draw(Rect, IReorderableListAdaptor, DrawEmptyAbsolute)"/>
		public void Draw( Rect position, IReorderableListAdaptor adaptor )
		{
			int controlID = GUIUtility.GetControlID( FocusType.Passive );
			Draw( position, controlID, adaptor, null );
		}

		#endregion

		#region Context Menu

		/// <summary>
		/// Content for "Move to Top" command.
		/// </summary>
		protected static readonly GUIContent commandMoveToTop = new GUIContent( "Move to Top" );
		/// <summary>
		/// Content for "Move to Bottom" command.
		/// </summary>
		protected static readonly GUIContent commandMoveToBottom = new GUIContent( "Move to Bottom" );
		/// <summary>
		/// Content for "Insert Above" command.
		/// </summary>
		protected static readonly GUIContent commandInsertAbove = new GUIContent( "Insert Above" );
		/// <summary>
		/// Content for "Insert Below" command.
		/// </summary>
		protected static readonly GUIContent commandInsertBelow = new GUIContent( "Insert Below" );
		/// <summary>
		/// Content for "Duplicate" command.
		/// </summary>
		protected static readonly GUIContent commandDuplicate = new GUIContent( "Duplicate" );
		/// <summary>
		/// Content for "Remove" command.
		/// </summary>
		protected static readonly GUIContent commandRemove = new GUIContent( "Remove" );
		/// <summary>
		/// Content for "Clear All" command.
		/// </summary>
		protected static readonly GUIContent commandClearAll = new GUIContent( "Clear All" );

		// Command control id and item index are assigned when context menu is shown.
		private static int s_ContextControlID;
		private static int s_ContextItemIndex;

		// Command name is assigned by default context menu handler.
		private static string s_ContextCommandName;

		private void ShowContextMenu( int controlID, int itemIndex, IReorderableListAdaptor adaptor )
		{
			GenericMenu menu = new GenericMenu();

			s_ContextControlID = controlID;
			s_ContextItemIndex = itemIndex;

			AddItemsToMenu( menu, itemIndex, adaptor );

			if (menu.GetItemCount() > 0)
				menu.ShowAsContext();
		}

		/// <summary>
		/// Default functionality to handle context command.
		/// </summary>
		/// <example>
		/// <para>Can be used when adding custom items to the context menu:</para>
		/// <code language="csharp"><![CDATA[
		/// protected override void AddItemsToMenu(GenericMenu menu, int itemIndex, IReorderableListAdaptor adaptor) {
		///     var specialCommand = new GUIContent("Special Command");
		///     menu.AddItem(specialCommand, false, defaultContextHandler, specialCommand);
		/// }
		/// ]]></code>
		/// <code language="unityscript"><![CDATA[
		/// function AddItemsToMenu(menu:GenericMenu, itemIndex:int, list:IReorderableListAdaptor) {
		///     var specialCommand = new GUIContent('Special Command');
		///     menu.AddItem(specialCommand, false, defaultContextHandler, specialCommand);
		/// }
		/// ]]></code>
		/// </example>
		/// <seealso cref="AddItemsToMenu"/>
		protected static readonly GenericMenu.MenuFunction2 defaultContextHandler = DefaultContextMenuHandler;

		private static void DefaultContextMenuHandler( object userData )
		{
			var commandContent = userData as GUIContent;
			if (commandContent == null || string.IsNullOrEmpty( commandContent.text ))
				return;

			s_ContextCommandName = commandContent.text;

			var e = EditorGUIUtility.CommandEvent( "ReorderableListContextCommand" );
			EditorWindow.focusedWindow.SendEvent( e );
		}

		/// <summary>
		/// Invoked to generate context menu for list item.
		/// </summary>
		/// <param name="menu">Menu which can be populated.</param>
		/// <param name="itemIndex">Zero-based index of item which was right-clicked.</param>
		/// <param name="adaptor">Reorderable list adaptor.</param>
		protected virtual void AddItemsToMenu( GenericMenu menu, int itemIndex, IReorderableListAdaptor adaptor )
		{
			if ((flags & ReorderableListFlags.DisableReordering) == 0)
			{
				if (itemIndex > 0)
					menu.AddItem( commandMoveToTop, false, defaultContextHandler, commandMoveToTop );
				else
					menu.AddDisabledItem( commandMoveToTop );

				if (itemIndex + 1 < adaptor.Count)
					menu.AddItem( commandMoveToBottom, false, defaultContextHandler, commandMoveToBottom );
				else
					menu.AddDisabledItem( commandMoveToBottom );

				if (hasAddButton)
				{
					menu.AddSeparator( "" );

					menu.AddItem( commandInsertAbove, false, defaultContextHandler, commandInsertAbove );
					menu.AddItem( commandInsertBelow, false, defaultContextHandler, commandInsertBelow );

					if ((flags & ReorderableListFlags.DisableDuplicateCommand) == 0)
						menu.AddItem( commandDuplicate, false, defaultContextHandler, commandDuplicate );
				}
			}

			if (hasRemoveButtons)
			{
				if (menu.GetItemCount() > 0)
					menu.AddSeparator( "" );

				menu.AddItem( commandRemove, false, defaultContextHandler, commandRemove );
				menu.AddSeparator( "" );
				menu.AddItem( commandClearAll, false, defaultContextHandler, commandClearAll );
			}
		}

		#endregion

		#region Command Handling

		/// <summary>
		/// Invoked to handle context command.
		/// </summary>
		/// <remarks>
		/// <para>It is important to set the value of <c>GUI.changed</c> to <c>true</c> if any
		/// changes are made by command handler.</para>
		/// <para>Default command handling functionality can be inherited:</para>
		/// <code language="csharp"><![CDATA[
		/// protected override bool HandleCommand(string commandName, int itemIndex, IReorderableListAdaptor adaptor) {
		///     if (base.HandleCommand(itemIndex, adaptor))
		///         return true;
		///
		///     // Place custom command handling code here...
		///     switch (commandName) {
		///         case "Your Command":
		///             return true;
		///     }
		///
		///     return false;
		/// }
		/// ]]></code>
		/// <code language="unityscript"><![CDATA[
		/// function HandleCommand(commandName:String, itemIndex:int, adaptor:IReorderableListAdaptor):boolean {
		///     if (base.HandleCommand(itemIndex, adaptor))
		///         return true;
		///
		///     // Place custom command handling code here...
		///     switch (commandName) {
		///         case 'Your Command':
		///             return true;
		///     }
		///
		///     return false;
		/// }
		/// ]]></code>
		/// </remarks>
		/// <param name="commandName">Name of command. This is the text shown in the context menu.</param>
		/// <param name="itemIndex">Zero-based index of item which was right-clicked.</param>
		/// <param name="adaptor">Reorderable list adaptor.</param>
		/// <returns>
		/// A value of <c>true</c> if command was known; otherwise <c>false</c>.
		/// </returns>
		protected virtual bool HandleCommand( string commandName, int itemIndex, IReorderableListAdaptor adaptor )
		{
			switch (commandName)
			{
				case "Move to Top":
					MoveItem( adaptor, itemIndex, 0 );
					return true;
				case "Move to Bottom":
					MoveItem( adaptor, itemIndex, adaptor.Count );
					return true;

				case "Insert Above":
					InsertItem( adaptor, itemIndex );
					return true;
				case "Insert Below":
					InsertItem( adaptor, itemIndex + 1 );
					return true;
				case "Duplicate":
					DuplicateItem( adaptor, itemIndex );
					return true;

				case "Remove":
					RemoveItem( adaptor, itemIndex );
					return true;
				case "Clear All":
					ClearAll( adaptor );
					return true;

				default:
					return false;
			}
		}

		/// <summary>
		/// Call to manually perform command.
		/// </summary>
		/// <remarks>
		/// <para>Warning message is logged to console if attempted to execute unknown command.</para>
		/// </remarks>
		/// <param name="commandName">Name of command. This is the text shown in the context menu.</param>
		/// <param name="itemIndex">Zero-based index of item which was right-clicked.</param>
		/// <param name="adaptor">Reorderable list adaptor.</param>
		/// <returns>
		/// A value of <c>true</c> if command was known; otherwise <c>false</c>.
		/// </returns>
		public bool DoCommand( string commandName, int itemIndex, IReorderableListAdaptor adaptor )
		{
			if (!HandleCommand( s_ContextCommandName, itemIndex, adaptor ))
			{
				Debug.LogWarning( "Unknown context command." );
				return false;
			}
			return true;
		}

		/// <summary>
		/// Call to manually perform command.
		/// </summary>
		/// <remarks>
		/// <para>Warning message is logged to console if attempted to execute unknown command.</para>
		/// </remarks>
		/// <param name="command">Content representing command.</param>
		/// <param name="itemIndex">Zero-based index of item which was right-clicked.</param>
		/// <param name="adaptor">Reorderable list adaptor.</param>
		/// <returns>
		/// A value of <c>true</c> if command was known; otherwise <c>false</c>.
		/// </returns>
		public bool DoCommand( GUIContent command, int itemIndex, IReorderableListAdaptor adaptor )
		{
			return DoCommand( command.text, itemIndex, adaptor );
		}

		#endregion

		#region Methods

		/// <summary>
		/// Calculate height of list control in pixels.
		/// </summary>
		/// <param name="adaptor">Reorderable list adaptor.</param>
		/// <returns>
		/// Required list height in pixels.
		/// </returns>
		public float CalculateListHeight( IReorderableListAdaptor adaptor )
		{
			FixStyles();

			float totalHeight = containerStyle.padding.vertical - 1;

			// Take list items into consideration.
			int count = adaptor.Count;
			for (int i = 0; i < count; ++i)
				totalHeight += adaptor.GetItemHeight( i );
			// Add spacing between list items.
			totalHeight += 4 * count;

			// Add height of add button.
			if (hasAddButton)
				totalHeight += addButtonStyle.fixedHeight;

			return totalHeight;
		}

		/// <summary>
		/// Calculate height of list control in pixels.
		/// </summary>
		/// <param name="itemCount">Count of items in list.</param>
		/// <param name="itemHeight">Fixed height of list item.</param>
		/// <returns>
		/// Required list height in pixels.
		/// </returns>
		public float CalculateListHeight( int itemCount, float itemHeight )
		{
			FixStyles();

			float totalHeight = containerStyle.padding.vertical - 1;

			// Take list items into consideration.
			totalHeight += (itemHeight + 4) * itemCount;

			// Add height of add button.
			if (hasAddButton)
				totalHeight += addButtonStyle.fixedHeight;

			return totalHeight;
		}

		/// <summary>
		/// Move item from source index to destination index.
		/// </summary>
		/// <param name="adaptor">Reorderable list adaptor.</param>
		/// <param name="sourceIndex">Zero-based index of source item.</param>
		/// <param name="destIndex">Zero-based index of destination index.</param>
		protected void MoveItem( IReorderableListAdaptor adaptor, int sourceIndex, int destIndex )
		{
			adaptor.Move( sourceIndex, destIndex );

			GUI.changed = true;
			ReorderableListGUI.indexOfChangedItem = -1;
		}

		/// <summary>
		/// Add item at end of list and raises the event <see cref="ItemInserted"/>.
		/// </summary>
		/// <param name="adaptor">Reorderable list adaptor.</param>
		protected void AddItem( IReorderableListAdaptor adaptor )
		{
			adaptor.Add();
			AutoFocusItem( s_ContextControlID, adaptor.Count - 1 );

			GUI.changed = true;
			ReorderableListGUI.indexOfChangedItem = -1;

			var args = new ItemInsertedEventArgs( adaptor, adaptor.Count - 1, false );
			OnItemInserted( args );
		}

		/// <summary>
		/// Insert item at specified index and raises the event <see cref="ItemInserted"/>.
		/// </summary>
		/// <param name="adaptor">Reorderable list adaptor.</param>
		/// <param name="itemIndex">Zero-based index of item.</param>
		protected void InsertItem( IReorderableListAdaptor adaptor, int itemIndex )
		{
			adaptor.Insert( itemIndex );
			AutoFocusItem( s_ContextControlID, itemIndex );

			GUI.changed = true;
			ReorderableListGUI.indexOfChangedItem = -1;

			var args = new ItemInsertedEventArgs( adaptor, itemIndex, false );
			OnItemInserted( args );
		}

		/// <summary>
		/// Duplicate specified item and raises the event <see cref="ItemInserted"/>.
		/// </summary>
		/// <param name="adaptor">Reorderable list adaptor.</param>
		/// <param name="itemIndex">Zero-based index of item.</param>
		protected void DuplicateItem( IReorderableListAdaptor adaptor, int itemIndex )
		{
			adaptor.Duplicate( itemIndex );
			AutoFocusItem( s_ContextControlID, itemIndex + 1 );

			GUI.changed = true;
			ReorderableListGUI.indexOfChangedItem = -1;

			var args = new ItemInsertedEventArgs( adaptor, itemIndex + 1, true );
			OnItemInserted( args );
		}

		/// <summary>
		/// Remove specified item.
		/// </summary>
		/// <remarks>
		/// <para>The event <see cref="ItemRemoving"/> is raised prior to removing item
		/// and allows removal to be cancelled.</para>
		/// </remarks>
		/// <param name="adaptor">Reorderable list adaptor.</param>
		/// <param name="itemIndex">Zero-based index of item.</param>
		/// <returns>
		/// Returns a value of <c>false</c> if operation was cancelled.
		/// </returns>
		protected bool RemoveItem( IReorderableListAdaptor adaptor, int itemIndex )
		{
			var args = new ItemRemovingEventArgs( adaptor, itemIndex );
			OnItemRemoving( args );
			if (args.Cancel)
				return false;

			adaptor.Remove( itemIndex );

			GUI.changed = true;
			ReorderableListGUI.indexOfChangedItem = -1;

			return true;
		}

		/// <summary>
		/// Remove all items from list.
		/// </summary>
		/// <remarks>
		/// <para>The event <see cref="ItemRemoving"/> is raised for each item prior to
		/// clearing array and allows entire operation to be cancelled.</para>
		/// </remarks>
		/// <param name="adaptor">Reorderable list adaptor.</param>
		/// <returns>
		/// Returns a value of <c>false</c> if operation was cancelled.
		/// </returns>
		protected bool ClearAll( IReorderableListAdaptor adaptor )
		{
			if (adaptor.Count == 0)
				return true;

			var args = new ItemRemovingEventArgs( adaptor, 0 );
			int count = adaptor.Count;
			for (int i = 0; i < count; ++i)
			{
				args.itemIndex = i;
				OnItemRemoving( args );
				if (args.Cancel)
					return false;
			}

			adaptor.Clear();

			GUI.changed = true;
			ReorderableListGUI.indexOfChangedItem = -1;

			return true;
		}

		#endregion
	}
}
#endif
