+ public class AssetListExamples : MonoBehaviour
+ {
+ [InfoBox("The AssetList attribute work on both lists of UnityEngine.Object types and UnityEngine.Object types, but have different behaviour.")]
+ [AssetList]
+ [InlineEditor(InlineEditorModes.LargePreview)]
+ public GameObject Prefab;
+
+ [AssetList]
+ public List<PlaceableObject> PlaceableObjects;
+
+ [AssetList(Path = "Plugins/Sirenix/")]
+ [InlineEditor(InlineEditorModes.LargePreview)]
+ public UnityEngine.Object Object;
+
+ [AssetList(AutoPopulate = true)]
+ public List<PlaceableObject> PlaceableObjectsAutoPopulated;
+
+ [AssetList(LayerNames = "MyLayerName")]
+ public GameObject[] AllPrefabsWithLayerName;
+
+ [AssetList(AssetNamePrefix = "Rock")]
+ public List<GameObject> PrefabsStartingWithRock;
+
+ [AssetList(Path = "/Plugins/Sirenix/")]
+ public List<GameObject> AllPrefabsLocatedInFolder;
+
+ [AssetList(Tags = "MyTagA, MyTabB", Path = "/Plugins/Sirenix/")]
+ public List<GameObject> GameObjectsWithTag;
+
+ [AssetList(Path = "/Plugins/Sirenix/")]
+ public List<Material> AllMaterialsInSirenix;
+
+ [AssetList(Path = "/Plugins/Sirenix/")]
+ public List<ScriptableObject> AllScriptableObjects;
+
+ [InfoBox("Use a method as a custom filter for the asset list.")]
+ [AssetList(CustomFilterMethod = "HasRigidbodyComponent")]
+ public List<GameObject> MyRigidbodyPrefabs;
+
+ private bool HasRigidbodyComponent(GameObject obj)
+ {
+ return obj.GetComponent<Rigidbody>() != null;
+ }
+ }
+
+ [AssetList(Paths = "Assets/Textures|Assets/Other/Textures")]
+
+ public MyComponent : MonoBehaviour
+ {
+ [AssetsOnly]
+ public GameObject MyPrefab;
+ }
+
+
+ public class BoxGroupExamples : MonoBehaviour
+ {
+ // Box with a centered title.
+ [BoxGroup("Centered Title", centerLabel: true)]
+ public int A;
+
+ [BoxGroup("Centered Title", centerLabel: true)]
+ public int B;
+
+ [BoxGroup("Centered Title", centerLabel: true)]
+ public int C;
+
+ // Box with a title.
+ [BoxGroup("Left Oriented Title")]
+ public int D;
+
+ [BoxGroup("Left Oriented Title")]
+ public int E;
+
+ // Box with a title recieved from a field.
+ [BoxGroup("$DynamicTitle1"), LabelText("Dynamic Title")]
+ public string DynamicTitle1 = "Dynamic box title";
+
+ [BoxGroup("$DynamicTitle1")]
+ public int F;
+
+ // Box with a title recieved from a property.
+ [BoxGroup("$DynamicTitle2")]
+ public int G;
+
+ [BoxGroup("$DynamicTitle2")]
+ public int H;
+
+ // Box without a title.
+ [InfoBox("You can also hide the label of a box group.")]
+ [BoxGroup("NoTitle", false)]
+ public int I;
+
+ [BoxGroup("NoTitle")]
+ public int J;
+
+ [BoxGroup("NoTitle")]
+ public int K;
+
+ #if UNITY_EDITOR
+ public string DynamicTitle2
+ {
+ get { return UnityEditor.PlayerSettings.productName; }
+ }
+ #endif
+
+ [BoxGroup("Boxed Struct"), HideLabel]
+ public SomeStruct BoxedStruct;
+
+ public SomeStruct DefaultStruct;
+
+ [Serializable]
+ public struct SomeStruct
+ {
+ public int One;
+ public int Two;
+ public int Three;
+ }
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [Button]
+ private void Init()
+ {
+ // ...
+ }
+ }
+
+
+ public class MyBot : MonoBehaviour
+ {
+ [Button]
+ private void Jump()
+ {
+ // ...
+ }
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [Button("Function")]
+ private void MyFunction()
+ {
+ // ...
+ }
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [ButtonGroup("MyGroup")]
+ private void A()
+ {
+ // ..
+ }
+
+ [ButtonGroup("MyGroup")]
+ private void B()
+ {
+ // ..
+ }
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [ButtonGroup("First")]
+ private void A()
+ { }
+
+ [ButtonGroup("First")]
+ private void B()
+ { }
+
+ [ButtonGroup("")]
+ private void One()
+ { }
+
+ [ButtonGroup("")]
+ private void Two()
+ { }
+
+ [ButtonGroup("")]
+ private void Three()
+ { }
+ }
+
+
+ public class ColorPaletteExamples : MonoBehaviour
+ {
+ [ColorPalette]
+ public Color ColorOptions;
+
+ [ColorPalette("Underwater")]
+ public Color UnderwaterColor;
+
+ [ColorPalette("Fall"), HideLabel]
+ public Color WideColorPalette;
+
+ [ColorPalette("My Palette")]
+ public Color MyColor;
+
+ [ColorPalette("Clovers")]
+ public Color[] ColorArray;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [CustomContextMenu("My custom option", "MyAction")]
+ public Vector3 MyVector;
+
+ private void MyAction()
+ {
+ MyVector = Random.onUnitSphere;
+ }
+ }
+
+
+ public class CustomDrawerExamples : MonoBehaviour
+ {
+ public float From = 2, To = 7;
+
+ [CustomValueDrawer("MyStaticCustomDrawerStatic")]
+ public float CustomDrawerStatic;
+
+ [CustomValueDrawer("MyStaticCustomDrawerInstance")]
+ public float CustomDrawerInstance;
+
+ [CustomValueDrawer("MyStaticCustomDrawerArray")]
+ public float[] CustomDrawerArray;
+
+ #if UNITY_EDITOR
+
+ private static float MyStaticCustomDrawerStatic(float value, GUIContent label)
+ {
+ return EditorGUILayout.Slider(value, 0f, 10f);
+ }
+
+ private float MyStaticCustomDrawerInstance(float value, GUIContent label)
+ {
+ return EditorGUILayout.Slider(value, this.From, this.To);
+ }
+
+ private float MyStaticCustomDrawerArray(float value, GUIContent label)
+ {
+ return EditorGUILayout.Slider(value, this.From, this.To);
+ }
+
+ #endif
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [DetailedInfoBox("This is a message", "Here is some more details about that message")]
+ public int MyInt;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [DisableContextMenu]
+ public Vector3 MyVector;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ public bool DisableProperty;
+
+ [DisableIf("DisableProperty")]
+ public int MyInt;
+
+ public SomeEnum SomeEnumField;
+
+ [DisableIf("SomeEnumField", SomeEnum.SomeEnumMember)]
+ public string SomeString;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [EnableIf("MyDisableFunction")]
+ public int MyInt;
+
+ private bool MyDisableFunction()
+ {
+ // ...
+ }
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [DisableInEditorMode]
+ public int MyInt;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [DisableInPlayMode]
+ public int MyInt;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [DisplayAsString]
+ public string MyInt = 5;
+
+ // You can combine with to display a message in the inspector.
+ [DisplayAsString, HideLabel]
+ public string MyMessage = "This string will be displayed as text in the inspector";
+
+ [DisplayAsString(false)]
+ public string InlineMessage = "This string is very long, but has been configured to not overflow.";
+ }
+
+
+ [DontApplyToListElements]
+ [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)]
+ public sealed class VisibleIfAttribute : Attribute
+ {
+ public string MemberName { get; private set; }
+
+ public VisibleIfAttribute(string memberName)
+ {
+ this.MemberName = memberName;
+ }
+ }
+
+
+ public class InlineEditorExamples : MonoBehaviour
+ {
+ [EnableGUI]
+ public string SomeReadonlyProperty { get { return "My GUI is usually disabled." } }
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ public bool EnableProperty;
+
+ [EnableIf("EnableProperty")]
+ public int MyInt;
+
+ public SomeEnum SomeEnumField;
+
+ [EnableIf("SomeEnumField", SomeEnum.SomeEnumMember)]
+ public string SomeString;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [EnableIf("MyEnableFunction")]
+ public int MyInt;
+
+ private bool MyEnableFunction()
+ {
+ // ...
+ }
+ }
+
+
+ public enum MyEnum
+ {
+ One,
+ Two,
+ Three,
+ }
+
+ public class MyMonoBehaviour : MonoBehaviour
+ {
+ [EnumPaging]
+ public MyEnum Value;
+ }
+
+
+ public class MyComponent : MonoBehvaiour
+ {
+ [EnumToggleButtons]
+ public MyBitmaskEnum MyBitmaskEnum;
+
+ [EnumToggleButtons]
+ public MyEnum MyEnum;
+ }
+
+ [Flags]
+ public enum MyBitmaskEnum
+ {
+ A = 1 << 1, // 1
+ B = 1 << 2, // 2
+ C = 1 << 3, // 4
+ ALL = A | B | C
+ }
+
+ public enum MyEnum
+ {
+ A,
+ B,
+ C
+ }
+
+
+ public class FilePathExamples : MonoBehaviour
+ {
+ // By default, FilePath provides a path relative to the Unity project.
+ [FilePath]
+ public string UnityProjectPath;
+
+ // It is possible to provide custom parent path. Parent paths can be relative to the Unity project, or absolute.
+ [FilePath(ParentFolder = "Assets/Plugins/Sirenix")]
+ public string RelativeToParentPath;
+
+ // Using parent path, FilePath can also provide a path relative to a resources folder.
+ [FilePath(ParentFolder = "Assets/Resources")]
+ public string ResourcePath;
+
+ // Provide a comma seperated list of allowed extensions. Dots are optional.
+ [FilePath(Extensions = "cs")]
+ public string ScriptFiles;
+
+ // By setting AbsolutePath to true, the FilePath will provide an absolute path instead.
+ [FilePath(AbsolutePath = true)]
+ [BoxGroup("Conditions")]
+ public string AbsolutePath;
+
+ // FilePath can also be configured to show an error, if the provided path is invalid.
+ [FilePath(RequireValidPath = true)]
+ public string ValidPath;
+
+ // By default, FilePath will enforce the use of forward slashes. It can also be configured to use backslashes instead.
+ [FilePath(UseBackslashes = true)]
+ public string Backslashes;
+
+ // FilePath also supports member references with the $ symbol.
+ [FilePath(ParentFolder = "$DynamicParent", Extensions = "$DynamicExtensions")]
+ public string DynamicFilePath;
+
+ public string DynamicParent = "Assets/Plugin/Sirenix";
+
+ public string DynamicExtensions = "cs, unity, jpg";
+ }
+
+
+ public class FolderPathExamples : MonoBehaviour
+ {
+ // By default, FolderPath provides a path relative to the Unity project.
+ [FolderPath]
+ public string UnityProjectPath;
+
+ // It is possible to provide custom parent patn. ParentFolder paths can be relative to the Unity project, or absolute.
+ [FolderPath(ParentFolder = "Assets/Plugins/Sirenix")]
+ public string RelativeToParentPath;
+
+ // Using ParentFolder, FolderPath can also provide a path relative to a resources folder.
+ [FolderPath(ParentFolder = "Assets/Resources")]
+ public string ResourcePath;
+
+ // By setting AbsolutePath to true, the FolderPath will provide an absolute path instead.
+ [FolderPath(AbsolutePath = true)]
+ public string AbsolutePath;
+
+ // FolderPath can also be configured to show an error, if the provided path is invalid.
+ [FolderPath(RequireValidPath = true)]
+ public string ValidPath;
+
+ // By default, FolderPath will enforce the use of forward slashes. It can also be configured to use backslashes instead.
+ [FolderPath(UseBackslashes = true)]
+ public string Backslashes;
+
+ // FolderPath also supports member references with the $ symbol.
+ [FolderPath(ParentFolder = "$DynamicParent")]
+ public string DynamicFolderPath;
+
+ public string DynamicParent = "Assets/Plugins/Sirenix";
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [FoldoutGroup("MyGroup")]
+ public int A;
+
+ [FoldoutGroup("MyGroup")]
+ public int B;
+
+ [FoldoutGroup("MyGroup")]
+ public int C;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [FoldoutGroup("First")]
+ public int A;
+
+ [FoldoutGroup("First")]
+ public int B;
+
+ [FoldoutGroup("Second")]
+ public int C;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [GUIColor(1f, 0f, 0f)]
+ public int A;
+
+ [GUIColor(1f, 0.5f, 0f, 0.2f)]
+ public int B;
+
+ [GUIColor("GetColor")]
+ public int C;
+
+ private Color GetColor() { return this.A == 0 ? Color.red : Color.white; }
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ public bool HideProperties;
+
+ [HideIf("HideProperties")]
+ public int MyInt;
+
+ [HideIf("HideProperties", false)]
+ public string MyString;
+
+ public SomeEnum SomeEnumField;
+
+ [HideIf("SomeEnumField", SomeEnum.SomeEnumMember)]
+ public string SomeString;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [HideIf("MyVisibleFunction")]
+ public int MyHideableField;
+
+ private bool MyVisibleFunction()
+ {
+ return !this.gameObject.activeInHierarchy;
+ }
+ }
+
+ HideIfGroup allows for showing or hiding a group of properties based on a condition.
+The attribute is a group attribute and can therefore be combined with other group attributes, and even be used to show or hide entire groups.
+Note that in the vast majority of cases where you simply want to be able to control the visibility of a single group, it is better to use the VisibleIf parameter that *all* group attributes have.
+
+ public class MyComponent : MonoBehaviour
+ {
+ [HideInEditorMode]
+ public int MyInt;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [HideInPlayMode]
+ public int MyInt;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [HideLabel]
+ public GameObject MyGameObjectWithoutLabel;
+ }
+
+
+ [HideMonoScript]
+ public class MyComponent : MonoBehaviour
+ {
+ // The Script property will not be shown for this component in the inspector
+ }
+
+
+ [HideNetworkBehaviourFields]
+ public class MyComponent : NetworkBehaviour
+ {
+ // The "Network Channel" and "Network Send Interval" properties will not be shown for this component in the inspector
+ }
+
+
+ public class MyComponent : SerializedMonoBehaviour
+ {
+ [Header("Hidden Object Pickers")]
+ [Indent]
+ [HideReferenceObjectPicker]
+ public MyCustomReferenceType OdinSerializedProperty1;
+
+ [Indent]
+ [HideReferenceObjectPicker]
+ public MyCustomReferenceType OdinSerializedProperty2;
+
+ [Indent]
+ [Header("Shown Object Pickers")]
+ public MyCustomReferenceType OdinSerializedProperty3;
+
+ [Indent]
+ public MyCustomReferenceType OdinSerializedProperty4;
+
+ public class MyCustomReferenceType
+ {
+ public int A;
+ public int B;
+ public int C;
+ }
+ }
+
+
+ // The width can either be specified as percentage or pixels.
+ // All values between 0 and 1 will be treated as a percentage.
+ // If the width is 0 the column will be automatically sized.
+ // Margin-left and right can only be specified in pixels.
+
+ public class HorizontalGroupAttributeExamples : MonoBehaviour
+ {
+ [HorizontalGroup]
+ public int A;
+
+ [HideLabel, LabelWidth (150)]
+ [HorizontalGroup(150)]
+ public LayerMask B;
+
+ // LabelWidth can be helpfull when dealing with HorizontalGroups.
+ [HorizontalGroup("Group 1"), LabelWidth(15)]
+ public int C;
+
+ [HorizontalGroup("Group 1"), LabelWidth(15)]
+ public int D;
+
+ [HorizontalGroup("Group 1"), LabelWidth(15)]
+ public int E;
+
+ // Having multiple properties in a column can be achived using multiple groups. Checkout the "Combining Group Attributes" example.
+ [HorizontalGroup("Split", 0.5f, PaddingRight = 15)]
+ [BoxGroup("Split/Left"), LabelWidth(15)]
+ public int L;
+
+ [BoxGroup("Split/Right"), LabelWidth(15)]
+ public int M;
+
+ [BoxGroup("Split/Left"), LabelWidth(15)]
+ public int N;
+
+ [BoxGroup("Split/Right"), LabelWidth(15)]
+ public int O;
+
+ // Horizontal Group also has supprot for: Title, MarginLeft, MarginRight, PaddingLeft, PaddingRight, MinWidth and MaxWidth.
+ [HorizontalGroup("MyButton", MarginLeft = 0.25f, MarginRight = 0.25f)]
+ public void SomeButton()
+ {
+
+ }
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [Indent]
+ public int IndentedInt;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [InfoBox("This is an int property")]
+ public int MyInt;
+
+ [InfoBox("This info box is a warning", InfoMessageType.Warning)]
+ public float MyFloat;
+
+ [InfoBox("This info box is an error", InfoMessageType.Error)]
+ public object MyObject;
+
+ [InfoBox("This info box is just a box", InfoMessageType.None)]
+ public Vector3 MyVector;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [InfoBox("This info box is hidden by an instance field.", "InstanceShowInfoBoxField")]
+ public int MyInt;
+ public bool InstanceShowInfoBoxField;
+
+ [InfoBox("This info box is hideable by a static field.", "StaticShowInfoBoxField")]
+ public float MyFloat;
+ public static bool StaticShowInfoBoxField;
+
+ [InfoBox("This info box is hidden by an instance property.", "InstanceShowInfoBoxProperty")]
+ public int MyOtherInt;
+ public bool InstanceShowInfoBoxProperty { get; set; }
+
+ [InfoBox("This info box is hideable by a static property.", "StaticShowInfoBoxProperty")]
+ public float MyOtherFloat;
+ public static bool StaticShowInfoBoxProperty { get; set; }
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [InfoBox("This info box is hidden by an instance function.", "InstanceShowFunction")]
+ public int MyInt;
+ public bool InstanceShowFunction()
+ {
+ return this.MyInt == 0;
+ }
+
+ [InfoBox("This info box is hidden by a static function.", "StaticShowFunction")]
+ public short MyShort;
+ public bool StaticShowFunction()
+ {
+ return true;
+ }
+
+ // You can also specify a function with the same type of parameter.
+ // Use this to specify the same function, for multiple different properties.
+ [InfoBox("This info box is hidden by an instance function with a parameter.", "InstanceShowParameterFunction")]
+ public GameObject MyGameObject;
+ public bool InstanceShowParameterFunction(GameObject property)
+ {
+ return property != null;
+ }
+
+ [InfoBox("This info box is hidden by a static function with a parameter.", "StaticShowParameterFunction")]
+ public Vector3 MyVector;
+ public bool StaticShowParameterFunction(Vector3 property)
+ {
+ return property.magnitude == 0f;
+ }
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ // Adds a button to the end of the A property.
+ [InlineButton("MyFunction")]
+ public int A;
+
+ // This is example demonstrates how you can change the label of the button.
+ // InlineButton also supports refering to string members with $.
+ [InlineButton("MyFunction", "Button")]
+ public int B;
+
+ private void MyFunction()
+ {
+ // ...
+ }
+ }
+
+
+ public class InlineEditorExamples : MonoBehaviour
+ {
+ [DisableInInlineEditors]
+ public Vector3 DisabledInInlineEditors;
+
+ [HideInInlineEditors]
+ public Vector3 HiddenInInlineEditors;
+
+ [InlineEditor]
+ public Transform InlineComponent;
+
+ [InlineEditor(InlineEditorModes.FullEditor)]
+ public Material FullInlineEditor;
+
+ [InlineEditor(InlineEditorModes.GUIAndHeader)]
+ public Material InlineMaterial;
+
+ [InlineEditor(InlineEditorModes.SmallPreview)]
+ public Material[] InlineMaterialList;
+
+ [InlineEditor(InlineEditorModes.LargePreview)]
+ public GameObject InlineObjectPreview;
+
+ [InlineEditor(InlineEditorModes.LargePreview)]
+ public Mesh InlineMeshPreview;
+ }
+
+
+ public class InlinePropertyExamples : MonoBehaviour
+ {
+ public Vector3 Vector3;
+
+ public Vector3Int Vector3Int;
+
+ [InlineProperty(LabelWidth = 12)] // It can be placed on classes as well as members
+ public Vector2Int Vector2Int;
+
+ }
+
+ [Serializable]
+ [InlineProperty(LabelWidth = 12)] // It can be placed on classes as well as members
+ public struct Vector3Int
+ {
+ [HorizontalGroup]
+ public int X;
+
+ [HorizontalGroup]
+ public int Y;
+
+ [HorizontalGroup]
+ public int Z;
+ }
+
+ [Serializable]
+ public struct Vector2Int
+ {
+ [HorizontalGroup]
+ public int X;
+
+ [HorizontalGroup]
+ public int Y;
+ }
+
+
+ public MyComponent : MonoBehaviour
+ {
+ [LabelText("1")]
+ public int MyInt1;
+
+ [LabelText("2")]
+ public int MyInt2;
+
+ [LabelText("3")]
+ public int MyInt3;
+ }
+
+
+ public MyComponent : MonoBehaviour
+ {
+ [LabelWidth("3")]
+ public int MyInt3;
+ }
+
+
+ [ListDrawerSettings(HideAddButton = true, OnTitleBarGUI = "DrawTitleBarGUI")]
+ public List<MyType> SomeList;
+
+ #if UNITY_EDITOR
+ private void DrawTitleBarGUI()
+ {
+ if (SirenixEditorGUI.ToolbarButton(EditorIcons.Plus))
+ {
+ this.SomeList.Add(new MyType());
+ }
+ }
+ #endif
+
+
+ public class Car : MonoBehaviour
+ {
+ // The speed of the car must be less than or equal to 200.
+ [MaxValue(200)]
+ public float Speed;
+ }
+
+
+ public class Health : MonoBehaviour
+ {
+ // The speed value must be between 0 and 200.
+ [MinValue(0), MaxValue(200)]
+ public float Speed;
+ }
+
+
+ public class Player : MonoBehaviour
+ {
+ [MinMaxSlider(4, 5)]
+ public Vector2 SpawnRadius;
+ }
+
+
+ public class Player : MonoBehaviour
+ {
+ // The life value must be set to at least 1.
+ [MinValue(1)]
+ public int Life;
+ }
+
+
+ public class Health : MonoBehaviour
+ {
+ // The health value must be between 0 and 100.
+ [MinValue(0), MaxValue(100)]
+ public float Health;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [MultiLineProperty]
+ public string MyString;
+
+ [ShowInInspector, MultiLineProperty(10)]
+ public string PropertyString;
+ }
+
+
+ [OnCollectionChanged("Before", "After")]
+ public List<string> list;
+
+ public void Before(CollectionChangeInfo info)
+ {
+ if (info.ChangeType == CollectionChangeType.Add || info.ChangeType == CollectionChangeType.Insert)
+ {
+ Debug.Log("Adding to the list!");
+ }
+ else if (info.ChangeType == CollectionChangeType.RemoveIndex || info.ChangeType == CollectionChangeType.RemoveValue)
+ {
+ Debug.Log("Removing from the list!");
+ }
+ }
+
+ public void After(CollectionChangeInfo info)
+ {
+ if (info.ChangeType == CollectionChangeType.Add || info.ChangeType == CollectionChangeType.Insert)
+ {
+ Debug.Log("Finished adding to the list!");
+ }
+ else if (info.ChangeType == CollectionChangeType.RemoveIndex || info.ChangeType == CollectionChangeType.RemoveValue)
+ {
+ Debug.Log("Finished removing from the list!");
+ }
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [OnInspectorDispose(@"@UnityEngine.Debug.Log(""Dispose event invoked!"")")]
+ [ShowInInspector, InfoBox("When you change the type of this field, or set it to null, the former property setup is disposed. The property setup will also be disposed when you deselect this example."), DisplayAsString]
+ public BaseClass PolymorphicField;
+
+ public abstract class BaseClass { public override string ToString() { return this.GetType().Name; } }
+ public class A : BaseClass { }
+ public class B : BaseClass { }
+ public class C : BaseClass { }
+ }
+
+
+ public MyComponent : MonoBehaviour
+ {
+ [OnInspectorGUI]
+ private void MyInspectorGUI()
+ {
+ GUILayout.Label("Label drawn from callback");
+ }
+ }
+
+
+ public MyComponent : MonoBehaviour
+ {
+ [OnInspectorGUI("MyInspectorGUI", false)]
+ public int MyField;
+
+ private void MyInspectorGUI()
+ {
+ GUILayout.Label("Label before My Field property");
+ }
+ }
+
+
+ public MyComponent : MonoBehaviour
+ {
+ [OnInspectorGUI("GUIBefore", "GUIAfter")]
+ public int MyField;
+
+ private void GUIBefore()
+ {
+ GUILayout.Label("Label before My Field property");
+ }
+
+ private void GUIAfter()
+ {
+ GUILayout.Label("Label after My Field property");
+ }
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ // Display current time for reference.
+ [ShowInInspector, DisplayAsString, PropertyOrder(-1)]
+ public string CurrentTime { get { GUIHelper.RequestRepaint(); return DateTime.Now.ToString(); } }
+
+ // OnInspectorInit executes the first time this string is about to be drawn in the inspector.
+ // It will execute again when the example is reselected.
+ [OnInspectorInit("@TimeWhenExampleWasOpened = DateTime.Now.ToString()")]
+ public string TimeWhenExampleWasOpened;
+
+ // OnInspectorInit will not execute before the property is actually "resolved" in the inspector.
+ // Remember, Odin's property system is lazily evaluated, and so a property does not actually exist
+ // and is not initialized before something is actually asking for it.
+ //
+ // Therefore, this OnInspectorInit attribute won't execute until the foldout is expanded.
+ [FoldoutGroup("Delayed Initialization", Expanded = false, HideWhenChildrenAreInvisible = false)]
+ [OnInspectorInit("@TimeFoldoutWasOpened = DateTime.Now.ToString()")]
+ public string TimeFoldoutWasOpened;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [OnStateUpdate("@$property.State.Visible = ToggleMyInt")]
+ public int MyInt;
+
+ public bool ToggleMyInt;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [OnStateUpdate("@$property.State.Expanded = ExpandList")]
+ public List<string> list;
+
+ public bool ExpandList;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ public List>string< list;
+
+ [OnStateUpdate("@#(list).State.Expanded = $value")]
+ public bool ExpandList;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [OnValueChanged("MyCallback")]
+ public int MyInt;
+
+ private void MyCallback()
+ {
+ // ..
+ }
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [OnValueChanged("OnPrefabChange")]
+ public GameObject MyPrefab;
+
+ // RigidBody component of MyPrefab.
+ [SerializeField, HideInInspector]
+ private RigidBody myPrefabRigidbody;
+
+ private void OnPrefabChange()
+ {
+ if(MyPrefab != null)
+ {
+ myPrefabRigidbody = MyPrefab.GetComponent<Rigidbody>();
+ }
+ else
+ {
+ myPrefabRigidbody = null;
+ }
+ }
+ }
+
+
+ public MyComponent : MonoBehaviour
+ {
+ [PreviewField]
+ public UnityEngine.Object SomeObject;
+
+ [PreviewField]
+ public Texture SomeTexture;
+
+ [HorizontalGroup, HideLabel, PreviewField(30)]
+ public Material A, B, C, D, F;
+ }
+
+
+ public class ProgressBarExample : MonoBehaviour
+ {
+ // Default progress bar.
+ [ProgressBar(0, 100)]
+ public int ProgressBar;
+
+ // Health bar.
+ [ProgressBar(0, 100, ColorMember = "GetHealthBarColor")]
+ public float HealthBar = 50;
+
+ private Color GetHealthBarColor(float value)
+ {
+ // Blends between red, and yellow color for when the health is below 30,
+ // and blends between yellow and green color for when the health is above 30.
+ return Color.Lerp(Color.Lerp(
+ Color.red, Color.yellow, MathUtilities.LinearStep(0f, 30f, value)),
+ Color.green, MathUtilities.LinearStep(0f, 100f, value));
+ }
+
+ // Stacked health bar.
+ // The ProgressBar attribute is placed on property, without a set method, so it can't be edited directly.
+ // So instead we have this Range attribute on a float to change the value.
+ [Range(0, 300)]
+ public float StackedHealth;
+
+ [ProgressBar(0, 100, ColorMember = "GetStackedHealthColor", BackgroundColorMember = "GetStackHealthBackgroundColor")]
+ private float StackedHealthProgressBar
+ {
+ // Loops the stacked health value between 0, and 100.
+ get { return this.StackedHealth - 100 * (int)((this.StackedHealth - 1) / 100); }
+ }
+
+ private Color GetStackedHealthColor()
+ {
+ return
+ this.StackedHealth > 200 ? Color.cyan :
+ this.StackedHealth > 100 ? Color.green :
+ Color.red;
+ }
+
+ private Color GetStackHealthBackgroundColor()
+ {
+ return
+ this.StackedHealth > 200 ? Color.green :
+ this.StackedHealth > 100 ? Color.red :
+ new Color(0.16f, 0.16f, 0.16f, 1f);
+ }
+
+ // Custom color and height.
+ [ProgressBar(-100, 100, r: 1, g: 1, b: 1, Height = 30)]
+ public short BigProgressBar = 50;
+
+ // You can also reference members by name to dynamically assign the min and max progress bar values.
+ [ProgressBar("DynamicMin", "DynamicMax")]
+ public float DynamicProgressBar;
+
+ public float DynamicMin, DynamicMax;
+ }
+
+
+ [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)]
+ public class BoxGroupAttribute : PropertyGroupAttribute
+ {
+ public string Label { get; private set; }
+ public bool ShowLabel { get; private set; }
+ public bool CenterLabel { get; private set; }
+
+ public BoxGroupAttribute(string group, bool showLabel = true, bool centerLabel = false, float order = 0)
+ : base(group, order)
+ {
+ this.Label = group;
+ this.ShowLabel = showLabel;
+ this.CenterLabel = centerLabel;
+ }
+
+ protected override void CombineValuesWith(PropertyGroupAttribute other)
+ {
+ // The given attribute parameter is *guaranteed* to be of type BoxGroupAttribute.
+ var attr = other as BoxGroupAttribute;
+
+ // If this attribute has no label, we the other group's label, thus preserving the label across combines.
+ if (this.Label == null)
+ {
+ this.Label = attr.Label;
+ }
+
+ // Combine ShowLabel and CenterLabel parameters.
+ this.ShowLabel |= attr.ShowLabel;
+ this.CenterLabel |= attr.CenterLabel;
+ }
+ }
+
+ protected override void CombineValuesWith(PropertyGroupAttribute other) { this.Title = this.Title ?? (other as MyGroupAttribute).Title; }
+ protected override void CombineValuesWith(PropertyGroupAttribute other)
+ {
+ // The given attribute parameter is *guaranteed* to be of type BoxGroupAttribute.
+ var attr = other as BoxGroupAttribute;
+
+ // If this attribute has no label, we the other group's label, thus preserving the label across combines.
+ if (this.Label == null)
+ {
+ this.Label = attr.Label;
+ }
+
+ // Combine ShowLabel and CenterLabel parameters.
+ this.ShowLabel |= attr.ShowLabel;
+ this.CenterLabel |= attr.CenterLabel;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [PropertyOrder(1)]
+ public int MySecondProperty;
+
+ [PropertyOrder(-1)]
+ public int MyFirstProperty;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [PropertyRange(0, 100)]
+ public int MyInt;
+
+ [PropertyRange(-100, 100)]
+ public float MyFloat;
+
+ [PropertyRange(-100, -50)]
+ public decimal MyDouble;
+
+ // This attribute also supports dynamically referencing members by name to assign the min and max values for the range field.
+ [PropertyRange("DynamicMin", "DynamicMax"]
+ public float MyDynamicValue;
+
+ public float DynamicMin, DynamicMax;
+ }
+
+
+ [PropertySpace] // Defaults to a space of 8 pixels just like Unity's Space attribute.
+ public int MyField;
+
+ [ShowInInspector, PropertySpace(16)]
+ public int MyProperty { get; set; }
+
+ [ShowInInspector, PropertySpace(16, 16)]
+ public int MyProperty { get; set; }
+
+ [Button, PropertySpace(32)]
+ public void MyMethod()
+ {
+ ...
+ }
+
+ [PropertySpace(-8)] // A negative space can also be remove existing space between properties.
+ public int MovedUp;
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [PropertyTooltip("This is an int property.")]
+ public int MyField;
+
+ [ShowInInspector, PropertyTooltip("This is another int property.")]
+ public int MyProperty { get; set; }
+ }
+
+
+ public class Health : MonoBehaviour
+ {
+ public int MaxHealth;
+
+ [ReadOnly]
+ public int CurrentHealth;
+ }
+
+
+ public class Health : MonoBehaviour
+ {
+ public int MaxHealth;
+
+ [ShowInInspector, ReadOnly]
+ private int currentHealth;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [Required]
+ public GameObject MyPrefab;
+
+ [Required(InfoMessageType.Warning)]
+ public Texture2D MyTexture;
+
+ [Required("MyMesh is nessessary for this component.")]
+ public Mesh MyMesh;
+
+ [Required("MyTransform might be important.", InfoMessageType.Info)]
+ public Transform MyTransform;
+ }
+
+
+ [ResponsiveButtonGroup]
+ public void Foo() { }
+
+ [ResponsiveButtonGroup]
+ public void Bar() { }
+
+ [ResponsiveButtonGroup]
+ public void Baz() { }
+
+
+ [ResponsiveButtonGroup(UniformLayout = true)]
+ public void Foo() { }
+
+ [ResponsiveButtonGroup]
+ public void Bar() { }
+
+ [ResponsiveButtonGroup]
+ public void Baz() { }
+
+
+ [ResponsiveButtonGroupAttribute(UniformLayout = true, DefaultButtonSize = ButtonSizes.Large)]
+ public void Foo() { }
+
+ [GUIColor(0, 1, 0))]
+ [Button(ButtonSizes.Large)]
+ [ResponsiveButtonGroup]
+ public void Bar() { }
+
+ [ResponsiveButtonGroup]
+ public void Baz() { }
+
+
+ [TabGroup("SomeTabGroup", "SomeTab")]
+ [ResponsiveButtonGroup("SomeTabGroup/SomeTab/SomeBtnGroup")]
+ public void Foo() { }
+
+ [ResponsiveButtonGroup("SomeTabGroup/SomeTab/SomeBtnGroup")]
+ public void Bar() { }
+
+ [ResponsiveButtonGroup("SomeTabGroup/SomeTab/SomeBtnGroup")]
+ public void Baz() { }
+
+
+ public MyComponent : MonoBehaviour
+ {
+ [SceneObjectsOnly]
+ public GameObject MyPrefab;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [ShowDrawerChain]
+ public int IndentedInt;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ public bool ShowProperties;
+
+ [ShowIf("showProperties")]
+ public int MyInt;
+
+ [ShowIf("showProperties", false)]
+ public string MyString;
+
+ public SomeEnum SomeEnumField;
+
+ [ShowIf("SomeEnumField", SomeEnum.SomeEnumMember)]
+ public string SomeString;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [ShowIf("MyVisibleFunction")]
+ public int MyHideableField;
+
+ private bool MyVisibleFunction()
+ {
+ return this.gameObject.activeInHierarchy;
+ }
+ }
+
+ ShowIfGroup allows for showing or hiding a group of properties based on a condition.
+The attribute is a group attribute and can therefore be combined with other group attributes, and even be used to show or hide entire groups.
+Note that in the vast majority of cases where you simply want to be able to control the visibility of a single group, it is better to use the VisibleIf parameter that *all* group attributes have.
+
+ public class MyComponent : MonoBehaviour
+ {
+ [ShowInInspector]
+ private int myField;
+
+ [ShowInInspector]
+ public int MyProperty { get; set; }
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [ShowPropertyResolver]
+ public int IndentedInt;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ // The SuffixLabel attribute draws a label at the end of a property.
+ // It's useful for conveying intend about a property.
+ // Fx, this field is supposed to have a prefab assigned.
+ [SuffixLabel("Prefab")]
+ public GameObject GameObject;
+
+ // Using the Overlay property, the suffix label will be drawn on top of the property instead of behind it.
+ // Use this for a neat inline look.
+ [SuffixLabel("ms", Overlay = true)]
+ public float Speed;
+
+ [SuffixLabel("radians", Overlay = true)]
+ public float Angle;
+
+ // The SuffixLabel attribute also supports string member references by using $.
+ [SuffixLabel("$Suffix", Overlay = true)]
+ public string Suffix = "Dynamic suffix label";
+ }
+
+
+ public class NamedValue<T>
+ {
+ public string Name;
+
+ // The Range attribute will be applied if T is compatible with it, but if T is not compatible, an error will not be shown.
+ [SuppressInvalidAttributeError, Range(0, 10)]
+ public T Value;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [TabGroup("First")]
+ public int MyFirstInt;
+
+ [TabGroup("First")]
+ public int AnotherInt;
+
+ [TabGroup("Second")]
+ public int MySecondInt;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [TabGroup("A", "FirstGroup")]
+ public int FirstGroupA;
+
+ [TabGroup("B", "FirstGroup")]
+ public int FirstGroupB;
+
+ // The second tab group has been configured to have constant height across all tabs.
+ [TabGroup("A", "SecondGroup", true)]
+ public int SecondgroupA;
+
+ [TabGroup("B", "SecondGroup")]
+ public int SecondGroupB;
+
+ [TabGroup("B", "SecondGroup")]
+ public int AnotherInt;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [TabGroup("ParentGroup", "First Tab")]
+ public int A;
+
+ [TabGroup("ParentGroup", "Second Tab")]
+ public int B;
+
+ // Specify 'First Tab' as a group, and another child group to the 'First Tab' group.
+ [TabGroup("ParentGroup/First Tab/InnerGroup", "Inside First Tab A")]
+ public int C;
+
+ [TabGroup("ParentGroup/First Tab/InnerGroup", "Inside First Tab B")]
+ public int D;
+
+ [TabGroup("ParentGroup/Second Tab/InnerGroup", "Inside Second Tab")]
+ public int E;
+ }
+
+
+ [TableList]
+ public List<SomeType> TableList = new List<SomeType>();
+
+ [Serializable]
+ public class SomeType
+ {
+ [LabelWidth(30)]
+ [TableColumnWidth(130, false)]
+ [VerticalGroup("Combined")]
+ public string A;
+
+ [LabelWidth(30)]
+ [VerticalGroup("Combined")]
+ public string B;
+
+ [Multiline(2), Space(3)]
+ public string fields;
+ }
+
+
+ // Inheriting from SerializedMonoBehaviour is only needed if you want Odin to serialize the multi-dimensional arrays for you.
+ // If you prefer doing that yourself, you can still make Odin show them in the inspector using the ShowInInspector attribute.
+ public class TableMatrixExamples : SerializedMonoBehaviour
+ {
+ [InfoBox("Right-click and drag column and row labels in order to modify the tables."), PropertyOrder(-10), OnInspectorGUI]
+ private void ShowMessageAtOP() { }
+
+ [BoxGroup("Two Dimensional array without the TableMatrix attribute.")]
+ public bool[,] BooleanTable = new bool[15, 6];
+
+ [BoxGroup("ReadOnly table")]
+ [TableMatrix(IsReadOnly = true)]
+ public int[,] ReadOnlyTable = new int[5, 5];
+
+ [BoxGroup("Labled table")]
+ [TableMatrix(HorizontalTitle = "X axis", VerticalTitle = "Y axis")]
+ public GameObject[,] LabledTable = new GameObject[15, 10];
+
+ [BoxGroup("Enum table")]
+ [TableMatrix(HorizontalTitle = "X axis")]
+ public InfoMessageType[,] EnumTable = new InfoMessageType[4,4];
+
+ [BoxGroup("Custom table")]
+ [TableMatrix(DrawElementMethod = "DrawColoredEnumElement", ResizableColumns = false)]
+ public bool[,] CustomCellDrawing = new bool[30,30];
+
+ #if UNITY_EDITOR
+
+ private static bool DrawColoredEnumElement(Rect rect, bool value)
+ {
+ if (Event.current.type == EventType.MouseDown && rect.Contains(Event.current.mousePosition))
+ {
+ value = !value;
+ GUI.changed = true;
+ Event.current.Use();
+ }
+
+ UnityEditor.EditorGUI.DrawRect(rect.Padding(1), value ? new Color(0.1f, 0.8f, 0.2f) : new Color(0, 0, 0, 0.5f));
+
+ return value;
+ }
+
+ #endif
+ }
+
+
+ public class TitleExamples : MonoBehaviour
+ {
+ [Title("Titles and Headers")]
+ [InfoBox(
+ "The Title attribute has the same purpose as Unity's Header attribute," +
+ "but it also supports properties, and methods." +
+ "\n\nTitle also offers more features such as subtitles, options for horizontal underline, bold text and text alignment." +
+ "\n\nBoth attributes, with Odin, supports either static strings, or refering to members strings by adding a $ in front.")]
+ public string MyTitle = "My Dynamic Title";
+ public string MySubtitle = "My Dynamic Subtitle";
+
+ [Title("Static title")]
+ public int C;
+ public int D;
+
+ [Title("Static title", "Static subtitle")]
+ public int E;
+ public int F;
+
+ [Title("$MyTitle", "$MySubtitle")]
+ public int G;
+ public int H;
+
+ [Title("Non bold title", "$MySubtitle", bold: false)]
+ public int I;
+ public int J;
+
+ [Title("Non bold title", "With no line seperator", horizontalLine: false, bold: false)]
+ public int K;
+ public int L;
+
+ [Title("$MyTitle", "$MySubtitle", TitleAlignments.Right)]
+ public int M;
+ public int N;
+
+ [Title("$MyTitle", "$MySubtitle", TitleAlignments.Centered)]
+ public int O;
+ public int P;
+
+ [Title("$Combined", titleAlignment: TitleAlignments.Centered)]
+ public int Q;
+ public int R;
+
+ [ShowInInspector]
+ [Title("Title on a Property")]
+ public int S { get; set; }
+
+ [Title("Title on a Method")]
+ [Button]
+ public void DoNothing()
+ { }
+
+ public string Combined { get { return this.MyTitle + " - " + this.MySubtitle; } }
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [Toggle("Enabled")]
+ public MyToggleable MyToggler = new MyToggleable();
+ }
+
+ public class MyToggleable
+ {
+ public bool Enabled;
+
+ public int MyValue;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ // This attribute has a title specified for the group. The title only needs to be applied to a single attribute for a group.
+ [ToggleGroup("FirstToggle", order: -1, groupTitle: "First")]
+ public bool FirstToggle;
+
+ [ToggleGroup("FirstToggle")]
+ public int MyInt;
+
+ // This group specifies a member string as the title of the group. A property or a function can also be used.
+ [ToggleGroup("SecondToggle", titleStringMemberName: "SecondGroupTitle")]
+ public bool SecondToggle { get; set; }
+
+ [ToggleGroup("SecondToggle")]
+ public float MyFloat;
+
+ [HideInInspector]
+ public string SecondGroupTitle = "Second";
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [ToggleLeft]
+ public bool MyBoolean;
+ }
+
+
+ [TypeInfoBox("This is my component and it is mine.")]
+ public class MyComponent : MonoBehaviour
+ {
+ // Class implementation.
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [ValidateInput("ValidateInput")]
+ public float Speed;
+
+ // Specify custom output message and message type.
+ [ValidateInput("ValidateInput", "Health must be more than 0!", InfoMessageType.Warning)]
+ public float Health;
+
+ private bool ValidateInput(float property)
+ {
+ return property > 0f;
+ }
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [ValidateInput("StaticValidateFunction")]
+ public int MyInt;
+
+ private static bool StaticValidateFunction(int property)
+ {
+ return property != 0;
+ }
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [ValueDropdown("myValues")]
+ public int MyInt;
+
+ // The selectable values for the dropdown.
+ private int[] myValues = { 1, 2, 3 };
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [ValueDropdown("myVectorValues")]
+ public Vector3 MyVector;
+
+ // The selectable values for the dropdown, with custom names.
+ private ValueDropdownList<Vector3> myVectorValues = new ValueDropdownList<Vector3>()
+ {
+ {"Forward", Vector3.forward },
+ {"Back", Vector3.back },
+ {"Up", Vector3.up },
+ {"Down", Vector3.down },
+ {"Right", Vector3.right },
+ {"Left", Vector3.left },
+ };
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ // Member field of type float[].
+ private float[] valuesField;
+
+ [ValueDropdown("valuesField")]
+ public float MyFloat;
+
+ // Member property of type List<thing>.
+ private List<string> ValuesProperty { get; set; }
+
+ [ValueDropdown("ValuesProperty")]
+ public string MyString;
+
+ // Member function that returns an object of type IList.
+ private IList<ValueDropdownItem<int>> ValuesFunction()
+ {
+ return new ValueDropdownList<int>
+ {
+ { "The first option", 1 },
+ { "The second option", 2 },
+ { "The third option", 3 },
+ };
+ }
+
+ [ValueDropdown("ValuesFunction")]
+ public int MyInt;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ // Make the field static.
+ private static MyEnum[] MyStaticEnumArray = MyEnum[] { ... };
+
+ // Force Unity to serialize the field, and hide the property from the inspector.
+ [SerializeField, HideInInspector]
+ private MyEnum MySerializedEnumArray = MyEnum[] { ... };
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [HorizontalGroup("Split")]
+ [VerticalGroup("Split/Left")]
+ public Vector3 Vector;
+
+ [VerticalGroup("Split/Left")]
+ public GameObject First;
+
+ [VerticalGroup("Split/Left")]
+ public GameObject Second;
+
+ [VerticalGroup("Split/Right", PaddingTop = 18f)]
+ public int A;
+
+ [VerticalGroup("Split/Right")]
+ public int B;
+ }
+
+
+ public class MyComponent : MonoBehaviour
+ {
+ [Wrap(-100, 100)]
+ public float MyFloat;
+ }
+
+
+ public class MyCustomClass : ISearchFilterable
+ {
+ public bool SearchEnabled;
+ public string MyStr;
+
+ public bool IsMatch(string searchString)
+ {
+ if (SearchEnabled)
+ {
+ return MyStr.Contains(searchString);
+ }
+
+ return false;
+ }
+ }
+
+
+ [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
+ public class CustomRangeAttribute : System.Attribute
+ {
+ public float Min;
+ public float Max;
+
+ public CustomRangeAttribute(float min, float max)
+ {
+ this.Min = min;
+ this.Max = max;
+ }
+ }
+
+ // Remember to wrap your custom attribute drawer within a #if UNITY_EDITOR condition, or locate the file inside an Editor folder.
+
+ public sealed class CustomRangeAttributeDrawer : OdinAttributeDrawer<CustomRangeAttribute, float>
+ {
+ protected override void DrawPropertyLayout(GUIContent label)
+ {
+ this.ValueEntry.SmartValue = EditorGUILayout.Slider(label, this.ValueEntry.SmartValue, this.Attribute.Min, this.Attribute.Max);
+ }
+ }
+
+ // Usage:
+ public class MyComponent : MonoBehaviour
+ {
+ [CustomRangeAttribute(0, 1)]
+ public float MyFloat;
+ }
+
+
+ [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
+ public class GUITintColorAttribute : System.Attribute
+ {
+ public Color Color;
+
+ public GUITintColorAttribute(float r, float g, float b, float a = 1)
+ {
+ this.Color = new Color(r, g, b, a);
+ }
+ }
+
+ // Remember to wrap your custom attribute drawer within a #if UNITY_EDITOR condition, or locate the file inside an Editor folder.
+
+ public sealed class GUITintColorAttributeDrawer : OdinAttributeDrawer<GUITintColorAttribute>
+ {
+ protected override void DrawPropertyLayout(GUIContent label)
+ {
+ Color prevColor = GUI.color;
+ GUI.color *= this.Attribute.Color;
+ this.CallNextDrawer(label);
+ GUI.color = prevColor;
+ }
+ }
+
+ // Usage:
+ public class MyComponent : MonoBehaviour
+ {
+ [GUITintColor(0, 1, 0)]
+ public float MyFloat;
+ }
+
+
+ [DrawerPriority(DrawerPriorityLevel.AttributePriority)]
+ public sealed class MyCustomAttributeDrawer<T> : OdinAttributeDrawer<MyCustomAttribute, T> where T : class
+ {
+ public override bool CanDrawTypeFilter(Type type)
+ {
+ return type != typeof(string);
+ }
+
+ protected override void DrawPropertyLayout(GUIContent label)
+ {
+ // Draw property here.
+ }
+ }
+
+
+ [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
+ public class CustomRangeAttribute : System.Attribute
+ {
+ public float Min;
+ public float Max;
+
+ public CustomRangeAttribute(float min, float max)
+ {
+ this.Min = min;
+ this.Max = max;
+ }
+ }
+
+ // Remember to wrap your custom attribute drawer within a #if UNITY_EDITOR condition, or locate the file inside an Editor folder.
+
+ public sealed class CustomRangeAttributeDrawer : OdinAttributeDrawer<CustomRangeAttribute, float>
+ {
+ protected override void DrawPropertyLayout(GUIContent label)
+ {
+ this.ValueEntry.SmartValue = EditorGUILayout.Slider(label, this.ValueEntry.SmartValue, this.Attribute.Min, this.Attribute.Max);
+ }
+ }
+
+ // Usage:
+ public class MyComponent : MonoBehaviour
+ {
+ [CustomRangeAttribute(0, 1)]
+ public float MyFloat;
+ }
+
+
+ [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
+ public class GUITintColorAttribute : System.Attribute
+ {
+ public Color Color;
+
+ public GUITintColorAttribute(float r, float g, float b, float a = 1)
+ {
+ this.Color = new Color(r, g, b, a);
+ }
+ }
+
+ // Remember to wrap your custom attribute drawer within a #if UNITY_EDITOR condition, or locate the file inside an Editor folder.
+
+ public sealed class GUITintColorAttributeDrawer : OdinAttributeDrawer<GUITintColorAttribute>
+ {
+ protected override void DrawPropertyLayout(GUIContent label)
+ {
+ Color prevColor = GUI.color;
+ GUI.color *= this.Attribute.Color;
+ this.CallNextDrawer(label);
+ GUI.color = prevColor;
+ }
+ }
+
+ // Usage:
+ public class MyComponent : MonoBehaviour
+ {
+ [GUITintColor(0, 1, 0)]
+ public float MyFloat;
+ }
+
+
+ [DrawerPriority(DrawerPriorityLevel.AttributePriority)]
+ public class MyCustomAttributeDrawer<T> : OdinAttributeDrawer<MyCustomAttribute, T> where T : class
+ {
+ public override bool CanDrawTypeFilter(Type type)
+ {
+ return type != typeof(string);
+ }
+
+ protected override void DrawPropertyLayout(GUIContent label)
+ {
+ // Draw property here.
+ }
+ }
+
+
+ [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public class MyBoxGroupAttribute : PropertyGroupAttribute
+ {
+ public MyBoxGroupAttribute(string group, float order = 0) : base(group, order)
+ {
+ }
+ }
+
+ // Remember to wrap your custom group drawer within a #if UNITY_EDITOR condition, or locate the file inside an Editor folder.
+
+ public class BoxGroupAttributeDrawer : OdinGroupDrawer<MyBoxGroupAttribute>
+ {
+ protected override void DrawPropertyGroupLayout(InspectorProperty property, MyBoxGroupAttribute attribute, GUIContent label)
+ {
+ GUILayout.BeginVertical("box");
+ for (int i = 0; i < property.Children.Count; i++)
+ {
+ InspectorUtilities.DrawProperty(property.Children[i]);
+ }
+ GUILayout.EndVertical();
+ }
+ }
+
+ // Usage:
+ public class MyComponent : MonoBehaviour
+ {
+ [MyBoxGroup("MyGroup")]
+ public int A;
+
+ [MyBoxGroup("MyGroup")]
+ public int B;
+
+ [MyBoxGroup("MyGroup")]
+ public int C;
+ }
+
+
+ public class MyCustomBaseType
+ {
+
+ }
+
+ public class MyCustomType : MyCustomBaseType
+ {
+
+ }
+
+ // Remember to wrap your custom attribute drawer within a #if UNITY_EDITOR condition, or locate the file inside an Editor folder.
+
+ public sealed class MyCustomBaseTypeDrawer<T> : OdinValueDrawer<T> where T : MyCustomBaseType
+ {
+ protected override void DrawPropertyLayout(IPropertyValueEntry<T> entry, GUIContent label)
+ {
+ T value = entry.SmartValue;
+ // Draw your custom drawer here using GUILayout and EditorGUILAyout.
+ }
+ }
+
+ // Usage:
+ // Both values will be drawn using the MyCustomBaseTypeDrawer
+ public class MyComponent : SerializedMonoBehaviour
+ {
+ public MyCustomBaseType A;
+
+ public MyCustomType B;
+ }
+
+
+ // [OdinDrawer(OdinDrawerBehaviour.DrawProperty)] // default
+ // [OdinDrawer(OdinDrawerBehaviour.AppendDecorator)]
+ [OdinDrawer(OdinDrawerBehaviour.PrependDecorator)]
+ [DrawerPriority(DrawerPriorityLevel.AttributePriority)]
+ public sealed class MyCustomTypeDrawer<T> : OdinValueDrawer<T> where T : MyCustomType
+ {
+ public override bool CanDrawTypeFilter(Type type)
+ {
+ return type != typeof(SomeType);
+ }
+
+ protected override void DrawPropertyLayout(IPropertyValueEntry<T> entry, GUIContent label)
+ {
+ T value = entry.SmartValue;
+ // Draw property here.
+ }
+ }
+
+
+
+ [AllowGUIEnabledForReadonly]
+ public sealed class SomeDrawerDrawer<T> : OdinValueDrawer<T> where T : class
+ {
+ }
+
+
+ [DrawerPriority(DrawerPriorityLevel.ValuePriority)]
+
+ public sealed class MyIntDrawer : InspectorValuePropertyDrawer<int>
+ {
+ // ...
+ }
+
+
+ [DrawerPriority(1, 0, 0)]
+
+ public sealed class MySpecialIntDrawer : InspectorValuePropertyDrawer<int>
+ {
+ // ...
+ }
+
+
+ // Specify that this drawer must be included in the inspector; without this, it will not be drawn
+ public class MyCustomTypeDrawer<T> : OdinValueDrawer<T> where T : MyCustomBaseType
+ {
+ protected override void DrawPropertyLayout(IPropertyValueEntry<T> entry, GUIContent label)
+ {
+ T value = entry.SmartValue;
+ // Draw property here.
+
+ // Optionally, call the next drawer in line.
+ this.CallNextDrawer(entry, label);
+ }
+ }
+
+
+
+ public class MyCustomTypeDrawer<T> : OdinValueDrawer<T> where T : MyCustomBaseType
+ {
+ protected override void DrawPropertyLayout(IPropertyValueEntry<T> entry, GUIContent label)
+ {
+ var isToggled = entry.Context(this, "toggled", false);
+ isToggled.Value = SirenixEditorGUI.Label(isToggled.Value, label);
+ if (SirenixEditorGUI.BeginFadeGroup(UniqueDrawerKey.Create(entry, this), isToggled.Value))
+ {
+ EditorGUI.indentLevel++;
+ this.CallNextDrawer(entry.Property, null);
+ EditorGUI.indentLevel--;
+ }
+ SirenixEditorGUI.EndFadeGroup();
+ }
+ }
+
+
+ public class SomeWindow : OdinEditorWindow
+ {
+ [MenuItem("My Game/Some Window")]
+ private static void OpenWindow()
+ {
+ GetWindow<SomeWindow>().Show();
+ }
+
+ [Button(ButtonSizes.Large)]
+ public void SomeButton() { }
+
+ [TableList]
+ public SomeType[] SomeTableData;
+ }
+
+
+ public class DrawSomeSingletonInAnEditorWindow : OdinEditorWindow
+ {
+ [MenuItem("My Game/Some Window")]
+ private static void OpenWindow()
+ {
+ GetWindow<DrawSomeSingletonInAnEditorWindow>().Show();
+ }
+
+ protected override object GetTarget()
+ {
+ return MySingleton.Instance;
+ }
+ }
+
+
+ private void InspectObjectInWindow()
+ {
+ OdinEditorWindow.InspectObject(someObject);
+ }
+
+ private void InspectObjectInDropDownWithAutoHeight()
+ {
+ var btnRect = GUIHelper.GetCurrentLayoutRect();
+ OdinEditorWindow.InspectObjectInDropDown(someObject, btnRect, btnRect.width);
+ }
+
+ private void InspectObjectInDropDown()
+ {
+ var btnRect = GUIHelper.GetCurrentLayoutRect();
+ OdinEditorWindow.InspectObjectInDropDown(someObject, btnRect, new Vector2(btnRect.width, 100));
+ }
+
+ private void InspectObjectInACenteredWindow()
+ {
+ var window = OdinEditorWindow.InspectObject(someObject);
+ window.position = GUIHelper.GetEditorWindowRect().AlignCenter(270, 200);
+ }
+
+ private void OtherStuffYouCanDo()
+ {
+ var window = OdinEditorWindow.InspectObject(this.someObject);
+
+ window.position = GUIHelper.GetEditorWindowRect().AlignCenter(270, 200);
+ window.titleContent = new GUIContent("Custom title", EditorIcons.RulerRect.Active);
+ window.OnClose += () => Debug.Log("Window Closed");
+ window.OnBeginGUI += () => GUILayout.Label("-----------");
+ window.OnEndGUI += () => GUILayout.Label("-----------");
+ }
+
+
+ public class OdinMenuEditorWindowExample : OdinMenuEditorWindow
+ {
+ [SerializeField, HideLabel]
+ private SomeData someData = new SomeData();
+
+ protected override OdinMenuTree BuildMenuTree()
+ {
+ OdinMenuTree tree = new OdinMenuTree(supportsMultiSelect: true)
+ {
+ { "Home", this, EditorIcons.House }, // draws the someDataField in this case.
+ { "Odin Settings", null, SdfIconType.GearFill },
+ { "Odin Settings/Color Palettes", ColorPaletteManager.Instance, EditorIcons.EyeDropper },
+ { "Odin Settings/AOT Generation", AOTGenerationConfig.Instance, EditorIcons.SmartPhone },
+ { "Camera current", Camera.current },
+ { "Some Class", this.someData }
+ };
+
+ tree.AddAllAssetsAtPath("More Odin Settings", SirenixAssetPaths.OdinEditorConfigsPath, typeof(ScriptableObject), true)
+ .AddThumbnailIcons();
+
+ tree.AddAssetAtPath("Odin Getting Started", SirenixAssetPaths.SirenixPluginPath + "Getting Started With Odin.asset");
+
+ var customMenuItem = new OdinMenuItem(tree, "Menu Style", tree.DefaultMenuStyle);
+ tree.MenuItems.Insert(2, customMenuItem);
+
+ tree.Add("Menu/Items/Are/Created/As/Needed", new GUIContent());
+ tree.Add("Menu/Items/Are/Created", new GUIContent("And can be overridden"));
+
+ // As you can see, Odin provides a few ways to quickly add editors / objects to your menu tree.
+ // The API also gives you full control over the selection, etc..
+ // Make sure to check out the API Documentation for OdinMenuEditorWindow, OdinMenuTree and OdinMenuItem for more information on what you can do!
+
+ return tree;
+ }
+ }
+
+
+ OdinMenuTree tree = new OdinMenuTree(supportsMultiSelect: true)
+ {
+ { "Home", this, EditorIcons.House },
+ { "Odin Settings", null, SdfIconType.GearFill },
+ { "Odin Settings/Color Palettes", ColorPaletteManager.Instance, EditorIcons.EyeDropper },
+ { "Odin Settings/AOT Generation", AOTGenerationConfig.Instance, EditorIcons.SmartPhone },
+ { "Camera current", Camera.current },
+ { "Some Class", this.someData }
+ };
+
+ tree.AddAllAssetsAtPath("Some Menu Item", "Some Asset Path", typeof(ScriptableObject), true)
+ .AddThumbnailIcons();
+
+ tree.AddAssetAtPath("Some Second Menu Item", "SomeAssetPath/SomeAssetFile.asset");
+
+ var customMenuItem = new OdinMenuItem(tree, "Menu Style", tree.DefaultMenuStyle);
+ tree.MenuItems.Insert(2, customMenuItem);
+
+ tree.Add("Menu/Items/Are/Created/As/Needed", new GUIContent());
+ tree.Add("Menu/Items/Are/Created", new GUIContent("And can be overridden"));
+
+ OdinMenuTrees are typically used with
+ // Draw stuff
+ someTree.DrawMenuTree();
+ // Draw stuff
+ someTree.HandleKeybaordMenuNavigation();
+
+
+ OdinMenuTree tree = new OdinMenuTree();
+ tree.AddAllAssetsAtPath("Some Menu Item", "Some Asset Path", typeof(ScriptableObject), true)
+ .AddThumbnailIcons();
+ tree.AddAssetAtPath("Some Second Menu Item", "SomeAssetPath/SomeAssetFile.asset");
+ // etc...
+
+
+ KeyCode someEnumValue;
+
+ [OnInspectorGUI]
+ void OnInspectorGUI()
+ {
+ // Use the selector manually. See the documentation for OdinSelector for more information.
+ if (GUILayout.Button("Open Enum Selector"))
+ {
+ EnumSelector<KeyCode> selector = new EnumSelector<KeyCode>();
+ selector.SetSelection(this.someEnumValue);
+ selector.SelectionConfirmed += selection => this.someEnumValue = selection.FirstOrDefault();
+ selector.ShowInPopup(); // Returns the Odin Editor Window instance, in case you want to mess around with that as well.
+ }
+
+ // Draw an enum dropdown field which uses the EnumSelector popup:
+ this.someEnumValue = EnumSelector<KeyCode>.DrawEnumField(new GUIContent("My Label"), this.someEnumValue);
+ }
+
+ // All Odin Selectors can be rendered anywhere with Odin. This includes the EnumSelector.
+ EnumSelector<KeyCode> inlineSelector;
+
+ [ShowInInspector]
+ EnumSelector<KeyCode> InlineSelector
+ {
+ get { return this.inlineSelector ?? (this.inlineSelector = new EnumSelector<KeyCode>()); }
+ set { }
+ }
+
+
+ SomeType someValue;
+
+ [OnInspectorGUI]
+ void OnInspectorGUI()
+ {
+ if (GUILayout.Button("Open Generic Selector Popup"))
+ {
+ List<SomeType> source = ...;
+ GenericSelector<SomeType> selector = new GenericSelector<SomeType>("Title", false, x => x.Path, source);
+ selector.SetSelection(this.someValue);
+ selector.SelectionTree.Config.DrawSearchToolbar = false;
+ selector.SelectionTree.DefaultMenuStyle.Height = 22;
+ selector.SelectionConfirmed += selection => this.someValue = selection.FirstOrDefault()
+ var window = selector.ShowInPopup();
+ window.OnEndGUI += () => { EditorGUILayout.HelpBox("A quick way of injecting custom GUI to the editor window popup instance.", MessageType.Info); };
+ window.OnClose += selector.SelectionTree.Selection.ConfirmSelection; // Confirm selection when window clses.
+ }
+ }
+
+
+ public class MySelector : OdinSelector<SomeType>
+ {
+ private readonly List<SomeType> source;
+ private readonly bool supportsMultiSelect;
+
+ public MySelector(List<SomeType> source, bool supportsMultiSelect)
+ {
+ this.source = source;
+ this.supportsMultiSelect = supportsMultiSelect;
+ }
+
+ protected override void BuildSelectionTree(OdinMenuTree tree)
+ {
+ tree.Config.DrawSearchToolbar = true;
+ tree.Selection.SupportsMultiSelect = this.supportsMultiSelect;
+
+ tree.Add("Defaults/None", null);
+ tree.Add("Defaults/A", new SomeType());
+ tree.Add("Defaults/B", new SomeType());
+
+ tree.AddRange(this.source, x => x.Path, x => x.SomeTexture);
+ }
+
+ [OnInspectorGUI]
+ private void DrawInfoAboutSelectedItem()
+ {
+ SomeType selected = this.GetCurrentSelection().FirstOrDefault();
+
+ if (selected != null)
+ {
+ GUILayout.Label("Name: " + selected.Name);
+ GUILayout.Label("Data: " + selected.Data);
+ }
+ }
+ }
+
+ Usage:
+
+ void OnGUI()
+ {
+ if (GUILayout.Button("Open My Selector"))
+ {
+ List<SomeType> source = this.GetListOfThingsToSelectFrom();
+ MySelector selector = new MySelector(source, false);
+
+ selector.SetSelection(this.someValue);
+
+ selector.SelectionCancelled += () => { }; // Occurs when the popup window is closed, and no slection was confirmed.
+ selector.SelectionChanged += col => { };
+ selector.SelectionConfirmed += col => this.someValue = col.FirstOrDefault();
+
+ selector.ShowInPopup(); // Returns the Odin Editor Window instance, in case you want to mess around with that as well.
+ }
+ }
+
+ // All Odin Selectors can be rendered anywhere with Odin.
+ [ShowInInspector]
+ MySelector inlineSelector;
+
+
+ Type[] selectedTypes;
+
+ void OnGUI()
+ {
+ // Use the selector manually. See the documentation for OdinSelector for more information.
+ if (GUILayout.Button("Open My Selector"))
+ {
+ TypeSelector selector = new TypeSelector(customListOfTypes);
+ TypeSelector selector = new TypeSelector(AssemblyTypeFlags.CustomTypes, supportsMultiSelect: true);
+ selector.SetSelection(this.selectedTypes);
+ selector.SelectionConfirmed += selection => this.selectedTypes = selection.ToArray();
+ selector.ShowInPopup(); // Returns the Odin Editor Window instance, in case you want to mess around with that as well.
+ }
+ }
+
+
+ private static Type currentSelectedType;
+ private static IEnumerable<Type> currentSource;
+ private static Func<Rect, OdinSelector<Type>> createTypeSelector = (rect) =>
+ {
+ TypeSelector selector = new TypeSelector(currentSource, false);
+ selector.SetSelection(currentSelectedType);
+ selector.ShowInPopup(rect);
+ return selector;
+ };
+
+ public static Type DrawTypeSelectorDropdown(GUIContent label, Type selectedType, IEnumerable<Type> source)
+ {
+ currentSource = source;
+ currentSelectedType = selectedType;
+
+ var dropdownText = selectedType == null ? "None" : selectedType.GetNiceName();
+ var selected = TypeSelector.DrawSelectorDropdown(label, dropdownText, createTypeSelector);
+ if (selected != null && selected.Any())
+ {
+ selectedType = selected.FirstOrDefault();
+ }
+ return selectedType;
+ }
+
+
+ [assembly: OdinSerializer.BindTypeNameToType("Namespace.OldTypeName", typeof(Namespace.NewTypeName))]
+ //[assembly: OdinSerializer.BindTypeNameToType("Namespace.OldTypeName, OldFullAssemblyName", typeof(Namespace.NewTypeName))]
+
+ namespace Namespace
+ {
+ public class SomeComponent : SerializedMonoBehaviour
+ {
+ public IInterface test; // Contains an instance of OldTypeName;
+ }
+
+ public interface IInterface { }
+
+ public class NewTypeName : IInterface { }
+
+ //public class OldTypeName : IInterface { }
+ }
+
+
+ var tabGroup = SirenixEditorGUI.CreateAnimatedTabGroup(someKey);
+ // Register your tabs before starting BeginGroup.
+ var tab1 = tabGroup.RegisterTab("tab 1");
+ var tab2 = tabGroup.RegisterTab("tab 2");
+
+ tabGroup.BeginGroup(drawToolbar: true);
+ {
+ if (tab1.BeginPage())
+ {
+ // Draw GUI for the first tab page;
+ }
+ tab1.EndPage();
+
+ if (tab2.BeginPage())
+ {
+ // Draw GUI for the second tab page;
+ }
+ tab2.EndPage();
+ }
+ tabGroup.EndGroup();
+
+ // Control the animation speed.
+ tabGroup.AnimationSpeed = 0.2f;
+
+ // If true, the tab group will have the height equal to the biggest page. Otherwise the tab group will animate in height as well when changing page.
+ tabGroup.FixedHeight = true;
+
+ // You can change page by calling:
+ tabGroup.GoToNextPage();
+ tabGroup.GoToPreviousPage();
+
+
+ private GUITable table;
+
+ private void Init()
+ {
+ bool[,] boolArr = new bool[20,20];
+
+ this.table = GUITable.Create(
+ twoDimArray: boolArr,
+ drawElement: (rect, x, y) => boolArr[x, y] = EditorGUI.Toggle(rect, boolArr[x, y]),
+ horizontalLabel: "Optional Horizontal Label", // horizontalLabel is optional and can be null.
+ columnLabels: (rect, x) => GUI.Label(rect, x.ToString()), // columnLabels is optional and can be null.
+ verticalLabel: "Optional Vertical Label", // verticalLabel is optional and can be null.
+ rowLabels: (rect, x) => GUI.Label(rect, x.ToString()) // rowLabels is optional and can be null.
+ );
+ }
+
+ private void OnGUI()
+ {
+ this.table.DrawTable();
+ }
+
+
+ private GUITable table;
+
+ private void Init()
+ {
+ Listt<SomeClasst> someList = new List<SomeClass>() { new SomeClass(), new SomeClass(), new SomeClass() };
+
+ this.table = GUITable.Create(someList, "Optional Title",
+ new GUITableColumn()
+ {
+ ColumnTitle = "A",
+ OnGUI = (rect, i) => someList[i].A = EditorGUI.TextField(rect, someList[i].A),
+ Width = 200,
+ MinWidth = 100,
+ },
+ new GUITableColumn()
+ {
+ ColumnTitle = "B",
+ OnGUI = (rect, i) => someList[i].B = EditorGUI.IntField(rect, someList[i].B),
+ Resizable = false,
+ },
+ new GUITableColumn()
+ {
+ ColumnTitle = "C",
+ OnGUI = (rect, i) => someList[i].C = EditorGUI.IntField(rect, someList[i].C),
+ SpanColumnTitle = true,
+ }
+ );
+ }
+
+ private void OnGUI()
+ {
+ this.table.DrawTable();
+ }
+
+ private class SomeClass
+ {
+ public string A;
+ public int B;
+ public int C;
+ public int D;
+ }
+
+
+ guiTable[x,y].GUIStyle += rect => EditorGUI.DrawRect(rect, Color.red);
+
+
+ // Span horizontally:
+ guiTable[x - 2,y] = null;
+ guiTable[x - 1,y] = null;
+ guiTable[x,y].SpanX = true;
+ guiTable[x + 1,y] = null;
+
+ // Span vertically:
+ guiTable[x,y - 2] = null;
+ guiTable[x,y - 1] = null;
+ guiTable[x,y].SpanY = true;
+ guiTable[x,y + 1] = null;
+
+
+ [GlobalConfig("Assets/Resources/MyConfigFiles/")]
+ public class MyGlobalConfig : GlobalConfig<MyGlobalConfig>
+ {
+ public int MyGlobalVariable;
+ }
+
+ void SomeMethod()
+ {
+ int value = MyGlobalConfig.Instance.MyGlobalVariable;
+ }
+
+
+ // Generates garbage:
+ GUILayout.Label(label, GUILayout.Label(label, GUILayout.Width(20), GUILayout.ExpandHeight(), GUILayout.MaxWidth(300)));
+
+ // Does not generate garbage:
+ GUILayout.Label(label, GUILayout.Label(label, GUILayoutOptions.Width(20).ExpandHeight().MaxWidth(300)));
+
+