Allow editing null strings, remove "null" ValueState

This commit is contained in:
Sinai 2021-05-07 06:26:48 +10:00
parent f080379e8a
commit 00c28f781a
2 changed files with 128 additions and 99 deletions

View File

@ -18,7 +18,7 @@ namespace UnityExplorer.UI.CacheObject
{ {
NotEvaluated, NotEvaluated,
Exception, Exception,
NullValue, //NullValue,
Boolean, Boolean,
Number, Number,
String, String,
@ -38,6 +38,7 @@ namespace UnityExplorer.UI.CacheObject
public object Value { get; protected set; } public object Value { get; protected set; }
public Type FallbackType { get; protected set; } public Type FallbackType { get; protected set; }
public bool LastValueWasNull { get; private set; }
public InteractiveValue IValue { get; private set; } public InteractiveValue IValue { get; private set; }
public Type CurrentIValueType { get; private set; } public Type CurrentIValueType { get; private set; }
@ -55,7 +56,7 @@ namespace UnityExplorer.UI.CacheObject
public virtual void SetFallbackType(Type fallbackType) public virtual void SetFallbackType(Type fallbackType)
{ {
this.FallbackType = fallbackType; this.FallbackType = fallbackType;
GetValueLabel(); this.ValueLabelText = GetValueLabel();
} }
// internals // internals
@ -104,6 +105,7 @@ namespace UnityExplorer.UI.CacheObject
public abstract void TrySetUserValue(object value); public abstract void TrySetUserValue(object value);
// The only method which sets the CacheObjectBase.Value
public virtual void SetValueFromSource(object value) public virtual void SetValueFromSource(object value)
{ {
this.Value = value; this.Value = value;
@ -111,88 +113,122 @@ namespace UnityExplorer.UI.CacheObject
if (!Value.IsNullOrDestroyed()) if (!Value.IsNullOrDestroyed())
Value = Value.TryCast(); Value = Value.TryCast();
var prevState = State;
ProcessOnEvaluate(); ProcessOnEvaluate();
if (State != prevState)
{
if (this.IValue != null)
{
// State has changed, need to return IValue
ReleaseIValue();
SubContentShowWanted = false;
}
}
if (this.IValue != null) if (this.IValue != null)
this.IValue.SetValue(Value); this.IValue.SetValue(Value);
} }
/// <summary>
/// Process the CacheMember state when the value has been evaluated (or re-evaluated)
/// </summary>
protected virtual void ProcessOnEvaluate() protected virtual void ProcessOnEvaluate()
{ {
var prevState = State;
if (HadException) if (HadException)
{
LastValueWasNull = true;
State = ValueState.Exception; State = ValueState.Exception;
}
else if (Value.IsNullOrDestroyed()) else if (Value.IsNullOrDestroyed())
State = ValueState.NullValue; {
LastValueWasNull = true;
State = GetStateForType(FallbackType);
}
else else
{ {
var type = Value.GetActualType(); LastValueWasNull = false;
State = GetStateForType(Value.GetActualType());
}
if (type == typeof(bool)) if (IValue != null)
State = ValueState.Boolean; {
else if (type.IsPrimitive || type == typeof(decimal)) // If we changed states (always needs IValue change)
State = ValueState.Number; // or if the value is null, and the fallback type isnt string (we always want to edit strings).
else if (type == typeof(string)) if (State != prevState || (State != ValueState.String && Value.IsNullOrDestroyed()))
State = ValueState.String; {
else if (type.IsEnum) // need to return IValue
State = ValueState.Enum; ReleaseIValue();
SubContentShowWanted = false;
// 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;
} }
// Set label text // 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) switch (State)
{ {
case ValueState.NotEvaluated: // bool and number dont want the label for the value at all
label = $"<i>{NOT_YET_EVAL} ({SignatureHighlighter.Parse(FallbackType, true)})</i>"; break;
case ValueState.Exception:
label = $"<i><color=red>{LastException.ReflectionExToString()}</color></i>"; break;
case ValueState.Boolean: case ValueState.Boolean:
case ValueState.Number: case ValueState.Number:
label = null; break; return null;
case ValueState.NotEvaluated:
return $"<i>{NOT_YET_EVAL} ({SignatureHighlighter.Parse(FallbackType, true)})</i>";
case ValueState.Exception:
return $"<i><color=red>{LastException.ReflectionExToString()}</color></i>";
case ValueState.String: case ValueState.String:
string s = Value as string; if (!LastValueWasNull)
if (s.Length > 200) {
s = $"{s.Substring(0, 200)}..."; string s = Value as string;
label = $"\"{s}\""; break; if (s.Length > 200)
case ValueState.NullValue: s = $"{s.Substring(0, 200)}...";
label = $"<i>{ToStringUtility.ToStringWithType(Value, FallbackType, true)}</i>"; break; return $"\"{s}\"";
case ValueState.Enum: }
break;
case ValueState.Collection: 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.Dictionary:
case ValueState.ValueStruct: if (!LastValueWasNull)
case ValueState.Color: {
case ValueState.Unsupported: if (Value is IDictionary iDict)
default: label = $"[{iDict.Count}] ";
label = ToStringUtility.ToStringWithType(Value, FallbackType, true); break; 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 // Setting cell state from our model
@ -218,17 +254,20 @@ namespace UnityExplorer.UI.CacheObject
switch (State) switch (State)
{ {
case ValueState.Exception: case ValueState.Exception:
case ValueState.NullValue: //case ValueState.NullValue:
SetValueState(cell, ValueStateArgs.Default); SetValueState(cell, ValueStateArgs.Default);
break; break;
case ValueState.Boolean: case ValueState.Boolean:
SetValueState(cell, new ValueStateArgs(false, toggleActive:true, applyActive: CanWrite)); SetValueState(cell, new ValueStateArgs(false, toggleActive: true, applyActive: CanWrite));
break; break;
case ValueState.Number: case ValueState.Number:
SetValueState(cell, new ValueStateArgs(false, typeLabelActive: true, inputActive: true, applyActive: CanWrite)); SetValueState(cell, new ValueStateArgs(false, typeLabelActive: true, inputActive: true, applyActive: CanWrite));
break; break;
case ValueState.String: 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; break;
case ValueState.Enum: case ValueState.Enum:
SetValueState(cell, new ValueStateArgs(true, subContentButtonActive: CanWrite)); SetValueState(cell, new ValueStateArgs(true, subContentButtonActive: CanWrite));
@ -237,10 +276,10 @@ namespace UnityExplorer.UI.CacheObject
case ValueState.Dictionary: case ValueState.Dictionary:
case ValueState.ValueStruct: case ValueState.ValueStruct:
case ValueState.Color: case ValueState.Color:
SetValueState(cell, new ValueStateArgs(true, inspectActive: true, subContentButtonActive: true)); SetValueState(cell, new ValueStateArgs(true, inspectActive: !LastValueWasNull, subContentButtonActive: !LastValueWasNull));
break; break;
case ValueState.Unsupported: case ValueState.Unsupported:
SetValueState(cell, new ValueStateArgs(true, inspectActive: true)); SetValueState(cell, new ValueStateArgs(true, inspectActive: !LastValueWasNull));
break; break;
} }
@ -249,6 +288,7 @@ namespace UnityExplorer.UI.CacheObject
protected virtual void SetValueState(CacheObjectCell cell, ValueStateArgs args) protected virtual void SetValueState(CacheObjectCell cell, ValueStateArgs args)
{ {
// main value label
if (args.valueActive) if (args.valueActive)
{ {
cell.ValueLabel.text = ValueLabelText; cell.ValueLabel.text = ValueLabelText;
@ -258,10 +298,12 @@ namespace UnityExplorer.UI.CacheObject
else else
cell.ValueLabel.text = ""; cell.ValueLabel.text = "";
// Type label (for primitives)
cell.TypeLabel.gameObject.SetActive(args.typeLabelActive); cell.TypeLabel.gameObject.SetActive(args.typeLabelActive);
if (args.typeLabelActive) if (args.typeLabelActive)
cell.TypeLabel.text = SignatureHighlighter.Parse(Value.GetActualType(), false); cell.TypeLabel.text = SignatureHighlighter.Parse(Value.GetActualType(), false);
// toggle for bools
cell.Toggle.gameObject.SetActive(args.toggleActive); cell.Toggle.gameObject.SetActive(args.toggleActive);
if (args.toggleActive) if (args.toggleActive)
{ {
@ -270,6 +312,7 @@ namespace UnityExplorer.UI.CacheObject
cell.ToggleText.text = Value.ToString(); cell.ToggleText.text = Value.ToString();
} }
// inputfield for numbers
cell.InputField.gameObject.SetActive(args.inputActive); cell.InputField.gameObject.SetActive(args.inputActive);
if (args.inputActive) if (args.inputActive)
{ {
@ -277,9 +320,13 @@ namespace UnityExplorer.UI.CacheObject
cell.InputField.readOnly = !CanWrite; cell.InputField.readOnly = !CanWrite;
} }
// apply for bool and numbers
cell.ApplyButton.Button.gameObject.SetActive(args.applyActive); 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 // CacheObjectCell Apply

View File

@ -16,8 +16,8 @@ namespace UnityExplorer.UI.Utility
internal static Dictionary<string, MethodInfo> toStringFormattedMethods = new Dictionary<string, MethodInfo>(); internal static Dictionary<string, MethodInfo> toStringFormattedMethods = new Dictionary<string, MethodInfo>();
// string allocs // string allocs
private static readonly StringBuilder _stringBuilder = new StringBuilder(16384);
private const string nullString = "<color=grey>null</color>"; private const string nullString = "<color=grey>null</color>";
private const string nullUnknown = nullString + " (?)";
private const string destroyedString = "<color=red>Destroyed</color>"; private const string destroyedString = "<color=red>Destroyed</color>";
private const string untitledString = "<i><color=grey>untitled</color></i>"; private const string untitledString = "<i><color=grey>untitled</color></i>";
@ -25,28 +25,28 @@ namespace UnityExplorer.UI.Utility
public static string ToStringWithType(object value, Type fallbackType, bool includeNamespace = true) public static string ToStringWithType(object value, Type fallbackType, bool includeNamespace = true)
{ {
if (value == null && fallbackType == null) if (value.IsNullOrDestroyed() && fallbackType == null)
return nullString; return nullUnknown;
Type type = value?.GetActualType() ?? fallbackType; Type type = value?.GetActualType() ?? fallbackType;
string richType = SignatureHighlighter.Parse(type, includeNamespace); string richType = SignatureHighlighter.Parse(type, includeNamespace);
_stringBuilder.Clear(); var sb = new StringBuilder();
if (value.IsNullOrDestroyed()) if (value.IsNullOrDestroyed())
{ {
if (value == null) if (value == null)
{ {
_stringBuilder.Append(nullString); sb.Append(nullString);
AppendRichType(_stringBuilder, richType); AppendRichType(sb, richType);
return _stringBuilder.ToString(); return sb.ToString();
} }
else // destroyed unity object else // destroyed unity object
{ {
_stringBuilder.Append(destroyedString); sb.Append(destroyedString);
AppendRichType(_stringBuilder, richType); AppendRichType(sb, richType);
return _stringBuilder.ToString(); return sb.ToString();
} }
} }
@ -58,57 +58,39 @@ namespace UnityExplorer.UI.Utility
else if (name.Length > 50) else if (name.Length > 50)
name = $"{name.Substring(0, 50)}..."; name = $"{name.Substring(0, 50)}...";
_stringBuilder.Append($"\"{name}\""); sb.Append($"\"{name}\"");
AppendRichType(_stringBuilder, richType); AppendRichType(sb, richType);
} }
else if (type.FullName.StartsWith(eventSystemNamespace)) else if (type.FullName.StartsWith(eventSystemNamespace))
{ {
// UnityEngine.EventSystem classes can have some obnoxious ToString results with rich text. // UnityEngine.EventSystem classes can have some obnoxious ToString results with rich text.
_stringBuilder.Append(richType); sb.Append(richType);
} }
else else
{ {
var toString = ToString(value); 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 if (type.IsGenericType
|| toString == type.FullName || toString == type.FullName
|| toString == $"{type.FullName} {type.FullName}" || toString == $"{type.FullName} {type.FullName}"
|| toString == $"Il2Cpp{type.FullName}" || type.FullName == $"Il2Cpp{toString}") || toString == $"Il2Cpp{type.FullName}" || type.FullName == $"Il2Cpp{toString}")
{ {
_stringBuilder.Append(richType); sb.Append(richType);
} }
else // the ToString contains some actual implementation, use that value. else // the ToString contains some actual implementation, use that value.
{ {
// prune long strings unless they're unity structs // prune long strings unless they're unity structs
// (Matrix4x4 and Rect can have some longs ones that we want to display fully) // (Matrix4x4 and Rect can have some longs ones that we want to display fully)
if (toString.Length > 100 && !(type.IsValueType && type.FullName.StartsWith("UnityEngine"))) if (toString.Length > 100 && !(type.IsValueType && type.FullName.StartsWith("UnityEngine")))
_stringBuilder.Append(toString.Substring(0, 100)); sb.Append(toString.Substring(0, 100));
else 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) private static void AppendRichType(StringBuilder sb, string richType)