using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
using UnityObject = UnityEngine.Object;
namespace Unity.Tutorials.Core.Editor
{
///
/// Criterion for checking a property modification.
///
public class PropertyModificationCriterion : Criterion
{
internal enum ValueMode
{
TargetValue = 0,
DifferentThanInitial
}
internal enum ValueType
{
Integer,
Decimal,
Text,
Boolean,
Color,
}
internal string PropertyPath { get => m_PropertyPath; set => m_PropertyPath = value; }
[SerializeField]
string m_PropertyPath;
internal ValueMode TargetValueMode { get => m_TargetValueMode; set => m_TargetValueMode = value; }
[SerializeField]
ValueMode m_TargetValueMode = ValueMode.TargetValue;
// TODO: Make this more like TypedCriterion
internal string TargetValue { get => m_TargetValue; set => m_TargetValue = value; }
[SerializeField]
[Tooltip("This value only applies if the TargetValueMode is set to TargetValue. This field will have no effects in other modes.")]
string m_TargetValue;
internal ValueType TargetValueType { get => m_TargetValueType; set => m_TargetValueType = value; }
[SerializeField]
ValueType m_TargetValueType;
internal SceneObjectReference Target { get => m_Target.SceneObjectReference; set => m_Target.SceneObjectReference = value; }
[SerializeField]
ObjectReference m_Target = new ObjectReference();
[NonSerialized]
string m_InitialValue;
///
/// Starts testing of the criterion.
///
public override void StartTesting()
{
var target = m_Target.SceneObjectReference.ReferencedObject;
if (m_TargetValueMode == ValueMode.TargetValue)
IsCompleted = PropertyFulfillCriterion(target, m_PropertyPath);
else
{
var so = new SerializedObject(target);
var sp = so.FindProperty(PropertyPath);
if (sp == null)
Debug.LogWarningFormat("PropertyModificationCriterion: Cannot find property \"{0}\" on \"{1}\"", PropertyPath, target);
else
m_InitialValue = GetPropertyValueAsString(sp);
}
Undo.postprocessModifications += PostprocessModifications;
Undo.undoRedoPerformed += UpdateCompletion;
}
///
/// Stops testing of the criterion.
///
public override void StopTesting()
{
Undo.postprocessModifications -= PostprocessModifications;
Undo.undoRedoPerformed -= UpdateCompletion;
}
///
/// Evaluates if the criterion is completed.
///
///
protected override bool EvaluateCompletion()
{
var targetObject = m_Target.SceneObjectReference.ReferencedObject;
return PropertyFulfillCriterion(targetObject, m_PropertyPath);
}
UndoPropertyModification[] PostprocessModifications(UndoPropertyModification[] modifications)
{
var targetObject = m_Target.SceneObjectReference.ReferencedObject;
var modificationsToTest = GetPropertiesToTest(modifications, targetObject);
if (modificationsToTest.Any())
{
IsCompleted = modificationsToTest.Any(m => PropertyFulfillCriterion(m.target, m.propertyPath));
}
return modifications;
}
IEnumerable GetPropertiesToTest(UndoPropertyModification[] modifications, UnityObject target)
{
var result = new List();
foreach (var m in modifications)
{
if (m.currentValue.target == target)
{
if (IsCompoundPropertyMatch(m.currentValue.propertyPath))
{
var propertyModification = m.currentValue;
propertyModification.propertyPath = PropertyPath;
result.Add(m.currentValue);
}
else if (m.currentValue.propertyPath == m_PropertyPath)
result.Add(m.currentValue);
}
}
return result;
}
bool IsCompoundPropertyMatch(string propertyPath)
{
if (m_TargetValueType == ValueType.Color)
{
Regex coloRegex = new Regex(m_PropertyPath + "\\.[rgba]");
if (coloRegex.IsMatch(propertyPath))
return true;
}
return propertyPath == m_PropertyPath;
}
bool DoPropertyTypeMatches(SerializedProperty property)
{
switch (m_TargetValueType)
{
case ValueType.Decimal:
return property.propertyType == SerializedPropertyType.Float;
case ValueType.Integer:
return property.propertyType == SerializedPropertyType.Integer;
case ValueType.Text:
return property.propertyType == SerializedPropertyType.String;
case ValueType.Boolean:
return property.propertyType == SerializedPropertyType.Boolean;
case ValueType.Color:
return property.propertyType == SerializedPropertyType.Color;
default:
throw new ArgumentOutOfRangeException();
}
throw new Exception("unknown TargetValueType");
}
string GetPropertyValueAsString(SerializedProperty property)
{
switch (TargetValueType)
{
case ValueType.Decimal:
return property.floatValue.ToString();
case ValueType.Integer:
return property.intValue.ToString();
case ValueType.Text:
return property.stringValue;
case ValueType.Boolean:
return property.boolValue.ToString();
case ValueType.Color:
return property.colorValue.ToString();
}
throw new Exception("unknown TargetValueType");
}
bool DoesPropertyMatches(SerializedProperty property, string value)
{
switch (TargetValueType)
{
case ValueType.Decimal:
{
float convertedValue;
return float.TryParse(value, out convertedValue) &&
Mathf.Approximately(property.floatValue, convertedValue);
}
case ValueType.Integer:
{
int convertedValue;
return int.TryParse(value, out convertedValue) && property.intValue == convertedValue;
}
case ValueType.Text:
{
return property.stringValue == value;
}
case ValueType.Boolean:
{
bool convertedValue;
return bool.TryParse(value, out convertedValue) && property.boolValue == convertedValue;
}
case ValueType.Color:
{
Color convertedValue;
return ColorUtility.TryParseHtmlString(value, out convertedValue) && property.colorValue == convertedValue;
}
}
return false;
}
bool SetPropertyTo(SerializedProperty property, string value)
{
switch (TargetValueType)
{
case ValueType.Decimal:
{
float convertedTargetValue;
if (!float.TryParse(value, out convertedTargetValue))
return false;
property.floatValue = convertedTargetValue;
return true;
}
case ValueType.Integer:
{
int convertedTargetValue;
if (!int.TryParse(value, out convertedTargetValue))
return false;
property.intValue = convertedTargetValue;
return true;
}
case ValueType.Text:
{
property.stringValue = value;
return true;
}
case ValueType.Boolean:
{
bool convertedTargetValue;
if (!bool.TryParse(value, out convertedTargetValue))
return false;
property.boolValue = convertedTargetValue;
return true;
}
case ValueType.Color:
{
Color convertedTargetValue;
if (!ColorUtility.TryParseHtmlString(value, out convertedTargetValue))
return false;
property.colorValue = convertedTargetValue;
return true;
}
}
return false;
}
bool SetPropertyToDifferentValueThan(SerializedProperty property, string value)
{
switch (TargetValueType)
{
case ValueType.Decimal:
{
float convertedTargetValue;
if (!float.TryParse(value, out convertedTargetValue))
return false;
property.floatValue = convertedTargetValue + 1.0f;
return true;
}
case ValueType.Integer:
{
int convertedTargetValue;
if (!int.TryParse(value, out convertedTargetValue))
return false;
property.intValue = convertedTargetValue + 1;
return true;
}
case ValueType.Text:
{
property.stringValue = value + "different ";
return true;
}
case ValueType.Boolean:
{
bool convertedTargetValue;
if (!bool.TryParse(value, out convertedTargetValue))
return false;
property.boolValue = !convertedTargetValue;
return true;
}
case ValueType.Color:
{
Color convertedTargetValue;
if (!ColorUtility.TryParseHtmlString(value, out convertedTargetValue))
return false;
property.colorValue = convertedTargetValue + Color.gray;
return true;
}
}
return false;
}
bool PropertyFulfillCriterion(UnityObject target, string propertyPath)
{
if (target == null)
return false;
if (m_TargetValueMode == ValueMode.TargetValue && m_TargetValueType != ValueType.Text && string.IsNullOrEmpty(m_TargetValue))
return true;
var serializedObject = new SerializedObject(target);
var property = serializedObject.FindProperty(propertyPath);
if (property == null)
return false;
if (!DoPropertyTypeMatches(property))
return false;
switch (m_TargetValueMode)
{
case ValueMode.TargetValue:
return DoesPropertyMatches(property, m_TargetValue);
case ValueMode.DifferentThanInitial:
return !DoesPropertyMatches(property, m_InitialValue);
}
return false;
}
///
/// Auto-completes the criterion.
///
/// True if the auto-completion succeeded.
public override bool AutoComplete()
{
var target = m_Target.SceneObjectReference.ReferencedObject;
if (target == null)
return false;
if (m_TargetValueMode == ValueMode.TargetValue && m_TargetValueType != ValueType.Text && string.IsNullOrEmpty(m_TargetValue))
return false;
var serializedObject = new SerializedObject(target);
var property = serializedObject.FindProperty(m_PropertyPath);
if (property == null)
return false;
if (!DoPropertyTypeMatches(property))
return false;
switch (m_TargetValueMode)
{
case ValueMode.TargetValue:
{
if (!SetPropertyTo(property, TargetValue))
return false;
break;
}
case ValueMode.DifferentThanInitial:
{
if (!SetPropertyToDifferentValueThan(property, m_InitialValue))
return false;
break;
}
}
serializedObject.ApplyModifiedProperties();
return true;
}
}
}