Skip to content

Instantly share code, notes, and snippets.

@gjroelofs
Last active July 19, 2023 12:33
Show Gist options
  • Save gjroelofs/e46deeba8296f617a3d0e9dc7ec1390c to your computer and use it in GitHub Desktop.
Save gjroelofs/e46deeba8296f617a3d0e9dc7ec1390c to your computer and use it in GitHub Desktop.

Revisions

  1. gjroelofs revised this gist May 23, 2020. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion Facade for OdinInspector
    Original file line number Diff line number Diff line change
    @@ -25,7 +25,7 @@ namespace Utility
    ///
    /// [HideLabel, ShowIf("@Target != null")]
    /// public Name {
    /// get => Target.Name;
    /// get => Target?.Name;
    /// set => {
    /// if(Target != null) Target.Name = value;
    /// }
  2. gjroelofs revised this gist May 23, 2020. 1 changed file with 3 additions and 1 deletion.
    4 changes: 3 additions & 1 deletion Facade for OdinInspector
    Original file line number Diff line number Diff line change
    @@ -26,7 +26,9 @@ namespace Utility
    /// [HideLabel, ShowIf("@Target != null")]
    /// public Name {
    /// get => Target.Name;
    /// set => Target.Name = value;
    /// set => {
    /// if(Target != null) Target.Name = value;
    /// }
    /// }
    ///
    /// }
  3. gjroelofs revised this gist May 23, 2020. 1 changed file with 20 additions and 11 deletions.
    31 changes: 20 additions & 11 deletions Facade for OdinInspector
    Original file line number Diff line number Diff line change
    @@ -39,23 +39,23 @@ namespace Utility
    /// <summary> Contains a mapping of the targeted Type to the concrete Facade. </summary>
    public Lazy<Dictionary< /* Foo */ Type, /* FooFacade (: Facade<Foo>) */ Type>> Facades = new Lazy<Dictionary<Type, Type>>(() =>
    {
    return TypeCache.GetTypesDerivedFrom(typeof(Facade<>))
    return TypeCache.GetTypesDerivedFrom(typeof(OdinFacade<>))
    .Where(t => !t.IsGenericTypeDefinition)
    .Select(t => Tuple.Create(
    t.GetInterfaces()
    .First(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IFacade<>))
    .First(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IOdinFacade<>))
    .GenericTypeArguments.First(), t))
    .ToDictionary(t => t.Item1, t => t.Item2);
    });

    /// <summary> Creates a getter setter that forwards the property's getter setter onto the Facade, with concrete type. </summary>
    public Lazy<MethodInfo> GetterSetter = new Lazy<MethodInfo>(() => typeof(FacadePropertyResolver).GetMethod("CreateWrapper", BindingFlags.Public | BindingFlags.Static));
    private Lazy<MethodInfo> GetterSetter = new Lazy<MethodInfo>(() => typeof(FacadePropertyResolver).GetMethod("CreateWrapper", BindingFlags.Public | BindingFlags.Static));

    public override void ProcessMemberProperties(List<InspectorPropertyInfo> propertyInfos)
    {
    // TODO: Support inheritance.
    // TODO: Cache the translation instead of doing it on every call.
    // TODO: Don't recreate the Facade every draw, but keep it alive on the Resolver.

    // Go through and replace all types for which we have a registered facade.
    for (int i = 0; i < propertyInfos.Count; i++)
    {
    var info = propertyInfos[i];
    @@ -76,27 +76,37 @@ namespace Utility
    }

    public static GetterSetter<TParent, TFacade> CreateWrapper<TParent, TWrapped, TFacade>(InspectorPropertyInfo property)
    where TFacade : Facade<TWrapped>, new()
    where TFacade : OdinFacade<TWrapped>, new()
    {
    var facade = new TFacade();
    return new GetterSetter<TParent, TFacade>(
    (ref TParent instance) => new TFacade{Target = (TWrapped) property.GetGetterSetter().GetValue(instance)},
    (ref TParent instance) => {
    facade.Target = (TWrapped) property.GetGetterSetter().GetValue(instance);
    return facade;
    },
    (ref TParent instance, TFacade value) => property.GetGetterSetter().SetValue(instance, value.Target)
    );
    }
    }

    public interface IFacade
    public interface IOdinFacade
    {
    object UntypedTarget { get; set; }
    }

    public interface IFacade<T> : IFacade
    public interface IOdinFacade<T> : IOdinFacade
    {
    /// <summary> Strongly typed target we are editing. </summary>
    T Target { get; set; }
    }

    /// <summary>
    /// Allows using a different type to construct an editor for a target type.
    /// I.e.: Given type ComplexObject, use ComplexObjectFacade : OdinFacade{ComplexObject} to define the variables & attributes to be used instead of those defined on ComplexObject.
    /// </summary>
    /// <typeparam name="T">The type of object we want to edit.</typeparam>
    [HideLabel, HideReferenceObjectPicker, InlineProperty]
    public class Facade<T> : IFacade<T>
    public class OdinFacade<T> : IOdinFacade<T>
    {
    public T Target { get; set; }

    @@ -106,5 +116,4 @@ namespace Utility
    }

    }
    }
    }
  4. gjroelofs revised this gist May 23, 2020. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions Facade for OdinInspector
    Original file line number Diff line number Diff line change
    @@ -106,4 +106,5 @@ namespace Utility
    }

    }
    }
    }
  5. gjroelofs revised this gist May 23, 2020. 1 changed file with 0 additions and 2 deletions.
    2 changes: 0 additions & 2 deletions Facade for OdinInspector
    Original file line number Diff line number Diff line change
    @@ -50,8 +50,6 @@ namespace Utility

    /// <summary> Creates a getter setter that forwards the property's getter setter onto the Facade, with concrete type. </summary>
    public Lazy<MethodInfo> GetterSetter = new Lazy<MethodInfo>(() => typeof(FacadePropertyResolver).GetMethod("CreateWrapper", BindingFlags.Public | BindingFlags.Static));

    private IFacade _temp;

    public override void ProcessMemberProperties(List<InspectorPropertyInfo> propertyInfos)
    {
  6. gjroelofs created this gist May 23, 2020.
    111 changes: 111 additions & 0 deletions Facade for OdinInspector
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,111 @@

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using Sirenix.OdinInspector;
    using Sirenix.OdinInspector.Editor;
    using UnityEngine;
    using TypeCache = UnityEditor.TypeCache;

    namespace Utility
    {

    /// <summary>
    /// Allows creating a type that will be used to render the editor in place of the given type.
    /// This allows you to use attributes / normal editor design workflow instead of having to write a custom value drawer.
    ///
    /// E.g.: Given type `ComplexObject`, create a `ComplexObjectFacade : Facade{ComplexObject}` which has all variables you want to expose and appropriate OdinInspector attributes.
    /// The `ComplexObject` will then be accessible through the `Target` variable.
    ///
    /// E.g.:
    /// <code>
    /// // The below code will only show the name of the ComplexObject, without label and if the CO is not null.
    /// public class ComplexObjectFacade : Facade{ComplexObject} {
    ///
    /// [HideLabel, ShowIf("@Target != null")]
    /// public Name {
    /// get => Target.Name;
    /// set => Target.Name = value;
    /// }
    ///
    /// }
    /// </code>
    ///
    /// </summary>
    public class FacadePropertyResolver : OdinPropertyProcessor
    {

    /// <summary> Contains a mapping of the targeted Type to the concrete Facade. </summary>
    public Lazy<Dictionary< /* Foo */ Type, /* FooFacade (: Facade<Foo>) */ Type>> Facades = new Lazy<Dictionary<Type, Type>>(() =>
    {
    return TypeCache.GetTypesDerivedFrom(typeof(Facade<>))
    .Where(t => !t.IsGenericTypeDefinition)
    .Select(t => Tuple.Create(
    t.GetInterfaces()
    .First(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IFacade<>))
    .GenericTypeArguments.First(), t))
    .ToDictionary(t => t.Item1, t => t.Item2);
    });

    /// <summary> Creates a getter setter that forwards the property's getter setter onto the Facade, with concrete type. </summary>
    public Lazy<MethodInfo> GetterSetter = new Lazy<MethodInfo>(() => typeof(FacadePropertyResolver).GetMethod("CreateWrapper", BindingFlags.Public | BindingFlags.Static));

    private IFacade _temp;

    public override void ProcessMemberProperties(List<InspectorPropertyInfo> propertyInfos)
    {
    // TODO: Cache the translation instead of doing it on every call.
    // TODO: Don't recreate the Facade every draw, but keep it alive on the Resolver.

    for (int i = 0; i < propertyInfos.Count; i++)
    {
    var info = propertyInfos[i];
    if (info.TypeOfValue == null) continue;
    if (!Facades.Value.ContainsKey(info.TypeOfValue)) continue;

    // Create a function that will given a propertyInfo, forward value get/set onto the Facade.
    var creator = GetterSetter.Value.MakeGenericMethod(info.TypeOfOwner, info.TypeOfValue, Facades.Value[info.TypeOfValue]);
    var getterSetter = (IValueGetterSetter) creator.Invoke(null, new object[]{info});

    // Draw the facade without a wrapping label.
    var newPI = InspectorPropertyInfo.CreateValue(info.PropertyName, info.Order, info.SerializationBackend, getterSetter);

    propertyInfos.RemoveAt(i);
    propertyInfos.Insert(i, newPI);
    }

    }

    public static GetterSetter<TParent, TFacade> CreateWrapper<TParent, TWrapped, TFacade>(InspectorPropertyInfo property)
    where TFacade : Facade<TWrapped>, new()
    {
    return new GetterSetter<TParent, TFacade>(
    (ref TParent instance) => new TFacade{Target = (TWrapped) property.GetGetterSetter().GetValue(instance)},
    (ref TParent instance, TFacade value) => property.GetGetterSetter().SetValue(instance, value.Target)
    );
    }
    }

    public interface IFacade
    {
    object UntypedTarget { get; set; }
    }

    public interface IFacade<T> : IFacade
    {
    T Target { get; set; }
    }

    [HideLabel, HideReferenceObjectPicker, InlineProperty]
    public class Facade<T> : IFacade<T>
    {
    public T Target { get; set; }

    public object UntypedTarget {
    get => Target;
    set => Target = (T) value;
    }

    }
    }