-
-
Save jacobdufault/a82e5a07af070a0dd94b to your computer and use it in GitHub Desktop.
Initial implementation of a single item editor for IList<T> types (example: http://imgur.com/ZnssAju)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using UnityEngine; | |
namespace FullInspector { | |
public class ComplexObject { | |
public int Item1, Item2, Item3, Item4, Item5; | |
public List<double> Item6, Item7; | |
public Guid Item8, Item9; | |
public GameObject Item10; | |
} | |
public class SingleItemBehavior : BaseBehavior { | |
[SingleItemEditor] | |
public List<ComplexObject> Items; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
namespace FullInspector { | |
public class SingleItemEditorAttribute : Attribute { | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment