+ [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
+ }
+
+
+ [TableMatrix(SquareCells = true, Labels = "GetLabel")]
+ public int[,] ChessBoard = new int[8, 8];
+
+ private (string, LabelDirection) GetLabel(int[,] array, TableAxis axis, int index)
+ {
+ var chessFileLetters = "ABCDEFGH";
+
+ switch (axis)
+ {
+ case TableAxis.Y:
+ return ((array.GetLength(1) - index).ToString(), LabelDirection.LeftToRight);
+ case TableAxis.X:
+ return (chessFileLetters[index].ToString(), LabelDirection.TopToBottom);
+ default:
+ return (index.ToString(), LabelDirection.LeftToRight);
+ }
+ }
+
+
+ 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;
+
+
+ decimal meters = 5m;
+ decimal centimeters = ConvertUnitsFromTo(meters, Units.Meter, Units.Centimeter);
+ // centimeters = 500
+
+
+ decimal meters = 5m;
+ decimal centimeters = ConvertUnitsFromTo(meters, meterUnitInfo, centimeterUnitInfo);
+ // centimeters = 500
+
+
+ decimal meters = 5m;
+ if (TryConvertUnitsFromTo(meters, Units.Meter, Units.Centimeter, out decimal centimeters)
+ {
+ // centimeters = 500
+ }
+
+
+ decimal meters = 5m;
+ if (TryConvertUnitsFromTo(meters, meterUnitInfo, centimeterUnitInfo, out decimal centimeters))
+ {
+ // centimeters = 500
+ }
+
+
+ UnitNumberUtility.AddCustomUnit("Centimeter", new string[]{ "cm" }, "Distance", 100m);
+
+
+ UnitNumberUtility.AddCustomUnit("Centimeter", new string[]{ "cm" }, UnitCategory.Distance, 100m);
+
+
+ UnitNumberUtility.AddCustomUnit("Centimeter", new string[]{ "cm" }, "Distance", x => x / 100m, x = > x * 100m);
+
+
+ UnitNumberUtility.AddCustomUnit("Centimeter", new string[]{ "cm" }, UnitCategory.Distance, x => x / 100m, x = > x * 100m);
+
+
+ [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)));
+
+