diff --git a/src/UI/CacheObject/CacheObjectBase.cs b/src/UI/CacheObject/CacheObjectBase.cs
index 76010f9..549b8a8 100644
--- a/src/UI/CacheObject/CacheObjectBase.cs
+++ b/src/UI/CacheObject/CacheObjectBase.cs
@@ -18,7 +18,7 @@ namespace UnityExplorer.UI.CacheObject
{
NotEvaluated,
Exception,
- NullValue,
+ //NullValue,
Boolean,
Number,
String,
@@ -38,6 +38,7 @@ namespace UnityExplorer.UI.CacheObject
public object Value { get; protected set; }
public Type FallbackType { get; protected set; }
+ public bool LastValueWasNull { get; private set; }
public InteractiveValue IValue { get; private set; }
public Type CurrentIValueType { get; private set; }
@@ -55,7 +56,7 @@ namespace UnityExplorer.UI.CacheObject
public virtual void SetFallbackType(Type fallbackType)
{
this.FallbackType = fallbackType;
- GetValueLabel();
+ this.ValueLabelText = GetValueLabel();
}
// internals
@@ -104,6 +105,7 @@ namespace UnityExplorer.UI.CacheObject
public abstract void TrySetUserValue(object value);
+ // The only method which sets the CacheObjectBase.Value
public virtual void SetValueFromSource(object value)
{
this.Value = value;
@@ -111,88 +113,122 @@ namespace UnityExplorer.UI.CacheObject
if (!Value.IsNullOrDestroyed())
Value = Value.TryCast();
- var prevState = State;
ProcessOnEvaluate();
- if (State != prevState)
- {
- if (this.IValue != null)
- {
- // State has changed, need to return IValue
- ReleaseIValue();
- SubContentShowWanted = false;
- }
- }
-
if (this.IValue != null)
this.IValue.SetValue(Value);
}
- ///
- /// Process the CacheMember state when the value has been evaluated (or re-evaluated)
- ///
protected virtual void ProcessOnEvaluate()
{
+ var prevState = State;
+
if (HadException)
+ {
+ LastValueWasNull = true;
State = ValueState.Exception;
+ }
else if (Value.IsNullOrDestroyed())
- State = ValueState.NullValue;
+ {
+ LastValueWasNull = true;
+ State = GetStateForType(FallbackType);
+ }
else
{
- var type = Value.GetActualType();
+ LastValueWasNull = false;
+ State = GetStateForType(Value.GetActualType());
+ }
- if (type == typeof(bool))
- State = ValueState.Boolean;
- else if (type.IsPrimitive || type == typeof(decimal))
- State = ValueState.Number;
- else if (type == typeof(string))
- State = ValueState.String;
- else if (type.IsEnum)
- State = ValueState.Enum;
-
- // todo Color and ValueStruct
-
- else if (typeof(IDictionary).IsAssignableFrom(type))
- State = ValueState.Dictionary;
- else if (typeof(IEnumerable).IsAssignableFrom(type))
- State = ValueState.Collection;
- else
- State = ValueState.Unsupported;
+ if (IValue != null)
+ {
+ // If we changed states (always needs IValue change)
+ // or if the value is null, and the fallback type isnt string (we always want to edit strings).
+ if (State != prevState || (State != ValueState.String && Value.IsNullOrDestroyed()))
+ {
+ // need to return IValue
+ ReleaseIValue();
+ SubContentShowWanted = false;
+ }
}
// Set label text
- GetValueLabel();
+ this.ValueLabelText = GetValueLabel();
}
- protected void GetValueLabel()
+ public ValueState GetStateForType(Type type)
{
- string label;
+ if (type == typeof(bool))
+ return ValueState.Boolean;
+ else if (type.IsPrimitive || type == typeof(decimal))
+ return ValueState.Number;
+ else if (type == typeof(string))
+ return ValueState.String;
+ else if (type.IsEnum)
+ return ValueState.Enum;
+
+ // todo Color and ValueStruct
+
+ else if (typeof(IDictionary).IsAssignableFrom(type))
+ return ValueState.Dictionary;
+ else if (typeof(IEnumerable).IsAssignableFrom(type))
+ return ValueState.Collection;
+ else
+ return ValueState.Unsupported;
+ }
+
+ protected string GetValueLabel()
+ {
+ string label = "";
+
switch (State)
{
- case ValueState.NotEvaluated:
- label = $"{NOT_YET_EVAL} ({SignatureHighlighter.Parse(FallbackType, true)})"; break;
- case ValueState.Exception:
- label = $"{LastException.ReflectionExToString()}"; break;
+ // bool and number dont want the label for the value at all
case ValueState.Boolean:
case ValueState.Number:
- label = null; break;
+ return null;
+
+ case ValueState.NotEvaluated:
+ return $"{NOT_YET_EVAL} ({SignatureHighlighter.Parse(FallbackType, true)})";
+
+ case ValueState.Exception:
+ return $"{LastException.ReflectionExToString()}";
+
case ValueState.String:
- string s = Value as string;
- if (s.Length > 200)
- s = $"{s.Substring(0, 200)}...";
- label = $"\"{s}\""; break;
- case ValueState.NullValue:
- label = $"{ToStringUtility.ToStringWithType(Value, FallbackType, true)}"; break;
- case ValueState.Enum:
+ if (!LastValueWasNull)
+ {
+ string s = Value as string;
+ if (s.Length > 200)
+ s = $"{s.Substring(0, 200)}...";
+ return $"\"{s}\"";
+ }
+ break;
+
case ValueState.Collection:
+ if (!LastValueWasNull)
+ {
+ if (Value is IList iList)
+ label = $"[{iList.Count}] ";
+ else if (Value is ICollection iCol)
+ label = $"[{iCol.Count}] ";
+ else
+ label = "[?] ";
+ }
+ break;
+
case ValueState.Dictionary:
- case ValueState.ValueStruct:
- case ValueState.Color:
- case ValueState.Unsupported:
- default:
- label = ToStringUtility.ToStringWithType(Value, FallbackType, true); break;
+ if (!LastValueWasNull)
+ {
+ if (Value is IDictionary iDict)
+ label = $"[{iDict.Count}] ";
+ else
+ label = "[?] ";
+ }
+ break;
}
- this.ValueLabelText = label;
+
+ // Cases which dont return will append to ToStringWithType
+
+ return label += ToStringUtility.ToStringWithType(Value, FallbackType, true);
}
// Setting cell state from our model
@@ -218,17 +254,20 @@ namespace UnityExplorer.UI.CacheObject
switch (State)
{
case ValueState.Exception:
- case ValueState.NullValue:
+ //case ValueState.NullValue:
SetValueState(cell, ValueStateArgs.Default);
break;
case ValueState.Boolean:
- SetValueState(cell, new ValueStateArgs(false, toggleActive:true, applyActive: CanWrite));
+ SetValueState(cell, new ValueStateArgs(false, toggleActive: true, applyActive: CanWrite));
break;
case ValueState.Number:
SetValueState(cell, new ValueStateArgs(false, typeLabelActive: true, inputActive: true, applyActive: CanWrite));
break;
case ValueState.String:
- SetValueState(cell, new ValueStateArgs(true, false, SignatureHighlighter.StringOrange, subContentButtonActive: true));
+ if (LastValueWasNull)
+ SetValueState(cell, new ValueStateArgs(true, subContentButtonActive: true));
+ else
+ SetValueState(cell, new ValueStateArgs(true, false, SignatureHighlighter.StringOrange, subContentButtonActive: true));
break;
case ValueState.Enum:
SetValueState(cell, new ValueStateArgs(true, subContentButtonActive: CanWrite));
@@ -237,10 +276,10 @@ namespace UnityExplorer.UI.CacheObject
case ValueState.Dictionary:
case ValueState.ValueStruct:
case ValueState.Color:
- SetValueState(cell, new ValueStateArgs(true, inspectActive: true, subContentButtonActive: true));
+ SetValueState(cell, new ValueStateArgs(true, inspectActive: !LastValueWasNull, subContentButtonActive: !LastValueWasNull));
break;
case ValueState.Unsupported:
- SetValueState(cell, new ValueStateArgs(true, inspectActive: true));
+ SetValueState(cell, new ValueStateArgs(true, inspectActive: !LastValueWasNull));
break;
}
@@ -249,6 +288,7 @@ namespace UnityExplorer.UI.CacheObject
protected virtual void SetValueState(CacheObjectCell cell, ValueStateArgs args)
{
+ // main value label
if (args.valueActive)
{
cell.ValueLabel.text = ValueLabelText;
@@ -258,10 +298,12 @@ namespace UnityExplorer.UI.CacheObject
else
cell.ValueLabel.text = "";
+ // Type label (for primitives)
cell.TypeLabel.gameObject.SetActive(args.typeLabelActive);
if (args.typeLabelActive)
cell.TypeLabel.text = SignatureHighlighter.Parse(Value.GetActualType(), false);
+ // toggle for bools
cell.Toggle.gameObject.SetActive(args.toggleActive);
if (args.toggleActive)
{
@@ -270,6 +312,7 @@ namespace UnityExplorer.UI.CacheObject
cell.ToggleText.text = Value.ToString();
}
+ // inputfield for numbers
cell.InputField.gameObject.SetActive(args.inputActive);
if (args.inputActive)
{
@@ -277,9 +320,13 @@ namespace UnityExplorer.UI.CacheObject
cell.InputField.readOnly = !CanWrite;
}
+ // apply for bool and numbers
cell.ApplyButton.Button.gameObject.SetActive(args.applyActive);
- cell.InspectButton.Button.gameObject.SetActive(args.inspectActive);
- cell.SubContentButton.Button.gameObject.SetActive(args.subContentButtonActive);
+
+ // Inspect and IValue (subcontent) buttons - only if last value not null.
+ cell.InspectButton.Button.gameObject.SetActive(args.inspectActive && !LastValueWasNull);
+ // allow IValue for null strings though.
+ cell.SubContentButton.Button.gameObject.SetActive(args.subContentButtonActive && (!LastValueWasNull || State == ValueState.String));
}
// CacheObjectCell Apply
diff --git a/src/UI/Utility/ToStringUtility.cs b/src/UI/Utility/ToStringUtility.cs
index e5bcdb6..99b8e44 100644
--- a/src/UI/Utility/ToStringUtility.cs
+++ b/src/UI/Utility/ToStringUtility.cs
@@ -16,8 +16,8 @@ namespace UnityExplorer.UI.Utility
internal static Dictionary toStringFormattedMethods = new Dictionary();
// string allocs
- private static readonly StringBuilder _stringBuilder = new StringBuilder(16384);
private const string nullString = "null";
+ private const string nullUnknown = nullString + " (?)";
private const string destroyedString = "Destroyed";
private const string untitledString = "untitled";
@@ -25,28 +25,28 @@ namespace UnityExplorer.UI.Utility
public static string ToStringWithType(object value, Type fallbackType, bool includeNamespace = true)
{
- if (value == null && fallbackType == null)
- return nullString;
+ if (value.IsNullOrDestroyed() && fallbackType == null)
+ return nullUnknown;
Type type = value?.GetActualType() ?? fallbackType;
string richType = SignatureHighlighter.Parse(type, includeNamespace);
- _stringBuilder.Clear();
+ var sb = new StringBuilder();
if (value.IsNullOrDestroyed())
{
if (value == null)
{
- _stringBuilder.Append(nullString);
- AppendRichType(_stringBuilder, richType);
- return _stringBuilder.ToString();
+ sb.Append(nullString);
+ AppendRichType(sb, richType);
+ return sb.ToString();
}
else // destroyed unity object
{
- _stringBuilder.Append(destroyedString);
- AppendRichType(_stringBuilder, richType);
- return _stringBuilder.ToString();
+ sb.Append(destroyedString);
+ AppendRichType(sb, richType);
+ return sb.ToString();
}
}
@@ -58,57 +58,39 @@ namespace UnityExplorer.UI.Utility
else if (name.Length > 50)
name = $"{name.Substring(0, 50)}...";
- _stringBuilder.Append($"\"{name}\"");
- AppendRichType(_stringBuilder, richType);
+ sb.Append($"\"{name}\"");
+ AppendRichType(sb, richType);
}
else if (type.FullName.StartsWith(eventSystemNamespace))
{
// UnityEngine.EventSystem classes can have some obnoxious ToString results with rich text.
- _stringBuilder.Append(richType);
+ sb.Append(richType);
}
else
{
var toString = ToString(value);
- if (typeof(IEnumerable).IsAssignableFrom(type))
- {
- if (value is IList iList)
- _stringBuilder.Append($"[{iList.Count}] ");
- else
- if (value is ICollection iCol)
- _stringBuilder.Append($"[{iCol.Count}] ");
- else
- _stringBuilder.Append("[?] ");
- }
- else if (typeof(IDictionary).IsAssignableFrom(type))
- {
- if (value is IDictionary iDict)
- _stringBuilder.Append($"[{iDict.Count}] ");
- else
- _stringBuilder.Append("[?] ");
- }
-
if (type.IsGenericType
|| toString == type.FullName
|| toString == $"{type.FullName} {type.FullName}"
|| toString == $"Il2Cpp{type.FullName}" || type.FullName == $"Il2Cpp{toString}")
{
- _stringBuilder.Append(richType);
+ sb.Append(richType);
}
else // the ToString contains some actual implementation, use that value.
{
// prune long strings unless they're unity structs
// (Matrix4x4 and Rect can have some longs ones that we want to display fully)
if (toString.Length > 100 && !(type.IsValueType && type.FullName.StartsWith("UnityEngine")))
- _stringBuilder.Append(toString.Substring(0, 100));
+ sb.Append(toString.Substring(0, 100));
else
- _stringBuilder.Append(toString);
+ sb.Append(toString);
- AppendRichType(_stringBuilder, richType);
+ AppendRichType(sb, richType);
}
}
- return _stringBuilder.ToString();
+ return sb.ToString();
}
private static void AppendRichType(StringBuilder sb, string richType)