Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: Add Supports to read/write latest values in MethodButton and Property #75

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Alchemy/Assets/Alchemy/Editor/AlchemyEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public abstract class AlchemyEditor : Editor
#if ALCHEMY_SUPPORT_SERIALIZATION
const string AlchemySerializationWarning = "In the current version, fields with the [AlchemySerializedField] attribute do not support editing multiple objects.";
#endif



void OnEnable()
{
Expand Down Expand Up @@ -96,6 +98,7 @@ public override VisualElement CreateInspectorGUI()

return root;
}

}

#if !ALCHEMY_DISABLE_DEFAULT_EDITOR
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public AlchemyPropertyField(SerializedProperty property, Type type, bool isArray
var drawer = AlchemyEditorUtility.CreateGroupDrawer(groupAttribute, targetType);

var root = drawer.CreateRootElement(labelText);
InspectorHelper.BuildElements(property.serializedObject, root, property.GetValue<object>(), name => property.FindPropertyRelative(name));
InspectorHelper.BuildElements(property.serializedObject, root, IObjectAccessor.Create(property), name => property.FindPropertyRelative(name));
if (root is BindableElement bindableElement) bindableElement.BindProperty(property);
element = root;
}
Expand All @@ -56,7 +56,7 @@ public AlchemyPropertyField(SerializedProperty property, Type type, bool isArray

var clickable = InternalAPIHelper.GetClickable(foldout.Q<Toggle>());
InternalAPIHelper.SetAcceptClicksIfDisabled(clickable, true);
InspectorHelper.BuildElements(property.serializedObject, foldout, property.GetValue<object>(), name => property.FindPropertyRelative(name));
InspectorHelper.BuildElements(property.serializedObject, foldout, IObjectAccessor.Create(property), name => property.FindPropertyRelative(name));
foldout.BindProperty(property);
element = foldout;
}
Expand Down
10 changes: 5 additions & 5 deletions Alchemy/Assets/Alchemy/Editor/Elements/ClassField.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ namespace Alchemy.Editor.Elements
{
public sealed class ClassField : VisualElement
{
public ClassField(Type type, string label) : this(TypeHelper.CreateDefaultInstance(type), type, label) { }
public ClassField(object obj, Type type, string label)
// public ClassField(Type type, string label) : this(TypeHelper.CreateDefaultInstance(type), type, label) { }
public ClassField(IObjectAccessor accessor, Type type, string label)
{
var foldout = new Foldout
{
Expand Down Expand Up @@ -44,14 +44,14 @@ public ClassField(object obj, Type type, string label)
// Add member elements
foreach (var member in node.Members.OrderByAttributeThenByMemberType())
{
var element = new ReflectionField(obj, member);
var element = new ReflectionField(accessor, member);
element.style.width = Length.Percent(100f);
element.OnValueChanged += x => OnValueChanged?.Invoke(obj);
element.OnValueChanged += x => OnValueChanged?.Invoke(accessor.Target);

var e = node.Drawer?.GetGroupElement(member.GetCustomAttribute<PropertyGroupAttribute>());
if (e == null) node.VisualElement.Add(element);
else e.Add(element);
AlchemyAttributeDrawer.ExecutePropertyDrawers(null, null, obj, member, element);
AlchemyAttributeDrawer.ExecutePropertyDrawers(null, null, accessor.Target, member, element);
}
}

Expand Down
6 changes: 3 additions & 3 deletions Alchemy/Assets/Alchemy/Editor/Elements/DictionaryField.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,14 @@ public Item(object collection, object keyValuePair)
};
box.Add(keyValueElement);

keyField = new GenericField(key, keyType, KeyName)
keyField = new GenericField(new IdentityAccessor(key), keyType, KeyName)
{
style = { flexGrow = 1f }
};
keyField.OnValueChanged += SetKey;
keyValueElement.Add(keyField);

valueField = new GenericField(value, valueType, ValueName)
valueField = new GenericField(new DelegateAccessor(()=>value,null), valueType, ValueName)
{
style = { flexGrow = 1f }
};
Expand Down
73 changes: 41 additions & 32 deletions Alchemy/Assets/Alchemy/Editor/Elements/GenericField.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@ namespace Alchemy.Editor.Elements
public sealed class GenericField : VisualElement
{
const string CreateButtonText = "Create...";

public GenericField(object obj, Type type, string label,bool isDelayed = false)
public GenericField(IObjectAccessor accessor, Type type, string label,bool isDelayed = false)
{
Build(obj, type, label, isDelayed);
Build(accessor, type, label, isDelayed);
GUIHelper.ScheduleAdjustLabelWidth(this);
}

void Build(object obj, Type type, string label, bool isDelayed)
void Build(IObjectAccessor accessor, Type type, string label, bool isDelayed)
{
Clear();

var obj = accessor.Target;
// Add [Create...] button
if (obj == null && !typeof(UnityEngine.Object).IsAssignableFrom(type))
{
Expand Down Expand Up @@ -52,7 +52,7 @@ void Build(object obj, Type type, string label, bool isDelayed)
nullLabelElement.Add(new Button(() =>
{
var instance = "";
Build(instance, type, label, isDelayed);
Build(new IdentityAccessor(instance), type, label, isDelayed);
OnValueChanged?.Invoke(instance);
})
{
Expand All @@ -64,7 +64,7 @@ void Build(object obj, Type type, string label, bool isDelayed)
nullLabelElement.Add(new Button(() =>
{
var instance = Activator.CreateInstance(type, Activator.CreateInstance(type.GenericTypeArguments[0]));
Build(instance, type, label, isDelayed);
Build(new IdentityAccessor(instance), type, label, isDelayed);
OnValueChanged?.Invoke(instance);
})
{
Expand All @@ -76,7 +76,7 @@ void Build(object obj, Type type, string label, bool isDelayed)
nullLabelElement.Add(new Button(() =>
{
var instance = TypeHelper.CreateDefaultInstance(type);
Build(instance, type, label, isDelayed);
Build(new IdentityAccessor(instance), type, label, isDelayed);
OnValueChanged?.Invoke(instance);
})
{
Expand All @@ -89,7 +89,6 @@ void Build(object obj, Type type, string label, bool isDelayed)
return;
}

this.isDelayed = isDelayed;

if (type == typeof(bool))
{
Expand Down Expand Up @@ -246,36 +245,46 @@ void Build(object obj, Type type, string label, bool isDelayed)
}
else
{
var field = new ClassField(obj, type, label);
var field = new ClassField(accessor, type, label);
field.OnValueChanged += x => OnValueChanged?.Invoke(x);
Add(field);
}
}

public event Action<object> OnValueChanged;
bool isDelayed;
bool changed;

void AddField<T>(BaseField<T> control, T value)
{
control.value = value;
if (isDelayed && control is not ObjectField) // ignore ObjectField
void AddField<T>(BaseField<T> control, T value)
{
control.RegisterValueChangedCallback(x => changed = true);
control.RegisterCallback<FocusOutEvent>(x =>
control.value = value;

if (isDelayed && control is not ObjectField) // ignore ObjectField
{
if (changed)
var changed = false;
control.RegisterValueChangedCallback(_ => changed = true);
control.RegisterCallback<FocusOutEvent>(_ =>
{
OnValueChanged?.Invoke(control.value);
changed = false;
}
});
}
else
{
control.RegisterValueChangedCallback(x => OnValueChanged?.Invoke(x.newValue));
if (changed)
{
OnValueChanged?.Invoke(control.value);
changed = false;
}
});
}
else
{
control.RegisterValueChangedCallback(x =>
{
OnValueChanged?.Invoke(x.newValue);

});
}
Add(control);
if (type.IsValueType&&accessor is not IdentityAccessor)
{
control.schedule.Execute(() =>
{
control.value = (T)accessor.Target;
}).Until(()=>control.parent==null);
}
}
Add(control);
}

public event Action<object> OnValueChanged;
}
}
7 changes: 4 additions & 3 deletions Alchemy/Assets/Alchemy/Editor/Elements/HashSetField.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public override HashMapItemBase CreateItem(object collection, object elementObj,

public sealed class Item : HashMapItemBase
{
public Item(object collection, object elementObj, string label)
public Item(object collection, object elementObj, string label)
{
var box = new Box()
{
Expand All @@ -54,13 +54,14 @@ public Item(object collection, object elementObj, string label)
};

var valueType = elementObj == null ? collection.GetType().GenericTypeArguments[0] : elementObj.GetType();

inputField = new GenericField(elementObj, valueType, label);
var accessor = new IdentityAccessor(elementObj);
inputField = new GenericField(accessor, valueType, label);
inputField.style.flexGrow = 1f;
inputField.OnValueChanged += x =>
{
value = x;
OnValueChanged?.Invoke(x);
accessor.Target = x;
};
box.Add(inputField);

Expand Down
2 changes: 1 addition & 1 deletion Alchemy/Assets/Alchemy/Editor/Elements/ListField.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public ListField(IList target, string label)
var value = list[index];
var listType = list.GetType();
var valueType = value != null ? value.GetType() : listType.IsGenericType ? listType.GenericTypeArguments[0] : typeof(object);
var fieldElement = new GenericField(value, valueType, label);
var fieldElement = new GenericField(new DelegateAccessor(()=>list[((Item)element).index] ,newValue=>list[((Item)element).index]=newValue), valueType, label);
element.Add(fieldElement);
var labelElement = fieldElement.Q<Label>();
if (labelElement != null) labelElement.text = "Element " + index;
Expand Down
18 changes: 14 additions & 4 deletions Alchemy/Assets/Alchemy/Editor/Elements/MethodButton.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,19 @@ public sealed class MethodButton : VisualElement
{
const string ButtonLabelText = "Invoke";

public MethodButton(object target, MethodInfo methodInfo)
public MethodButton(IObjectAccessor accessor, MethodInfo methodInfo)
{
var parameters = methodInfo.GetParameters();

// Create parameterless button
if (parameters.Length == 0)
{
button = new Button(() => methodInfo.Invoke(target, null))
button = new Button(() =>
{
var target = accessor.Target;
methodInfo.Invoke(target, null);
accessor.Target = target;
})
{
text = methodInfo.Name
};
Expand All @@ -40,7 +45,12 @@ public MethodButton(object target, MethodInfo methodInfo)
InternalAPIHelper.GetClickable(foldout.Q<Toggle>()), true
);

button = new Button(() => methodInfo.Invoke(target, parameterObjects))
button = new Button(() =>
{
var target = accessor.Target;
methodInfo.Invoke(target, parameterObjects);
accessor.Target = target;
})
{
text = ButtonLabelText,
style = {
Expand All @@ -60,7 +70,7 @@ public MethodButton(object target, MethodInfo methodInfo)
var index = i;
var parameter = parameters[index];
parameterObjects[index] = TypeHelper.CreateDefaultInstance(parameter.ParameterType);
var element = new GenericField(parameterObjects[index], parameter.ParameterType, ObjectNames.NicifyVariableName(parameter.Name));
var element = new GenericField( new IdentityAccessor(parameterObjects[index]) , parameter.ParameterType, ObjectNames.NicifyVariableName(parameter.Name));
element.OnValueChanged += x => parameterObjects[index] = x;
element.style.paddingRight = 4f;
foldout.Add(element);
Expand Down
44 changes: 25 additions & 19 deletions Alchemy/Assets/Alchemy/Editor/Elements/ReflectionField.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,67 +9,73 @@ namespace Alchemy.Editor.Elements
{
public sealed class ReflectionField : VisualElement
{
public ReflectionField(object target, MemberInfo memberInfo)
public ReflectionField(IObjectAccessor accessor, MemberInfo memberInfo)
{
Rebuild(target, memberInfo);
Rebuild(accessor, memberInfo);
}

public void Rebuild(object target, MemberInfo memberInfo)
public void Rebuild(IObjectAccessor accessor, MemberInfo memberInfo)
{
Clear();

if (memberInfo is MethodInfo methodInfo)
{
if (methodInfo.HasCustomAttribute<ButtonAttribute>())
{
var button = new MethodButton(target, methodInfo);
var button = new MethodButton(accessor, methodInfo);
Add(button);
}
return;
}

object value;
GenericField element;
switch (memberInfo)
{
default: return;
case FieldInfo fieldInfo:
value = fieldInfo.IsStatic ? fieldInfo.GetValue(null) : target == null ? TypeHelper.GetDefaultValue(fieldInfo.FieldType) : fieldInfo.GetValue(target);
var fieldAccessor = accessor.Create(fieldInfo);
var target = accessor.Target;
var fieldType = target == null ? fieldInfo.FieldType : fieldInfo.GetValue(target)?.GetType() ?? fieldInfo.FieldType;
element = new GenericField(value, fieldType, ObjectNames.NicifyVariableName(memberInfo.Name), true);
element = new GenericField(fieldAccessor, fieldType, ObjectNames.NicifyVariableName(memberInfo.Name), false);
element.OnValueChanged += x =>
{
OnBeforeValueChange?.Invoke(target);
fieldInfo.SetValue(target, x);
var currentTarget = accessor.Target;
OnBeforeValueChange?.Invoke(currentTarget);
fieldInfo.SetValue(currentTarget, x);

// Force serialization
if (target is ISerializationCallbackReceiver receiver)
if (currentTarget is ISerializationCallbackReceiver receiver)
{
receiver.OnBeforeSerialize();
}

OnValueChanged?.Invoke(target);
OnValueChanged?.Invoke(currentTarget);
accessor.Target= currentTarget;
};
break;
case PropertyInfo propertyInfo:
if (!propertyInfo.HasCustomAttribute<ShowInInspectorAttribute>()) return;
if (!propertyInfo.CanRead) return;

value = propertyInfo.GetMethod.IsStatic ? propertyInfo.GetValue(null) : target == null ? TypeHelper.GetDefaultValue(propertyInfo.PropertyType) : propertyInfo.GetValue(target);
var propertyAccessor = accessor.Create(propertyInfo);
target = accessor.Target;
var propertyType = target == null ? propertyInfo.PropertyType : propertyInfo.GetValue(target)?.GetType() ?? propertyInfo.PropertyType;
element = new GenericField(value, propertyType, ObjectNames.NicifyVariableName(memberInfo.Name), true);
element = new GenericField(propertyAccessor, propertyType, ObjectNames.NicifyVariableName(memberInfo.Name), false);
element.OnValueChanged += x =>
{
OnBeforeValueChange?.Invoke(target);
if (propertyInfo.CanWrite) propertyInfo.SetValue(target, x);
var currentTarget = accessor.Target;
OnBeforeValueChange?.Invoke(currentTarget);
if (propertyInfo.CanWrite)
{
propertyInfo.SetValue(currentTarget, x);
}

// Force serialization
if (target is ISerializationCallbackReceiver receiver)
if (currentTarget is ISerializationCallbackReceiver receiver)
{
receiver.OnBeforeSerialize();
}

OnValueChanged?.Invoke(target);
OnValueChanged?.Invoke(currentTarget);
accessor.Target= currentTarget;
};
element.SetEnabled(propertyInfo.CanWrite);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ void Rebuild(SerializedProperty property)
}
else
{
InspectorHelper.BuildElements(property.serializedObject, foldout, property.managedReferenceValue, x => property.FindPropertyRelative(x));
InspectorHelper.BuildElements(property.serializedObject, foldout, new IdentityAccessor(property.managedReferenceValue), x => property.FindPropertyRelative(x));
}

this.Bind(property.serializedObject);
Expand Down
Loading