Example #1
0
 /// <summary>
 /// Registers the supplied hashable property's initial backing field value and setter callback.
 /// </summary>
 /// <param name="trigger">Hashable property whose value changes will trigger the callback.</param>
 /// <param name="propertyToInvoke">
 /// Hashable property whose setter to invoke. Differs from trigger only when trigger is an array size property
 /// that triggers the setter in the array property.
 /// </param>
 /// <param name="getter">Getter.</param>
 /// <param name="setter">Setter.</param>
 /// <param name="propertyType">Property type.</param>
 private static void RegisterPropertyIfNeeded(
     HashableSerializedProperty trigger,
     HashableSerializedProperty propertyToInvoke,
     System.Func <object, object> getter,
     System.Action <object, object> setter,
     System.Type propertyType
     )
 {
     // initialize value cache
     if (!valueCache.ContainsKey(trigger))
     {
         valueCache.Add(trigger, trigger.SerializedProperty.GetValue());
         // if it is an array element, ensure the array and the size properties are registered
         SerializedProperty sp = trigger.SerializedProperty;
         if (sp.IsArrayElement())
         {
             RegisterArrayProperty(sp.GetParentProperty(), getter, setter, propertyType);
         }
     }
     if (!valueCache.ContainsKey(propertyToInvoke))
     {
         valueCache.Add(propertyToInvoke, propertyToInvoke.SerializedProperty.GetValue());
     }
     // add callbacks associated with the trigger
     if (!propertySetterCallbacks.ContainsKey(trigger))
     {
         propertySetterCallbacks.Add(
             trigger, () => OnTriggerPropertySetter(propertyToInvoke, getter, setter, propertyType)
             );
     }
 }
Example #2
0
    /// <summary>
    /// Gets any properties upstream of the supplied property. This method is used to determine when a parent setter
    /// might need to be invoked.
    /// </summary>
    /// <returns>The upstream properties.</returns>
    /// <param name="property">Property.</param>
    private static List <HashableSerializedProperty> GetUpstreamProperties(HashableSerializedProperty property)
    {
        List <HashableSerializedProperty> upstreamParents = new List <HashableSerializedProperty>();
        SerializedProperty sp = property.SerializedProperty;

        if (sp != null)
        {
            while (sp.GetParentProperty() != null)
            {
                sp = sp.GetParentProperty();
                upstreamParents.Add(new HashableSerializedProperty(sp.propertyPath, property.TargetObject));
            }
        }
        return(upstreamParents);
    }
Example #3
0
 /// <summary>
 /// Registers the supplied hashable property's initial backing field value and setter callback.
 /// </summary>
 /// <param name="trigger">Hashable property whose value changes will trigger the callback.</param>
 /// <param name="propertyToInvoke">
 /// Hashable property whose setter to invoke. Differs from trigger only when trigger is an array size property
 /// that triggers the setter in the array property.
 /// </param>
 /// <param name="getter">Getter.</param>
 /// <param name="setter">Setter.</param>
 /// <param name="propertyType">Property type.</param>
 private static void RegisterPropertyIfNeeded(
     HashableSerializedProperty trigger,
     HashableSerializedProperty propertyToInvoke,
     System.Func <object, object> getter,
     System.Action <object, object> setter,
     System.Type propertyType
     )
 {
     if (propertyType == null)
     {
         return;
     }
     // ensure concrete property type is registered
     if (propertyType.IsGenericType)
     {
         FieldInfo field;
         propertyToInvoke.SerializedProperty.GetProvider(out field);
         propertyType = field.FieldType;
     }
     // initialize value cache
     if (!s_ValueCache.ContainsKey(trigger))
     {
         s_ValueCache.Add(trigger, trigger.SerializedProperty.GetValue());
         // if it is an array element, ensure the array and the size properties are registered
         SerializedProperty sp = trigger.SerializedProperty;
         if (sp.IsArrayElement())
         {
             RegisterArrayProperty(sp.GetParentProperty(), getter, setter, propertyType);
         }
     }
     if (!s_ValueCache.ContainsKey(propertyToInvoke))
     {
         s_ValueCache.Add(propertyToInvoke, propertyToInvoke.SerializedProperty.GetValue());
     }
     // add callbacks associated with the trigger
     if (!s_PropertySetterCallbacks.ContainsKey(trigger))
     {
         s_PropertySetterCallbacks.Add(
             trigger, () => OnTriggerPropertySetter(propertyToInvoke, getter, setter, propertyType)
             );
     }
 }
Example #4
0
 /// <summary>
 /// Raises the GUI event.
 /// </summary>
 /// <param name="position">Position.</param>
 /// <param name="property">Property.</param>
 /// <param name="label">Label.</param>
 public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
 {
     // TODO: this can be removed when Unity fixes bug 601339
     if (property.isArray && property.propertyType != SerializedPropertyType.String)
     {
         return;
     }
     // clear all callbacks and cached values if the selection has changed
     if (!currentSelection.SetEquals(Selection.objects))
     {
         propertySetterCallbacks.Clear();
         valueCache.Clear();
         currentSelection = new HashSet <Object>(Selection.objects);
     }
     // ensure all properties are registered
     foreach (Object target in property.serializedObject.targetObjects)
     {
         HashableSerializedProperty hashableProperty =
             new HashableSerializedProperty(property.propertyPath, target);
         // register property if needed
         RegisterPropertyIfNeeded(
             hashableProperty, hashableProperty, Attribute.Getter, Attribute.Setter, Attribute.PropertyType
             );
     }
     // display field
     EditorGUI.BeginDisabledGroup(Attribute.Setter == null);
     {
         if (DrawerToUse == null)
         {
             EditorGUI.PropertyField(position, property, true);
         }
         else
         {
             DrawerToUse.OnGUI(position, property, label);
         }
     }
     EditorGUI.EndDisabledGroup();
 }
Example #5
0
    /// <summary>
    /// Registers the array property and its size property.
    /// </summary>
    /// <param name="arrayProperty">Array property.</param>
    /// <param name="getter">Getter.</param>
    /// <param name="setter">Setter.</param>
    /// <param name="propertyType">Property type.</param>
    private static void RegisterArrayProperty(
        SerializedProperty arrayProperty,
        System.Func <object, object> getter,
        System.Action <object, object> setter,
        System.Type propertyType
        )
    {
        HashableSerializedProperty hashableArrayProperty =
            new HashableSerializedProperty(arrayProperty.propertyPath, arrayProperty.serializedObject.targetObject);
        HashableSerializedProperty hashableArraySizeProperty = new HashableSerializedProperty(
            arrayProperty.propertyPath + ".Array.size", arrayProperty.serializedObject.targetObject
            );

        RegisterPropertyIfNeeded(hashableArrayProperty, hashableArrayProperty, getter, setter, propertyType);
        RegisterPropertyIfNeeded(hashableArraySizeProperty, hashableArrayProperty, getter, setter, propertyType);
        FieldInfo field;

        arrayProperty.GetProvider(out field);
        s_ValueCache[hashableArrayProperty] = field.FieldType.IsArray ?
                                              System.Array.CreateInstance(field.FieldType.GetElementType(), 0) :
                                              System.Activator.CreateInstance(field.FieldType);
        s_ValueCache[hashableArraySizeProperty] = 0;
    }
	/// <summary>
	/// Raises the GUI event.
	/// </summary>
	/// <param name="position">Position.</param>
	/// <param name="property">Property.</param>
	/// <param name="label">Label.</param>
	public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
	{
#if UNITY_4_6
		// bug 601339
		if (property.isArray && property.propertyType != SerializedPropertyType.String)
		{
			return;
		}
#endif
		// ensure property's decorator height is registered
		if (!decoratorHeights.ContainsKey(property.propertyPath))
		{
			decoratorHeights.Add(property.propertyPath, 0f);
		}
		// clear all callbacks and cached values if the selection has changed
		if (!s_CurrentSelection.SetEquals(Selection.objects))
		{
			s_PropertySetterCallbacks.Clear();
			s_ValueCache.Clear();
			s_CurrentSelection = new HashSet<Object>(Selection.objects);
		}
		// ensure all properties are registered
		foreach (Object target in property.serializedObject.targetObjects)
		{
			HashableSerializedProperty hashableProperty =
				new HashableSerializedProperty(property.propertyPath, target);
			// register property if needed
			if (hashableProperty.SerializedProperty != null) // newly added array elements might not yet exist
			{
				RegisterPropertyIfNeeded(
					hashableProperty, hashableProperty, Attribute.Getter, Attribute.Setter, Attribute.PropertyType
				);
			}
		}
		// display field
		bool hasSetter = Attribute.Setter != null;
		EditorGUI.BeginDisabledGroup(!hasSetter);
		{
			if (!hasSetter)
			{
				position.width -= EditorGUIUtility.singleLineHeight;
			}
			if (DrawerToUse == null)
			{
				// for generic types, just back up and draw the decorator again
				if (
					property.propertyType == SerializedPropertyType.Generic ||
					property.propertyType == SerializedPropertyType.ObjectReference
				)
				{
					position.y -= decoratorHeights[property.propertyPath];
					EditorGUI.PropertyField(position, property, label, true);
					// TODO: did Unity 5.1.0b3 fix this?
				}
				// for other types, use the default property field
				else
				{
					s_DefaultPropertyField.Invoke(null, new object[] { position, property, label });
				}
			}
			else
			{
				DrawerToUse.OnGUI(position, property, label);
			}
		}
		EditorGUI.EndDisabledGroup();
		if (!hasSetter)
		{
			position.x += position.width;
			position.height = EditorGUIUtility.singleLineHeight;
			position.width = EditorGUIUtility.singleLineHeight;
			s_NoSetterStatusIcon.tooltip = string.Format("No setter found for {0}.", Attribute.PropertyName);
			GUI.Box(position, s_NoSetterStatusIcon, EditorStylesX.StatusIconStyle);
		}
	}
	/// <summary>
	/// Registers the supplied hashable property's initial backing field value and setter callback.
	/// </summary>
	/// <param name="trigger">Hashable property whose value changes will trigger the callback.</param>
	/// <param name="propertyToInvoke">
	/// Hashable property whose setter to invoke. Differs from trigger only when trigger is an array size property
	/// that triggers the setter in the array property.
	/// </param>
	/// <param name="getter">Getter.</param>
	/// <param name="setter">Setter.</param>
	/// <param name="propertyType">Property type.</param>
	private static void RegisterPropertyIfNeeded(
		HashableSerializedProperty trigger,
		HashableSerializedProperty propertyToInvoke,
		System.Func<object, object> getter,
		System.Action<object, object> setter,
		System.Type propertyType
	)
	{
		if (propertyType == null)
		{
			return;
		}
		// ensure concrete property type is registered
		if (propertyType.IsGenericType)
		{
			FieldInfo field;
			propertyToInvoke.SerializedProperty.GetProvider(out field);
			propertyType = field.FieldType;
		}
		// initialize value cache
		if (!s_ValueCache.ContainsKey(trigger))
		{
			s_ValueCache.Add(trigger, trigger.SerializedProperty.GetValue());
			// if it is an array element, ensure the array and the size properties are registered
			SerializedProperty sp = trigger.SerializedProperty;
			if (sp.IsArrayElement())
			{
				RegisterArrayProperty(sp.GetParentProperty(), getter, setter, propertyType);
			}
		}
		if (!s_ValueCache.ContainsKey(propertyToInvoke))
		{
			s_ValueCache.Add(propertyToInvoke, propertyToInvoke.SerializedProperty.GetValue());
		}
		// add callbacks associated with the trigger
		if (!s_PropertySetterCallbacks.ContainsKey(trigger))
		{
			s_PropertySetterCallbacks.Add(
				trigger, () => OnTriggerPropertySetter(propertyToInvoke, getter, setter, propertyType)
			);
		}
	}
	/// <summary>
	/// Registers the array property and its size property.
	/// </summary>
	/// <param name="arrayProperty">Array property.</param>
	/// <param name="getter">Getter.</param>
	/// <param name="setter">Setter.</param>
	/// <param name="propertyType">Property type.</param>
	private static void RegisterArrayProperty(
		SerializedProperty arrayProperty,
		System.Func<object, object> getter,
		System.Action<object, object> setter,
		System.Type propertyType
	)
	{
		HashableSerializedProperty hashableArrayProperty =
			new HashableSerializedProperty(arrayProperty.propertyPath, arrayProperty.serializedObject.targetObject);
		HashableSerializedProperty hashableArraySizeProperty = new HashableSerializedProperty(
			arrayProperty.propertyPath + ".Array.size", arrayProperty.serializedObject.targetObject
		);
		RegisterPropertyIfNeeded(hashableArrayProperty, hashableArrayProperty, getter, setter, propertyType);
		RegisterPropertyIfNeeded(hashableArraySizeProperty, hashableArrayProperty, getter, setter, propertyType);
		FieldInfo field;
		arrayProperty.GetProvider(out field);
		s_ValueCache[hashableArrayProperty] = field.FieldType.IsArray ?
			System.Array.CreateInstance(field.FieldType.GetElementType(), 0) :
			System.Activator.CreateInstance(field.FieldType);
		s_ValueCache[hashableArraySizeProperty] = 0;
	}
	/// <summary>
	/// Raises the trigger property setter event.
	/// </summary>
	/// <param name="hashableProperty">Hashable property.</param>
	/// <param name="getter">Getter.</param>
	/// <param name="setter">Setter.</param>
	/// <param name="propertyType">Property type.</param>
	/// <param name="oldValue">Old value.</param>
	private static void OnTriggerPropertySetter(
		HashableSerializedProperty hashableProperty,
		System.Func<object, object> getter,
		System.Action<object, object> setter,
		System.Type propertyType
	)
	{
		// clean up lookup tables and early out if property is dead
		SerializedProperty sp = hashableProperty.SerializedProperty;
		if (sp == null)
		{
			s_PropertySetterCallbacks.Remove(hashableProperty);
			if (s_ValueCache.ContainsKey(hashableProperty))
			{
				s_ValueCache.Remove(hashableProperty);
			}
			return;
		}
		// invoke the setter
		InvokeSetter(hashableProperty, getter, setter, propertyType);
		// when any part of an array changes, ensure array, elements, and size are all up to date
		bool doElementsNeedUpdate = false;
		bool doesArrayNeedUpdate = false;
		bool doesSizeNeedUpdate = false;
		if (sp.isArray && sp.propertyType != SerializedPropertyType.String)
		{
			doElementsNeedUpdate = true;
			doesSizeNeedUpdate = true;
		}
		else if (sp.IsArrayElement())
		{
			doesArrayNeedUpdate = true;
			doesSizeNeedUpdate = true;
			sp = sp.GetParentProperty();
		}
		else if (sp.IsArraySize())
		{
			doesArrayNeedUpdate = true;
			doElementsNeedUpdate = true;
			sp = sp.GetParentProperty();
		}
		if (doElementsNeedUpdate)
		{
			for (int elementIndex = 0; elementIndex < sp.arraySize; ++elementIndex)
			{
				HashableSerializedProperty hashableElement = new HashableSerializedProperty(
					string.Format("{0}.Array.data[{1}]", sp.propertyPath, elementIndex),
					hashableProperty.TargetObject
				);
				if (s_ValueCache.ContainsKey(hashableElement))
				{
					s_ValueCache[hashableElement] = hashableElement.SerializedProperty.GetValue();
				}
				else // for added elements when list grows
				{
					s_ValueCache.Add(hashableElement, hashableElement.SerializedProperty.GetValue());
				}
			}
		}
		if (doesArrayNeedUpdate)
		{
			HashableSerializedProperty hashableArray =
				new HashableSerializedProperty(sp.propertyPath, hashableProperty.TargetObject);
			s_ValueCache[hashableArray] = hashableArray.SerializedProperty.GetValue();
		}
		if (doesSizeNeedUpdate)
		{
			HashableSerializedProperty hashableSize = new HashableSerializedProperty(
				string.Format("{0}.Array.size", sp.propertyPath), hashableProperty.TargetObject
			);
			s_ValueCache[hashableSize] = hashableSize.SerializedProperty.GetValue();
		}
	}
	/// <summary>
	/// Invokes the supplied property setter for the supplied hashable serialized property and update its value
	/// cache.
	/// </summary>
	/// <param name="hashableProperty">A hashable representation of the property's serialized backing field.</param>
	/// <param name="getter">A getter method that takes the provider as its argument.</param>
	/// <param name="setter">A setter method that takes the provider and new value as its arguments.</param> 
	/// <param name="propertyType">The type returned by the getter and specified in the setter signature.</param>
	private static void InvokeSetter(
		HashableSerializedProperty hashableProperty,
		System.Func<object, object> getter,
		System.Action<object, object> setter,
		System.Type propertyType
	)
	{
		SerializedProperty sp = hashableProperty.SerializedProperty;
		// mark for undo
		HashSet<Object> objectsToUndo = new HashSet<Object>();
		objectsToUndo.Add(hashableProperty.TargetObject);
		string undoName = string.Format("Change {0}", sp.GetDisplayName());
		// if it's on a monobehaviour, it may affect other objects in the hierarchy
		if (hashableProperty.TargetObject is MonoBehaviour)
		{
			MonoBehaviour monoBehaviour = hashableProperty.TargetObject as MonoBehaviour;
			foreach (Transform t in monoBehaviour.transform.root.GetComponentsInChildren<Transform>(true))
			{
				objectsToUndo.Add(t.gameObject);
				foreach (Component c in t.GetComponents<Component>())
				{
					if (c != null) // skip MonoBehaviours with unassigned script reference
					{
						objectsToUndo.Add(c);
					}
				}
			}
		}
		Undo.RecordObjects(objectsToUndo.ToArray(), undoName);
		// get the providers
		FieldInfo fieldInfo;
		object provider = sp.GetProvider(out fieldInfo);
		// get the element index of the property being set, if any
		int elementIndex = hashableProperty.SerializedProperty.GetElementIndex();
		// flush inspector changes and store pending values
		sp.serializedObject.ApplyModifiedProperties();
		object pendingValue = null;
		// ensure IList backing field values are converted to the type expected by the setter
		if (elementIndex < 0 && typeof(IList).IsAssignableFrom(propertyType) && propertyType != fieldInfo.FieldType)
		{
			pendingValue = sp.GetConvertedIListValues(propertyType);
		}
		else
		{
			pendingValue = sp.GetValue();
		}
		// reset backing field to old values
		if (elementIndex >= 0)
		{
			(fieldInfo.GetValue(provider) as IList)[elementIndex] = s_ValueCache[hashableProperty];
		}
		else
		{
			fieldInfo.SetValue(provider, s_ValueCache[hashableProperty]);
		}
		// invoke the setter
		if (elementIndex >= 0)
		{
			// clone the result of the getter
			IList arrayValues = (IList)getter.Invoke(provider);
			if (typeof(System.Array).IsAssignableFrom(propertyType))
			{
				arrayValues = (IList)((System.ICloneable)arrayValues).Clone();
			}
			else
			{
				IList srcValues = arrayValues;
				arrayValues = (IList)System.Activator.CreateInstance(propertyType);
				for (int idx = 0; idx < srcValues.Count; ++idx)
				{
					arrayValues.Add(srcValues[idx]);
				}
			}
			// assign the pending element value
			arrayValues[elementIndex] = pendingValue;
			// invoke setter
			setter.Invoke(provider, arrayValues);
		}
		else
		{
			setter.Invoke(provider, pendingValue);
			// if the provider is a struct, copy its changes back up to the next reference type
			if (provider.GetType().IsValueType)
			{
				object newValue = provider;
				SerializedProperty parent = sp.GetParentProperty();
				while (parent != null && !parent.isArray)
				{
					provider = parent.GetProvider(out fieldInfo);
					if (typeof(IList).IsAssignableFrom(fieldInfo.FieldType))
					{
						elementIndex = parent.GetElementIndex();
						(fieldInfo.GetValue(provider) as IList)[elementIndex] = newValue;
					}
					else
					{
						fieldInfo.SetValue(provider, newValue);
					}
					if (provider.GetType().IsValueType)
					{
						newValue = provider;
						parent = parent.GetParentProperty();
					}
					else
					{
						parent = null;
					}
				}
			}
		}
		// set dirty
		EditorUtilityX.SetDirty(objectsToUndo.ToArray());
		// update cache
		s_ValueCache[hashableProperty] = sp.GetValue();
	}
	/// <summary>
	/// Gets any properties upstream of the supplied property. This method is used to determine when a parent setter
	/// might need to be invoked.
	/// </summary>
	/// <returns>The upstream properties.</returns>
	/// <param name="property">Property.</param>
	private static List<HashableSerializedProperty> GetUpstreamProperties(HashableSerializedProperty property)
	{
		List<HashableSerializedProperty> upstreamParents = new List<HashableSerializedProperty>();
		SerializedProperty sp = property.SerializedProperty;
		if (sp != null)
		{
			while (sp.GetParentProperty() != null)
			{
				sp = sp.GetParentProperty();
				upstreamParents.Add(new HashableSerializedProperty(sp.propertyPath, property.TargetObject));
			}
		}
		return upstreamParents;
	}
Example #12
0
    /// <summary>
    /// Raises the GUI event.
    /// </summary>
    /// <param name="position">Position.</param>
    /// <param name="property">Property.</param>
    /// <param name="label">Label.</param>
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
#if UNITY_4_6
        // bug 601339
        if (property.isArray && property.propertyType != SerializedPropertyType.String)
        {
            return;
        }
#endif
        // ensure property's decorator height is registered
        if (!decoratorHeights.ContainsKey(property.propertyPath))
        {
            decoratorHeights.Add(property.propertyPath, 0f);
        }
        // clear all callbacks and cached values if the selection has changed
        if (!s_CurrentSelection.SetEquals(Selection.objects))
        {
            s_PropertySetterCallbacks.Clear();
            s_ValueCache.Clear();
            s_CurrentSelection = new HashSet <Object>(Selection.objects);
        }
        // ensure all properties are registered
        foreach (Object target in property.serializedObject.targetObjects)
        {
            HashableSerializedProperty hashableProperty =
                new HashableSerializedProperty(property.propertyPath, target);
            // register property if needed
            if (hashableProperty.SerializedProperty != null)             // newly added array elements might not yet exist
            {
                RegisterPropertyIfNeeded(
                    hashableProperty, hashableProperty, Attribute.Getter, Attribute.Setter, Attribute.PropertyType
                    );
            }
        }
        // display field
        bool hasSetter = Attribute.Setter != null;
        EditorGUI.BeginDisabledGroup(!hasSetter);
        {
            if (!hasSetter)
            {
                position.width -= EditorGUIUtility.singleLineHeight;
            }
            if (DrawerToUse == null)
            {
                // for generic types, just back up and draw the decorator again
                if (
                    property.propertyType == SerializedPropertyType.Generic ||
                    property.propertyType == SerializedPropertyType.ObjectReference
                    )
                {
                    position.y -= decoratorHeights[property.propertyPath];
                    EditorGUI.PropertyField(position, property, label, true);
                    // TODO: did Unity 5.1.0b3 fix this?
                }
                // for other types, use the default property field
                else
                {
                    s_DefaultPropertyField.Invoke(null, new object[] { position, property, label });
                }
            }
            else
            {
                DrawerToUse.OnGUI(position, property, label);
            }
        }
        EditorGUI.EndDisabledGroup();
        if (!hasSetter)
        {
            position.x     += position.width;
            position.height = EditorGUIUtility.singleLineHeight;
            position.width  = EditorGUIUtility.singleLineHeight;
            s_NoSetterStatusIcon.tooltip = string.Format("No setter found for {0}.", Attribute.PropertyName);
            GUI.Box(position, s_NoSetterStatusIcon, EditorStylesX.StatusIconStyle);
        }
    }
Example #13
0
    /// <summary>
    /// Raises the trigger property setter event.
    /// </summary>
    /// <param name="hashableProperty">Hashable property.</param>
    /// <param name="getter">Getter.</param>
    /// <param name="setter">Setter.</param>
    /// <param name="propertyType">Property type.</param>
    /// <param name="oldValue">Old value.</param>
    private static void OnTriggerPropertySetter(
        HashableSerializedProperty hashableProperty,
        System.Func <object, object> getter,
        System.Action <object, object> setter,
        System.Type propertyType
        )
    {
        // clean up lookup tables and early out if property is dead
        SerializedProperty sp = hashableProperty.SerializedProperty;

        if (sp == null)
        {
            s_PropertySetterCallbacks.Remove(hashableProperty);
            if (s_ValueCache.ContainsKey(hashableProperty))
            {
                s_ValueCache.Remove(hashableProperty);
            }
            return;
        }
        // invoke the setter
        InvokeSetter(hashableProperty, getter, setter, propertyType);
        // when any part of an array changes, ensure array, elements, and size are all up to date
        bool doElementsNeedUpdate = false;
        bool doesArrayNeedUpdate = false;
        bool doesSizeNeedUpdate = false;

        if (sp.isArray && sp.propertyType != SerializedPropertyType.String)
        {
            doElementsNeedUpdate = true;
            doesSizeNeedUpdate = true;
        }
        else if (sp.IsArrayElement())
        {
            doesArrayNeedUpdate = true;
            doesSizeNeedUpdate = true;
            sp = sp.GetParentProperty();
        }
        else if (sp.IsArraySize())
        {
            doesArrayNeedUpdate = true;
            doElementsNeedUpdate = true;
            sp = sp.GetParentProperty();
        }
        if (doElementsNeedUpdate)
        {
            for (int elementIndex = 0; elementIndex < sp.arraySize; ++elementIndex)
            {
                HashableSerializedProperty hashableElement = new HashableSerializedProperty(
                    string.Format("{0}.Array.data[{1}]", sp.propertyPath, elementIndex),
                    hashableProperty.TargetObject
                    );
                if (s_ValueCache.ContainsKey(hashableElement))
                {
                    s_ValueCache[hashableElement] = hashableElement.SerializedProperty.GetValue();
                }
                else                 // for added elements when list grows
                {
                    s_ValueCache.Add(hashableElement, hashableElement.SerializedProperty.GetValue());
                }
            }
        }
        if (doesArrayNeedUpdate)
        {
            HashableSerializedProperty hashableArray =
                new HashableSerializedProperty(sp.propertyPath, hashableProperty.TargetObject);
            s_ValueCache[hashableArray] = hashableArray.SerializedProperty.GetValue();
        }
        if (doesSizeNeedUpdate)
        {
            HashableSerializedProperty hashableSize = new HashableSerializedProperty(
                string.Format("{0}.Array.size", sp.propertyPath), hashableProperty.TargetObject
                );
            s_ValueCache[hashableSize] = hashableSize.SerializedProperty.GetValue();
        }
    }
Example #14
0
    /// <summary>
    /// Invokes the supplied property setter for the supplied hashable serialized property and update its value
    /// cache.
    /// </summary>
    /// <param name="hashableProperty">A hashable representation of the property's serialized backing field.</param>
    /// <param name="getter">A getter method that takes the provider as its argument.</param>
    /// <param name="setter">A setter method that takes the provider and new value as its arguments.</param>
    /// <param name="propertyType">The type returned by the getter and specified in the setter signature.</param>
    private static void InvokeSetter(
        HashableSerializedProperty hashableProperty,
        System.Func <object, object> getter,
        System.Action <object, object> setter,
        System.Type propertyType
        )
    {
        SerializedProperty sp = hashableProperty.SerializedProperty;
        // mark for undo
        HashSet <Object> objectsToUndo = new HashSet <Object>();

        objectsToUndo.Add(hashableProperty.TargetObject);
        string undoName = string.Format("Change {0}", sp.GetDisplayName());

        // if it's on a monobehaviour, it may affect other objects in the hierarchy
        if (hashableProperty.TargetObject is MonoBehaviour)
        {
            MonoBehaviour monoBehaviour = hashableProperty.TargetObject as MonoBehaviour;
            foreach (Transform t in monoBehaviour.transform.root.GetComponentsInChildren <Transform>(true))
            {
                objectsToUndo.Add(t.gameObject);
                foreach (Component c in t.GetComponents <Component>())
                {
                    if (c != null)                     // skip MonoBehaviours with unassigned script reference
                    {
                        objectsToUndo.Add(c);
                    }
                }
            }
        }
        Undo.RecordObjects(objectsToUndo.ToArray(), undoName);
        // get the providers
        FieldInfo fieldInfo;
        object    provider = sp.GetProvider(out fieldInfo);
        // get the element index of the property being set, if any
        int elementIndex = hashableProperty.SerializedProperty.GetElementIndex();

        // flush inspector changes and store pending values
        sp.serializedObject.ApplyModifiedProperties();
        object pendingValue = null;

        // ensure IList backing field values are converted to the type expected by the setter
        if (elementIndex < 0 && typeof(IList).IsAssignableFrom(propertyType) && propertyType != fieldInfo.FieldType)
        {
            pendingValue = sp.GetConvertedIListValues(propertyType);
        }
        else
        {
            pendingValue = sp.GetValue();
        }
        // reset backing field to old values
        if (elementIndex >= 0)
        {
            (fieldInfo.GetValue(provider) as IList)[elementIndex] = s_ValueCache[hashableProperty];
        }
        else
        {
            fieldInfo.SetValue(provider, s_ValueCache[hashableProperty]);
        }
        // invoke the setter
        if (elementIndex >= 0)
        {
            // clone the result of the getter
            IList arrayValues = (IList)getter.Invoke(provider);
            if (typeof(System.Array).IsAssignableFrom(propertyType))
            {
                arrayValues = (IList)((System.ICloneable)arrayValues).Clone();
            }
            else
            {
                IList srcValues = arrayValues;
                arrayValues = (IList)System.Activator.CreateInstance(propertyType);
                for (int idx = 0; idx < srcValues.Count; ++idx)
                {
                    arrayValues.Add(srcValues[idx]);
                }
            }
            // assign the pending element value
            arrayValues[elementIndex] = pendingValue;
            // invoke setter
            setter.Invoke(provider, arrayValues);
        }
        else
        {
            setter.Invoke(provider, pendingValue);
            // if the provider is a struct, copy its changes back up to the next reference type
            if (provider.GetType().IsValueType)
            {
                object             newValue = provider;
                SerializedProperty parent   = sp.GetParentProperty();
                while (parent != null && !parent.isArray)
                {
                    provider = parent.GetProvider(out fieldInfo);
                    if (typeof(IList).IsAssignableFrom(fieldInfo.FieldType))
                    {
                        elementIndex = parent.GetElementIndex();
                        (fieldInfo.GetValue(provider) as IList)[elementIndex] = newValue;
                    }
                    else
                    {
                        fieldInfo.SetValue(provider, newValue);
                    }
                    if (provider.GetType().IsValueType)
                    {
                        newValue = provider;
                        parent   = parent.GetParentProperty();
                    }
                    else
                    {
                        parent = null;
                    }
                }
            }
        }
        // set dirty
        EditorUtilityX.SetDirty(objectsToUndo.ToArray());
        // update cache
        s_ValueCache[hashableProperty] = sp.GetValue();
    }