11#if UNITY_EDITOR
2- using System ;
32using System . IO ;
43using UnityEngine . InputSystem . Utilities ;
54using UnityEditor ;
65using UnityEditor . AssetImporters ;
6+ using UnityEngine . UIElements ;
7+ using UnityEditor . UIElements ;
78
89////TODO: support for multi-editing
910
@@ -15,90 +16,219 @@ namespace UnityEngine.InputSystem.Editor
1516 [ CustomEditor ( typeof ( InputActionImporter ) ) ]
1617 internal class InputActionImporterEditor : ScriptedImporterEditor
1718 {
18- public override void OnInspectorGUI ( )
19+ public override VisualElement CreateInspectorGUI ( )
1920 {
21+ var root = new VisualElement ( ) ;
2022 var inputActionAsset = GetAsset ( ) ;
2123
2224 // ScriptedImporterEditor in 2019.2 now requires explicitly updating the SerializedObject
2325 // like in other types of editors.
2426 serializedObject . Update ( ) ;
2527
26- EditorGUILayout . Space ( ) ;
27-
2828 if ( inputActionAsset == null )
29- EditorGUILayout . HelpBox ( "The currently selected object is not an editable input action asset." ,
30- MessageType . Info ) ;
29+ {
30+ root . Add ( new HelpBox (
31+ "The currently selected object is not an editable input action asset." ,
32+ HelpBoxMessageType . Info ) ) ;
33+ }
34+
35+ var editButton = new Button ( ( ) => OpenEditor ( inputActionAsset ) )
36+ {
37+ text = GetOpenEditorButtonText ( inputActionAsset )
38+ } ;
39+ editButton . style . height = 30 ;
40+ editButton . style . marginTop = 4 ;
41+ editButton . style . marginBottom = 4 ;
42+ editButton . SetEnabled ( inputActionAsset != null ) ;
43+ root . Add ( editButton ) ;
44+
45+ var projectWideContainer = new VisualElement ( ) ;
46+ projectWideContainer . style . marginTop = 6 ;
47+ projectWideContainer . style . marginBottom = 6 ;
48+ root . Add ( projectWideContainer ) ;
49+ BuildProjectWideSection ( projectWideContainer , inputActionAsset ) ;
50+
51+ BuildCodeGenerationSection ( root , inputActionAsset ) ;
52+
53+ root . Add ( new IMGUIContainer ( ( ) =>
54+ {
55+ serializedObject . ApplyModifiedProperties ( ) ;
56+ ApplyRevertGUI ( ) ;
57+ } ) ) ;
58+
59+ return root ;
60+ }
61+
62+ private void BuildProjectWideSection ( VisualElement container , InputActionAsset inputActionAsset )
63+ {
64+ container . Clear ( ) ;
65+
66+ var currentActions = InputSystem . actions ;
3167
32- // Button to pop up window to edit the asset.
33- using ( new EditorGUI . DisabledScope ( inputActionAsset == null ) )
68+ if ( currentActions == inputActionAsset )
3469 {
35- if ( GUILayout . Button ( GetOpenEditorButtonText ( inputActionAsset ) , GUILayout . Height ( 30 ) ) )
36- OpenEditor ( inputActionAsset ) ;
70+ container . Add ( new HelpBox (
71+ "These actions are assigned as the Project-wide Input Actions." ,
72+ HelpBoxMessageType . Info ) ) ;
73+ return ;
74+ }
75+
76+ var message = "These actions are not assigned as the Project-wide Input Actions for the Input System." ;
77+ if ( currentActions != null )
78+ {
79+ var currentPath = AssetDatabase . GetAssetPath ( currentActions ) ;
80+ if ( ! string . IsNullOrEmpty ( currentPath ) )
81+ message += $ " The actions currently assigned as the Project-wide Input Actions are: { currentPath } . ";
3782 }
3883
39- EditorGUILayout . Space ( ) ;
84+ container . Add ( new HelpBox ( message , HelpBoxMessageType . Warning ) ) ;
85+
86+ var assignButton = new Button ( ( ) =>
87+ {
88+ InputSystem . actions = inputActionAsset ;
89+ BuildProjectWideSection ( container , inputActionAsset ) ;
90+ } )
91+ {
92+ text = "Assign as the Project-wide Input Actions"
93+ } ;
94+ assignButton . SetEnabled ( ! EditorApplication . isPlayingOrWillChangePlaymode ) ;
95+ container . Add ( assignButton ) ;
96+ }
4097
41- // Project-wide Input Actions Asset UI.
42- InputAssetEditorUtils . CreateMakeActiveGui ( InputSystem . actions , inputActionAsset ,
43- inputActionAsset ? inputActionAsset . name : "Null" , "Project-wide Input Actions" ,
44- ( value ) => InputSystem . actions = value , ! EditorApplication . isPlayingOrWillChangePlaymode ) ;
98+ private void BuildCodeGenerationSection ( VisualElement root , InputActionAsset inputActionAsset )
99+ {
100+ var generateField = new PropertyField (
101+ serializedObject . FindProperty ( "m_GenerateWrapperCode" ) , "Generate C# Class" ) ;
102+ root . Add ( generateField ) ;
45103
46- EditorGUILayout . Space ( ) ;
104+ var codeGenContainer = new VisualElement ( ) ;
105+ root . Add ( codeGenContainer ) ;
47106
48- // Importer settings UI.
49- var generateWrapperCodeProperty = serializedObject . FindProperty ( "m_GenerateWrapperCode" ) ;
50- EditorGUILayout . PropertyField ( generateWrapperCodeProperty , m_GenerateWrapperCodeLabel ) ;
51- if ( generateWrapperCodeProperty . boolValue )
107+ // File path with browse button
108+ string defaultFileName = "" ;
109+ if ( inputActionAsset != null )
52110 {
53- var wrapperCodePathProperty = serializedObject . FindProperty ( "m_WrapperCodePath" ) ;
54- var wrapperClassNameProperty = serializedObject . FindProperty ( "m_WrapperClassName" ) ;
55- var wrapperCodeNamespaceProperty = serializedObject . FindProperty ( "m_WrapperCodeNamespace" ) ;
111+ var assetPath = AssetDatabase . GetAssetPath ( inputActionAsset ) ;
112+ defaultFileName = Path . ChangeExtension ( assetPath , ".cs" ) ;
113+ }
114+
115+ var pathRow = new VisualElement ( ) ;
116+ pathRow . style . flexDirection = FlexDirection . Row ;
117+ pathRow . style . alignItems = Align . Center ;
118+ codeGenContainer . Add ( pathRow ) ;
56119
57- EditorGUILayout . BeginHorizontal ( ) ;
120+ var pathField = new TextField ( "C# Class File" ) { bindingPath = "m_WrapperCodePath" } ;
121+ pathField . style . flexGrow = 1 ;
122+ pathField . AddToClassList ( BaseField < string > . alignedFieldUssClassName ) ;
123+ SetupPlaceholder ( pathField , defaultFileName ) ;
124+ pathRow . Add ( pathField ) ;
58125
59- string defaultFileName = "" ;
60- if ( inputActionAsset != null )
126+ var browseButton = new Button ( ( ) =>
127+ {
128+ var fileName = EditorUtility . SaveFilePanel ( "Location for generated C# file" ,
129+ Path . GetDirectoryName ( defaultFileName ) ,
130+ Path . GetFileName ( defaultFileName ) , "cs" ) ;
131+ if ( ! string . IsNullOrEmpty ( fileName ) )
61132 {
62- var assetPath = AssetDatabase . GetAssetPath ( inputActionAsset ) ;
63- defaultFileName = Path . ChangeExtension ( assetPath , ".cs" ) ;
133+ if ( fileName . StartsWith ( Application . dataPath ) )
134+ fileName = "Assets/" + fileName . Substring ( Application . dataPath . Length + 1 ) ;
135+
136+ var prop = serializedObject . FindProperty ( "m_WrapperCodePath" ) ;
137+ prop . stringValue = fileName ;
138+ serializedObject . ApplyModifiedProperties ( ) ;
64139 }
140+ } )
141+ {
142+ text = "…"
143+ } ;
144+ browseButton . style . width = 25 ;
145+ browseButton . style . minWidth = 25 ;
146+ pathRow . Add ( browseButton ) ;
65147
66- wrapperCodePathProperty . PropertyFieldWithDefaultText ( m_WrapperCodePathLabel , defaultFileName ) ;
148+ // Class name
149+ string typeName = inputActionAsset != null
150+ ? CSharpCodeHelpers . MakeTypeName ( inputActionAsset . name )
151+ : null ;
67152
68- if ( GUILayout . Button ( "…" , EditorStyles . miniButton , GUILayout . MaxWidth ( 20 ) ) )
69- {
70- var fileName = EditorUtility . SaveFilePanel ( "Location for generated C# file" ,
71- Path . GetDirectoryName ( defaultFileName ) ,
72- Path . GetFileName ( defaultFileName ) , "cs" ) ;
73- if ( ! string . IsNullOrEmpty ( fileName ) )
74- {
75- if ( fileName . StartsWith ( Application . dataPath ) )
76- fileName = "Assets/" + fileName . Substring ( Application . dataPath . Length + 1 ) ;
77-
78- wrapperCodePathProperty . stringValue = fileName ;
79- }
80- }
81- EditorGUILayout . EndHorizontal ( ) ;
153+ var classNameField = new TextField ( "C# Class Name" ) { bindingPath = "m_WrapperClassName" } ;
154+ classNameField . AddToClassList ( BaseField < string > . alignedFieldUssClassName ) ;
155+ SetupPlaceholder ( classNameField , typeName ?? "<Class name>" ) ;
156+ codeGenContainer . Add ( classNameField ) ;
82157
83- string typeName = null ;
84- if ( inputActionAsset != null )
85- typeName = CSharpCodeHelpers . MakeTypeName ( inputActionAsset ? . name ) ;
86- wrapperClassNameProperty . PropertyFieldWithDefaultText ( m_WrapperClassNameLabel , typeName ?? "<Class name>" ) ;
158+ var classNameError = new HelpBox ( "Must be a valid C# identifier" , HelpBoxMessageType . Error ) ;
159+ codeGenContainer . Add ( classNameError ) ;
87160
88- if ( ! CSharpCodeHelpers . IsEmptyOrProperIdentifier ( wrapperClassNameProperty . stringValue ) )
89- EditorGUILayout . HelpBox ( "Must be a valid C# identifier" , MessageType . Error ) ;
161+ var classNameProp = serializedObject . FindProperty ( "m_WrapperClassName" ) ;
162+ classNameError . style . display = ! CSharpCodeHelpers . IsEmptyOrProperIdentifier ( classNameProp . stringValue )
163+ ? DisplayStyle . Flex : DisplayStyle . None ;
90164
91- wrapperCodeNamespaceProperty . PropertyFieldWithDefaultText ( m_WrapperCodeNamespaceLabel , "<Global namespace>" ) ;
165+ classNameField . RegisterValueChangedCallback ( evt =>
166+ {
167+ classNameError . style . display = ! CSharpCodeHelpers . IsEmptyOrProperIdentifier ( evt . newValue )
168+ ? DisplayStyle . Flex : DisplayStyle . None ;
169+ } ) ;
92170
93- if ( ! CSharpCodeHelpers . IsEmptyOrProperNamespaceName ( wrapperCodeNamespaceProperty . stringValue ) )
94- EditorGUILayout . HelpBox ( "Must be a valid C# namespace name" , MessageType . Error ) ;
95- }
171+ // Namespace
172+ var namespaceField = new TextField ( "C# Class Namespace" ) { bindingPath = "m_WrapperCodeNamespace" } ;
173+ namespaceField . AddToClassList ( BaseField < string > . alignedFieldUssClassName ) ;
174+ SetupPlaceholder ( namespaceField , "<Global namespace>" ) ;
175+ codeGenContainer . Add ( namespaceField ) ;
176+
177+ var namespaceError = new HelpBox ( "Must be a valid C# namespace name" , HelpBoxMessageType . Error ) ;
178+ codeGenContainer . Add ( namespaceError ) ;
96179
97- // Using ApplyRevertGUI requires calling Update and ApplyModifiedProperties around the serializedObject,
98- // and will print warning messages otherwise (see warning message in ApplyRevertGUI implementation).
99- serializedObject . ApplyModifiedProperties ( ) ;
180+ var namespaceProp = serializedObject . FindProperty ( "m_WrapperCodeNamespace" ) ;
181+ namespaceError . style . display = ! CSharpCodeHelpers . IsEmptyOrProperNamespaceName ( namespaceProp . stringValue )
182+ ? DisplayStyle . Flex : DisplayStyle . None ;
100183
101- ApplyRevertGUI ( ) ;
184+ namespaceField . RegisterValueChangedCallback ( evt =>
185+ {
186+ namespaceError . style . display = ! CSharpCodeHelpers . IsEmptyOrProperNamespaceName ( evt . newValue )
187+ ? DisplayStyle . Flex : DisplayStyle . None ;
188+ } ) ;
189+
190+ // Show/hide code gen fields based on toggle
191+ var generateProp = serializedObject . FindProperty ( "m_GenerateWrapperCode" ) ;
192+ codeGenContainer . style . display = generateProp . boolValue ? DisplayStyle . Flex : DisplayStyle . None ;
193+
194+ generateField . RegisterValueChangeCallback ( evt =>
195+ {
196+ codeGenContainer . style . display = evt . changedProperty . boolValue
197+ ? DisplayStyle . Flex : DisplayStyle . None ;
198+ } ) ;
199+ }
200+
201+ private static void SetupPlaceholder ( TextField textField , string placeholder )
202+ {
203+ if ( string . IsNullOrEmpty ( placeholder ) )
204+ return ;
205+
206+ var placeholderLabel = new Label ( placeholder ) ;
207+ placeholderLabel . pickingMode = PickingMode . Ignore ;
208+ placeholderLabel . style . position = Position . Absolute ;
209+ placeholderLabel . style . unityTextAlign = TextAnchor . MiddleLeft ;
210+ placeholderLabel . style . opacity = 0.5f ;
211+ placeholderLabel . style . paddingLeft = 2 ;
212+
213+ textField . RegisterCallback < GeometryChangedEvent > ( _ =>
214+ {
215+ var textInput = textField . Q ( "unity-text-input" ) ;
216+ if ( textInput != null && placeholderLabel . parent != textInput )
217+ {
218+ textInput . Add ( placeholderLabel ) ;
219+ UpdatePlaceholder ( textField , placeholderLabel ) ;
220+ }
221+ } ) ;
222+
223+ textField . RegisterValueChangedCallback ( _ => UpdatePlaceholder ( textField , placeholderLabel ) ) ;
224+ textField . RegisterCallback < FocusInEvent > ( _ => placeholderLabel . style . display = DisplayStyle . None ) ;
225+ textField . RegisterCallback < FocusOutEvent > ( _ => UpdatePlaceholder ( textField , placeholderLabel ) ) ;
226+ }
227+
228+ private static void UpdatePlaceholder ( TextField textField , Label placeholder )
229+ {
230+ placeholder . style . display = string . IsNullOrEmpty ( textField . value )
231+ ? DisplayStyle . Flex : DisplayStyle . None ;
102232 }
103233
104234 private InputActionAsset GetAsset ( )
@@ -131,7 +261,6 @@ private string GetOpenEditorButtonText(InputActionAsset asset)
131261
132262 private static void OpenEditor ( InputActionAsset asset )
133263 {
134- // Redirect to Project-settings Input Actions editor if this is the project-wide actions asset
135264 if ( IsProjectWideActionsAsset ( asset ) )
136265 {
137266 SettingsService . OpenProjectSettings ( InputSettingsPath . kSettingsRootPath ) ;
@@ -140,11 +269,6 @@ private static void OpenEditor(InputActionAsset asset)
140269
141270 InputActionsEditorWindow . OpenEditor ( asset ) ;
142271 }
143-
144- private readonly GUIContent m_GenerateWrapperCodeLabel = EditorGUIUtility . TrTextContent ( "Generate C# Class" ) ;
145- private readonly GUIContent m_WrapperCodePathLabel = EditorGUIUtility . TrTextContent ( "C# Class File" ) ;
146- private readonly GUIContent m_WrapperClassNameLabel = EditorGUIUtility . TrTextContent ( "C# Class Name" ) ;
147- private readonly GUIContent m_WrapperCodeNamespaceLabel = EditorGUIUtility . TrTextContent ( "C# Class Namespace" ) ;
148272 }
149273}
150274#endif // UNITY_EDITOR
0 commit comments