|
using System; |
|
using System.Collections.Generic; |
|
using System.Runtime.Serialization; |
|
using UnityEditor; |
|
using UnityEngine; |
|
|
|
namespace FullInspector { |
|
[CustomAttributePropertyEditor(typeof(SingleItemEditorAttribute), ReplaceOthers = true)] |
|
public class SingleItemEditorAttributePropertyEditor<TDerived, T> : |
|
AttributePropertyEditor<IList<T>, SingleItemEditorAttribute> |
|
where TDerived : IList<T>, new() { |
|
|
|
private IPropertyEditor _itemEditor = PropertyEditor.Get(typeof(T), null); |
|
|
|
private static void EnsureInstance(ref IList<T> element) { |
|
if (element == null) { |
|
element = new TDerived(); |
|
} |
|
} |
|
|
|
private bool TryGetEditedElement(IList<T> list, out T edited) { |
|
if (list.Count == 0) { |
|
edited = default(T); |
|
return false; |
|
} |
|
|
|
ObjectMetadata metadata = ObjectInstanceMap<ObjectMetadata>.Get(list); |
|
if (metadata.CurrentIndex < 0) metadata.CurrentIndex = 0; |
|
if (metadata.CurrentIndex >= list.Count) metadata.CurrentIndex = list.Count - 1; |
|
|
|
edited = list[metadata.CurrentIndex]; |
|
return true; |
|
} |
|
|
|
private void UpdateEditedElement(IList<T> list, T edited) { |
|
if (list.Count == 0) { |
|
return; |
|
} |
|
|
|
ObjectMetadata metadata = ObjectInstanceMap<ObjectMetadata>.Get(list); |
|
if (metadata.CurrentIndex < 0 || metadata.CurrentIndex >= list.Count) { |
|
return; |
|
} |
|
|
|
list[metadata.CurrentIndex] = edited; |
|
} |
|
|
|
private static bool CanDelete(IList<T> list) { |
|
ObjectMetadata metadata = ObjectInstanceMap<ObjectMetadata>.Get(list); |
|
return metadata.CurrentIndex >= 0 && metadata.CurrentIndex < list.Count; |
|
} |
|
|
|
private static bool CanGoBack(IList<T> list) { |
|
ObjectMetadata metadata = ObjectInstanceMap<ObjectMetadata>.Get(list); |
|
return metadata.CurrentIndex > 0; |
|
} |
|
|
|
private static void ChangeEditedElement(IList<T> list, int offset) { |
|
ObjectMetadata metadata = ObjectInstanceMap<ObjectMetadata>.Get(list); |
|
metadata.CurrentIndex += offset; |
|
|
|
if (metadata.CurrentIndex < 0) metadata.CurrentIndex = 0; |
|
|
|
while (metadata.CurrentIndex >= list.Count) { |
|
list.Add(default(T)); |
|
} |
|
} |
|
|
|
private static void RemoveEditedElement(IList<T> list) { |
|
ObjectMetadata metadata = ObjectInstanceMap<ObjectMetadata>.Get(list); |
|
if (metadata.CurrentIndex < 0 || metadata.CurrentIndex >= list.Count) { |
|
return; |
|
} |
|
|
|
list.RemoveAt(metadata.CurrentIndex); |
|
} |
|
|
|
private static GUIContent AddEditingInformation(GUIContent content, IList<T> list) { |
|
string updatedText; |
|
|
|
if (list.Count == 0) { |
|
updatedText = string.Format("{0} (empty)", content.text); |
|
} |
|
else { |
|
ObjectMetadata metadata = ObjectInstanceMap<ObjectMetadata>.Get(list); |
|
updatedText = string.Format("{0} (element {1} of {2})", content.text, |
|
metadata.CurrentIndex, list.Count - 1); |
|
|
|
} |
|
|
|
return new GUIContent(updatedText, content.image, content.tooltip); |
|
} |
|
|
|
private static GUIContent GetNextButtonLabel(IList<T> list) { |
|
ObjectMetadata metadata = ObjectInstanceMap<ObjectMetadata>.Get(list); |
|
|
|
if (list.Count == 0 || metadata.CurrentIndex == list.Count - 1) { |
|
return new GUIContent("Add"); |
|
} |
|
|
|
return new GUIContent(">>"); |
|
} |
|
|
|
private static float HeaderHeight = EditorStyles.label.CalcHeight(GUIContent.none, 100) * 2.5f; |
|
private static float HeaderMargin = 5; |
|
|
|
private Rect DrawHeader(Rect region, GUIContent label, IList<T> list) { |
|
Rect headerRect = region; |
|
headerRect.height = HeaderHeight; |
|
region.y += HeaderHeight + HeaderMargin; |
|
region.height -= HeaderHeight + HeaderMargin; |
|
|
|
Rect top, bottom; |
|
Rect topLeft, topRight; |
|
Rect bottomLeft, bottomRight; |
|
|
|
SplitVertically(headerRect, .5f, 0, out top, out bottom); |
|
SplitHorizontally(top, .5f, 5, out topLeft, out topRight); |
|
SplitHorizontally(bottom, .5f, 5, out bottomLeft, out bottomRight); |
|
|
|
EditorGUI.LabelField(topLeft, AddEditingInformation(label, list)); |
|
EditorGUI.BeginDisabledGroup(!CanDelete(list)); |
|
if (GUI.Button(topRight, "del")) { |
|
RemoveEditedElement(list); |
|
} |
|
EditorGUI.EndDisabledGroup(); |
|
|
|
EditorGUI.BeginDisabledGroup(!CanGoBack(list)); |
|
if (GUI.Button(bottomLeft, "<<")) { |
|
ChangeEditedElement(list, -1); |
|
} |
|
EditorGUI.EndDisabledGroup(); |
|
|
|
if (GUI.Button(bottomRight, GetNextButtonLabel(list))) { |
|
ChangeEditedElement(list, 1); |
|
} |
|
|
|
return region; |
|
} |
|
|
|
private static void SplitHorizontally(Rect rect, float percentage, float margin, out Rect left, out Rect right) { |
|
left = new Rect(rect); |
|
left.width *= percentage; |
|
|
|
right = new Rect(rect); |
|
right.x += left.width + margin; |
|
right.width -= left.width + margin; |
|
} |
|
|
|
private static void SplitVertically(Rect rect, float percentage, float margin, out Rect top, out Rect bottom) { |
|
top = new Rect(rect); |
|
top.height *= percentage; |
|
|
|
bottom = new Rect(rect); |
|
bottom.y += top.height + margin; |
|
bottom.height -= top.height + margin; |
|
} |
|
|
|
protected override IList<T> Edit(Rect region, GUIContent label, IList<T> list, SingleItemEditorAttribute attribute) { |
|
EnsureInstance(ref list); |
|
|
|
region = DrawHeader(region, label, list); |
|
|
|
T edited; |
|
if (TryGetEditedElement(list, out edited)) { |
|
edited = (T)_itemEditor.Edit(region, GUIContent.none, edited); |
|
UpdateEditedElement(list, edited); |
|
} |
|
|
|
return list; |
|
} |
|
|
|
protected override float GetElementHeight(GUIContent label, IList<T> list, SingleItemEditorAttribute attribute) { |
|
EnsureInstance(ref list); |
|
|
|
float height = HeaderHeight + HeaderMargin; |
|
|
|
T edited; |
|
if (TryGetEditedElement(list, out edited)) { |
|
height += _itemEditor.GetElementHeight(GUIContent.none, edited); |
|
} |
|
|
|
return height; |
|
} |
|
|
|
private class ObjectMetadata { |
|
public int CurrentIndex; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Returns an associated object for another object. |
|
/// </summary> |
|
internal class ObjectInstanceMap<TMapped> where TMapped : new() { |
|
private static ObjectIDGenerator _ids = new ObjectIDGenerator(); |
|
private static Dictionary<long, TMapped> _items = new Dictionary<long, TMapped>(); |
|
private static TMapped _nullValue = new TMapped(); |
|
|
|
public static TMapped Get(object item) { |
|
if (item == null) { |
|
return _nullValue; |
|
} |
|
|
|
bool firstTime; |
|
long id = _ids.GetId(item, out firstTime); |
|
|
|
if (firstTime) { |
|
TMapped value = new TMapped(); |
|
_items[id] = value; |
|
return value; |
|
} |
|
|
|
return _items[id]; |
|
} |
|
} |
|
} |