using System; using System.Collections.Generic; using UnityEngine; namespace UnityEditor.Rendering { [Flags] public enum FoldoutOption { None = 0, Indent = 1 << 0, Boxed = 1 << 2, SubFoldout = 1 << 3, NoSpaceAtEnd = 1 << 4 } [Flags] public enum GroupOption { None = 0, Indent = 1 << 0 } /// /// Utility class to draw inspectors /// /// Type of class containing data needed to draw inspector public static class CoreEditorDrawer { /// Abstraction that have the Draw hability public interface IDrawer { void Draw(TData p, Editor owner); } public delegate bool Enabler(TData data, Editor owner); public delegate void SwitchEnabler(TData data, Editor owner); public delegate T2Data DataSelect(TData data, Editor owner); public delegate void ActionDrawer(TData data, Editor owner); /// Equivalent to EditorGUILayout.Space that can be put in a drawer group public static readonly IDrawer space = Group((data, owner) => EditorGUILayout.Space()); /// Use it when IDrawer required but no operation should be done public static readonly IDrawer noop = Group((data, owner) => { }); /// /// Conditioned drawer that will only be drawn if its enabler function is null or return true /// /// Enable the drawing if null or return true /// The content of the group public static IDrawer Conditional(Enabler enabler, params IDrawer[] contentDrawers) { return new ConditionalDrawerInternal(enabler, contentDrawers.Draw); } /// /// Conditioned drawer that will only be drawn if its enabler function is null or return true /// /// Enable the drawing if null or return true /// The content of the group public static IDrawer Conditional(Enabler enabler, params ActionDrawer[] contentDrawers) { return new ConditionalDrawerInternal(enabler, contentDrawers); } class ConditionalDrawerInternal : IDrawer { ActionDrawer[] actionDrawers { get; set; } Enabler m_Enabler; public ConditionalDrawerInternal(Enabler enabler = null, params ActionDrawer[] actionDrawers) { this.actionDrawers = actionDrawers; m_Enabler = enabler; } void IDrawer.Draw(TData data, Editor owner) { if (m_Enabler != null && !m_Enabler(data, owner)) return; for (var i = 0; i < actionDrawers.Length; i++) actionDrawers[i](data, owner); } } /// /// Group of drawing function for inspector. /// They will be drawn one after the other. /// /// The content of the group public static IDrawer Group(params IDrawer[] contentDrawers) { return new GroupDrawerInternal(-1f, GroupOption.None, contentDrawers.Draw); } /// /// Group of drawing function for inspector. /// They will be drawn one after the other. /// /// The content of the group public static IDrawer Group(params ActionDrawer[] contentDrawers) { return new GroupDrawerInternal(-1f, GroupOption.None, contentDrawers); } /// Group of drawing function for inspector with a set width for labels /// Width used for all labels in the group /// The content of the group public static IDrawer Group(float labelWidth, params IDrawer[] contentDrawers) { return new GroupDrawerInternal(labelWidth, GroupOption.None, contentDrawers.Draw); } /// Group of drawing function for inspector with a set width for labels /// Width used for all labels in the group /// The content of the group public static IDrawer Group(float labelWidth, params ActionDrawer[] contentDrawers) { return new GroupDrawerInternal(labelWidth, GroupOption.None, contentDrawers); } /// /// Group of drawing function for inspector. /// They will be drawn one after the other. /// /// Allow to add indentation on this group /// The content of the group public static IDrawer Group(GroupOption options, params IDrawer[] contentDrawers) { return new GroupDrawerInternal(-1f, options, contentDrawers.Draw); } /// /// Group of drawing function for inspector. /// They will be drawn one after the other. /// /// Allow to add indentation on this group /// The content of the group public static IDrawer Group(GroupOption options, params ActionDrawer[] contentDrawers) { return new GroupDrawerInternal(-1f, options, contentDrawers); } /// Group of drawing function for inspector with a set width for labels /// Width used for all labels in the group /// Allow to add indentation on this group /// The content of the group public static IDrawer Group(float labelWidth, GroupOption options, params IDrawer[] contentDrawers) { return new GroupDrawerInternal(labelWidth, options, contentDrawers.Draw); } /// Group of drawing function for inspector with a set width for labels /// Width used for all labels in the group /// Allow to add indentation on this group /// The content of the group public static IDrawer Group(float labelWidth, GroupOption options, params ActionDrawer[] contentDrawers) { return new GroupDrawerInternal(labelWidth, options, contentDrawers); } class GroupDrawerInternal : IDrawer { ActionDrawer[] actionDrawers { get; set; } float m_LabelWidth; bool isIndented; public GroupDrawerInternal(float labelWidth = -1f, GroupOption options = GroupOption.None, params ActionDrawer[] actionDrawers) { this.actionDrawers = actionDrawers; m_LabelWidth = labelWidth; isIndented = (options & GroupOption.Indent) != 0; } void IDrawer.Draw(TData data, Editor owner) { if (isIndented) ++EditorGUI.indentLevel; var currentLabelWidth = EditorGUIUtility.labelWidth; if (m_LabelWidth >= 0f) { EditorGUIUtility.labelWidth = m_LabelWidth; } for (var i = 0; i < actionDrawers.Length; i++) actionDrawers[i](data, owner); if (m_LabelWidth >= 0f) { EditorGUIUtility.labelWidth = currentLabelWidth; } if (isIndented) --EditorGUI.indentLevel; } } /// Create an IDrawer based on an other data container /// The data new source for the inner drawers /// Inner drawers drawed with given data sources /// public static IDrawer Select( DataSelect dataSelect, params CoreEditorDrawer.IDrawer[] otherDrawers) { return new SelectDrawerInternal(dataSelect, otherDrawers.Draw); } /// Create an IDrawer based on an other data container /// The data new source for the inner drawers /// Inner drawers drawed with given data sources /// public static IDrawer Select( DataSelect dataSelect, params CoreEditorDrawer.ActionDrawer[] otherDrawers) { return new SelectDrawerInternal(dataSelect, otherDrawers); } class SelectDrawerInternal : IDrawer { DataSelect m_DataSelect; CoreEditorDrawer.ActionDrawer[] m_SourceDrawers; public SelectDrawerInternal(DataSelect dataSelect, params CoreEditorDrawer.ActionDrawer[] otherDrawers) { m_SourceDrawers = otherDrawers; m_DataSelect = dataSelect; } void IDrawer.Draw(TData data, Editor o) { var p2 = m_DataSelect(data, o); for (var i = 0; i < m_SourceDrawers.Length; i++) m_SourceDrawers[i](p2, o); } } /// /// Create an IDrawer foldout header using an ExpandedState. /// The default option is Indent in this version. /// /// Title wanted for this foldout header /// Bit mask (enum) used to define the boolean saving the state in ExpandedState /// The ExpandedState describing the component /// The content of the foldout header public static IDrawer FoldoutGroup(string title, TEnum mask, ExpandedState state, params IDrawer[] contentDrawers) where TEnum : struct, IConvertible { return FoldoutGroup(title, mask, state, contentDrawers.Draw); } /// /// Create an IDrawer foldout header using an ExpandedState. /// The default option is Indent in this version. /// /// Title wanted for this foldout header /// Bit mask (enum) used to define the boolean saving the state in ExpandedState /// The ExpandedState describing the component /// The content of the foldout header public static IDrawer FoldoutGroup(string title, TEnum mask, ExpandedState state, params ActionDrawer[] contentDrawers) where TEnum : struct, IConvertible { return FoldoutGroup(EditorGUIUtility.TrTextContent(title), mask, state, contentDrawers); } /// Create an IDrawer foldout header using an ExpandedState /// Title wanted for this foldout header /// Bit mask (enum) used to define the boolean saving the state in ExpandedState /// The ExpandedState describing the component /// The content of the foldout header public static IDrawer FoldoutGroup(string title, TEnum mask, ExpandedState state, FoldoutOption options, params IDrawer[] contentDrawers) where TEnum : struct, IConvertible { return FoldoutGroup(title, mask, state, options, contentDrawers.Draw); } /// Create an IDrawer foldout header using an ExpandedState /// Title wanted for this foldout header /// Bit mask (enum) used to define the boolean saving the state in ExpandedState /// The ExpandedState describing the component /// The content of the foldout header public static IDrawer FoldoutGroup(string title, TEnum mask, ExpandedState state, FoldoutOption options, params ActionDrawer[] contentDrawers) where TEnum : struct, IConvertible { return FoldoutGroup(EditorGUIUtility.TrTextContent(title), mask, state, options, contentDrawers); } /// /// Create an IDrawer foldout header using an ExpandedState. /// The default option is Indent in this version. /// /// Title wanted for this foldout header /// Bit mask (enum) used to define the boolean saving the state in ExpandedState /// The ExpandedState describing the component /// The content of the foldout header public static IDrawer FoldoutGroup(GUIContent title, TEnum mask, ExpandedState state, params IDrawer[] contentDrawers) where TEnum : struct, IConvertible { return FoldoutGroup(title, mask, state, contentDrawers.Draw); } /// /// Create an IDrawer foldout header using an ExpandedState. /// The default option is Indent in this version. /// /// Title wanted for this foldout header /// Bit mask (enum) used to define the boolean saving the state in ExpandedState /// The ExpandedState describing the component /// The content of the foldout header public static IDrawer FoldoutGroup(GUIContent title, TEnum mask, ExpandedState state, params ActionDrawer[] contentDrawers) where TEnum : struct, IConvertible { return FoldoutGroup(title, mask, state, FoldoutOption.Indent, contentDrawers); } /// Create an IDrawer foldout header using an ExpandedState /// Title wanted for this foldout header /// Bit mask (enum) used to define the boolean saving the state in ExpandedState /// The ExpandedState describing the component /// The content of the foldout header public static IDrawer FoldoutGroup(GUIContent title, TEnum mask, ExpandedState state, FoldoutOption options, params IDrawer[] contentDrawers) where TEnum : struct, IConvertible { return FoldoutGroup(title, mask, state, options, contentDrawers.Draw); } /// Create an IDrawer foldout header using an ExpandedState /// Title wanted for this foldout header /// Bit mask (enum) used to define the boolean saving the state in ExpandedState /// The ExpandedState describing the component /// The content of the foldout header public static IDrawer FoldoutGroup(GUIContent title, TEnum mask, ExpandedState state, FoldoutOption options, params ActionDrawer[] contentDrawers) where TEnum : struct, IConvertible { return FoldoutGroup(title, mask, state, options, null, null, contentDrawers); } // This one is private as we do not want to have unhandled advanced switch. Change it if necessary. static IDrawer FoldoutGroup(GUIContent title, TEnum mask, ExpandedState state, FoldoutOption options, Enabler isAdvanced, SwitchEnabler switchAdvanced, params ActionDrawer[] contentDrawers) where TEnum : struct, IConvertible { return Group((data, owner) => { bool isBoxed = (options & FoldoutOption.Boxed) != 0; bool isIndented = (options & FoldoutOption.Indent) != 0; bool isSubFoldout = (options & FoldoutOption.SubFoldout) != 0; bool noSpaceAtEnd = (options & FoldoutOption.NoSpaceAtEnd) != 0; bool expended = state[mask]; bool newExpended = expended; if (isSubFoldout) { newExpended = CoreEditorUtils.DrawSubHeaderFoldout(title, expended, isBoxed, isAdvanced == null ? (Func)null : () => isAdvanced(data, owner), switchAdvanced == null ? (Action)null : () => switchAdvanced(data, owner)); } else { CoreEditorUtils.DrawSplitter(isBoxed); newExpended = CoreEditorUtils.DrawHeaderFoldout(title, expended, isBoxed, isAdvanced == null ? (Func)null : () => isAdvanced(data, owner), switchAdvanced == null ? (Action)null : () => switchAdvanced(data, owner)); } if (newExpended ^ expended) state[mask] = newExpended; if (newExpended) { if (isIndented) ++EditorGUI.indentLevel; for (var i = 0; i < contentDrawers.Length; i++) contentDrawers[i](data, owner); if (isIndented) --EditorGUI.indentLevel; if (!noSpaceAtEnd) EditorGUILayout.Space(); } }); } /// Helper to draw a foldout with an advanced switch on it. /// Title wanted for this foldout header /// Bit mask (enum) used to define the boolean saving the state in ExpandedState /// The ExpandedState describing the component /// Delegate allowing to check if advanced mode is active. /// Delegate to know what to do when advance is switched. /// The content of the foldout header always visible if expended. /// The content of the foldout header only visible if advanced mode is active and if foldout is expended. public static IDrawer AdvancedFoldoutGroup(GUIContent foldoutTitle, TEnum foldoutMask, ExpandedState foldoutState, Enabler isAdvanced, SwitchEnabler switchAdvanced, IDrawer normalContent, IDrawer advancedContent, FoldoutOption options = FoldoutOption.Indent) where TEnum : struct, IConvertible { return AdvancedFoldoutGroup(foldoutTitle, foldoutMask, foldoutState, isAdvanced, switchAdvanced, normalContent.Draw, advancedContent.Draw, options); } /// Helper to draw a foldout with an advanced switch on it. /// Title wanted for this foldout header /// Bit mask (enum) used to define the boolean saving the state in ExpandedState /// The ExpandedState describing the component /// Delegate allowing to check if advanced mode is active. /// Delegate to know what to do when advance is switched. /// The content of the foldout header always visible if expended. /// The content of the foldout header only visible if advanced mode is active and if foldout is expended. public static IDrawer AdvancedFoldoutGroup(GUIContent foldoutTitle, TEnum foldoutMask, ExpandedState foldoutState, Enabler isAdvanced, SwitchEnabler switchAdvanced, ActionDrawer normalContent, IDrawer advancedContent, FoldoutOption options = FoldoutOption.Indent) where TEnum : struct, IConvertible { return AdvancedFoldoutGroup(foldoutTitle, foldoutMask, foldoutState, isAdvanced, switchAdvanced, normalContent, advancedContent.Draw, options); } /// Helper to draw a foldout with an advanced switch on it. /// Title wanted for this foldout header /// Bit mask (enum) used to define the boolean saving the state in ExpandedState /// The ExpandedState describing the component /// Delegate allowing to check if advanced mode is active. /// Delegate to know what to do when advance is switched. /// The content of the foldout header always visible if expended. /// The content of the foldout header only visible if advanced mode is active and if foldout is expended. public static IDrawer AdvancedFoldoutGroup(GUIContent foldoutTitle, TEnum foldoutMask, ExpandedState foldoutState, Enabler isAdvanced, SwitchEnabler switchAdvanced, IDrawer normalContent, ActionDrawer advancedContent, FoldoutOption options = FoldoutOption.Indent) where TEnum : struct, IConvertible { return AdvancedFoldoutGroup(foldoutTitle, foldoutMask, foldoutState, isAdvanced, switchAdvanced, normalContent.Draw, advancedContent, options); } /// Helper to draw a foldout with an advanced switch on it. /// Title wanted for this foldout header /// Bit mask (enum) used to define the boolean saving the state in ExpandedState /// The ExpandedState describing the component /// Delegate allowing to check if advanced mode is active. /// Delegate to know what to do when advance is switched. /// The content of the foldout header always visible if expended. /// The content of the foldout header only visible if advanced mode is active and if foldout is expended. public static IDrawer AdvancedFoldoutGroup(GUIContent foldoutTitle, TEnum foldoutMask, ExpandedState foldoutState, Enabler isAdvanced, SwitchEnabler switchAdvanced, ActionDrawer normalContent, ActionDrawer advancedContent, FoldoutOption options = FoldoutOption.Indent) where TEnum : struct, IConvertible { return FoldoutGroup(foldoutTitle, foldoutMask, foldoutState, options, isAdvanced, switchAdvanced, normalContent, Conditional((serialized, owner) => isAdvanced(serialized, owner) && foldoutState[foldoutMask], advancedContent).Draw ); } } public static class CoreEditorDrawersExtensions { /// Concatenate a collection of IDrawer as a unique IDrawer public static void Draw(this IEnumerable.IDrawer> drawers, TData data, Editor owner) { foreach (var drawer in drawers) drawer.Draw(data, owner); } } }