Skip to content

Instantly share code, notes, and snippets.

@jacobdufault
Created April 27, 2014 20:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jacobdufault/a82e5a07af070a0dd94b to your computer and use it in GitHub Desktop.
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)
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;
}
}
using System;
namespace FullInspector {
public class SingleItemEditorAttribute : Attribute {
}
}
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