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,215 @@ 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 ( ) ;
22+ root . styleSheets . Add ( AssetDatabase . LoadAssetAtPath < StyleSheet > (
23+ InputActionsEditorConstants . PackagePath +
24+ "/InputSystem/Editor/AssetImporter/InputActionImporterEditor.uss" ) ) ;
2025 var inputActionAsset = GetAsset ( ) ;
2126
2227 // ScriptedImporterEditor in 2019.2 now requires explicitly updating the SerializedObject
2328 // like in other types of editors.
2429 serializedObject . Update ( ) ;
2530
26- EditorGUILayout . Space ( ) ;
27-
2831 if ( inputActionAsset == null )
29- EditorGUILayout . HelpBox ( "The currently selected object is not an editable input action asset." ,
30- MessageType . Info ) ;
32+ {
33+ root . Add ( new HelpBox (
34+ "The currently selected object is not an editable input action asset." ,
35+ HelpBoxMessageType . Info ) ) ;
36+ }
37+
38+ var editButton = new Button ( ( ) => OpenEditor ( inputActionAsset ) )
39+ {
40+ text = GetOpenEditorButtonText ( inputActionAsset )
41+ } ;
42+ editButton . AddToClassList ( "input-action-importer-editor__edit-button" ) ;
43+ editButton . SetEnabled ( inputActionAsset != null ) ;
44+ root . Add ( editButton ) ;
45+
46+ var projectWideContainer = new VisualElement ( ) ;
47+ projectWideContainer . AddToClassList ( "input-action-importer-editor__project-wide-container" ) ;
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 ( ) ;
3165
32- // Button to pop up window to edit the asset.
33- using ( new EditorGUI . DisabledScope ( inputActionAsset == null ) )
66+ var currentActions = InputSystem . actions ;
67+
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 ;
3774 }
3875
39- EditorGUILayout . Space ( ) ;
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 } . ";
82+ }
4083
41- // Project-wide Input Actions Asset UI.
42- InputAssetEditorUtils . DrawMakeActiveGui ( InputSystem . actions , inputActionAsset ,
43- inputActionAsset ? inputActionAsset . name : "Null" , "Project-wide Input Actions" ,
44- ( value ) => InputSystem . actions = value , ! EditorApplication . isPlayingOrWillChangePlaymode ) ;
84+ container . Add ( new HelpBox ( message , HelpBoxMessageType . Warning ) ) ;
4585
46- EditorGUILayout . Space ( ) ;
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 . AddToClassList ( "input-action-importer-editor__assign-button" ) ;
95+ assignButton . SetEnabled ( ! EditorApplication . isPlayingOrWillChangePlaymode ) ;
96+ container . Add ( assignButton ) ;
97+ }
4798
48- // Importer settings UI.
49- var generateWrapperCodeProperty = serializedObject . FindProperty ( "m_GenerateWrapperCode" ) ;
50- EditorGUILayout . PropertyField ( generateWrapperCodeProperty , m_GenerateWrapperCodeLabel ) ;
51- if ( generateWrapperCodeProperty . boolValue )
99+ private void BuildCodeGenerationSection ( VisualElement root , InputActionAsset inputActionAsset )
100+ {
101+ var generateField = new PropertyField (
102+ serializedObject . FindProperty ( "m_GenerateWrapperCode" ) , "Generate C# Class" ) ;
103+ root . Add ( generateField ) ;
104+
105+ var codeGenContainer = new VisualElement ( ) ;
106+ root . Add ( codeGenContainer ) ;
107+
108+ // File path with browse button
109+ string defaultFileName = "" ;
110+ if ( inputActionAsset != null )
52111 {
53- var wrapperCodePathProperty = serializedObject . FindProperty ( "m_WrapperCodePath" ) ;
54- var wrapperClassNameProperty = serializedObject . FindProperty ( "m_WrapperClassName" ) ;
55- var wrapperCodeNamespaceProperty = serializedObject . FindProperty ( "m_WrapperCodeNamespace" ) ;
112+ var assetPath = AssetDatabase . GetAssetPath ( inputActionAsset ) ;
113+ defaultFileName = Path . ChangeExtension ( assetPath , ".cs" ) ;
114+ }
115+
116+ var pathRow = new VisualElement ( ) ;
117+ pathRow . AddToClassList ( "input-action-importer-editor__path-row" ) ;
118+ codeGenContainer . Add ( pathRow ) ;
56119
57- EditorGUILayout . BeginHorizontal ( ) ;
120+ var pathField = new TextField ( "C# Class File" ) { bindingPath = "m_WrapperCodePath" } ;
121+ pathField . AddToClassList ( "input-action-importer-editor__path-field" ) ;
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 . AddToClassList ( "input-action-importer-editor__browse-button" ) ;
145+ pathRow . Add ( browseButton ) ;
65146
66- wrapperCodePathProperty . PropertyFieldWithDefaultText ( m_WrapperCodePathLabel , defaultFileName ) ;
147+ // Class name
148+ string typeName = inputActionAsset != null
149+ ? CSharpCodeHelpers . MakeTypeName ( inputActionAsset . name )
150+ : null ;
67151
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 ( ) ;
152+ var classNameField = new TextField ( "C# Class Name" ) { bindingPath = "m_WrapperClassName" } ;
153+ classNameField . AddToClassList ( BaseField < string > . alignedFieldUssClassName ) ;
154+ SetupPlaceholder ( classNameField , typeName ?? "<Class name>" ) ;
155+ codeGenContainer . Add ( classNameField ) ;
82156
83- string typeName = null ;
84- if ( inputActionAsset != null )
85- typeName = CSharpCodeHelpers . MakeTypeName ( inputActionAsset ? . name ) ;
86- wrapperClassNameProperty . PropertyFieldWithDefaultText ( m_WrapperClassNameLabel , typeName ?? "<Class name>" ) ;
157+ var classNameError = new HelpBox ( "Must be a valid C# identifier" , HelpBoxMessageType . Error ) ;
158+ codeGenContainer . Add ( classNameError ) ;
87159
88- if ( ! CSharpCodeHelpers . IsEmptyOrProperIdentifier ( wrapperClassNameProperty . stringValue ) )
89- EditorGUILayout . HelpBox ( "Must be a valid C# identifier" , MessageType . Error ) ;
160+ var classNameProp = serializedObject . FindProperty ( "m_WrapperClassName" ) ;
161+ classNameError . style . display = ! CSharpCodeHelpers . IsEmptyOrProperIdentifier ( classNameProp . stringValue )
162+ ? DisplayStyle . Flex : DisplayStyle . None ;
90163
91- wrapperCodeNamespaceProperty . PropertyFieldWithDefaultText ( m_WrapperCodeNamespaceLabel , "<Global namespace>" ) ;
164+ classNameField . RegisterValueChangedCallback ( evt =>
165+ {
166+ classNameError . style . display = ! CSharpCodeHelpers . IsEmptyOrProperIdentifier ( evt . newValue )
167+ ? DisplayStyle . Flex : DisplayStyle . None ;
168+ } ) ;
92169
93- if ( ! CSharpCodeHelpers . IsEmptyOrProperNamespaceName ( wrapperCodeNamespaceProperty . stringValue ) )
94- EditorGUILayout . HelpBox ( "Must be a valid C# namespace name" , MessageType . Error ) ;
95- }
170+ // Namespace
171+ var namespaceField = new TextField ( "C# Class Namespace" ) { bindingPath = "m_WrapperCodeNamespace" } ;
172+ namespaceField . AddToClassList ( BaseField < string > . alignedFieldUssClassName ) ;
173+ SetupPlaceholder ( namespaceField , "<Global namespace>" ) ;
174+ codeGenContainer . Add ( namespaceField ) ;
175+
176+ var namespaceError = new HelpBox ( "Must be a valid C# namespace name" , HelpBoxMessageType . Error ) ;
177+ codeGenContainer . Add ( namespaceError ) ;
96178
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 ( ) ;
179+ var namespaceProp = serializedObject . FindProperty ( "m_WrapperCodeNamespace" ) ;
180+ namespaceError . style . display = ! CSharpCodeHelpers . IsEmptyOrProperNamespaceName ( namespaceProp . stringValue )
181+ ? DisplayStyle . Flex : DisplayStyle . None ;
100182
101- ApplyRevertGUI ( ) ;
183+ namespaceField . RegisterValueChangedCallback ( evt =>
184+ {
185+ namespaceError . style . display = ! CSharpCodeHelpers . IsEmptyOrProperNamespaceName ( evt . newValue )
186+ ? DisplayStyle . Flex : DisplayStyle . None ;
187+ } ) ;
188+
189+ // Show/hide code gen fields based on toggle
190+ var generateProp = serializedObject . FindProperty ( "m_GenerateWrapperCode" ) ;
191+ codeGenContainer . style . display = generateProp . boolValue ? DisplayStyle . Flex : DisplayStyle . None ;
192+
193+ generateField . RegisterValueChangeCallback ( evt =>
194+ {
195+ codeGenContainer . style . display = evt . changedProperty . boolValue
196+ ? DisplayStyle . Flex : DisplayStyle . None ;
197+ } ) ;
198+ }
199+
200+ private static void SetupPlaceholder ( TextField textField , string placeholder )
201+ {
202+ if ( string . IsNullOrEmpty ( placeholder ) )
203+ return ;
204+
205+ var placeholderLabel = new Label ( placeholder ) ;
206+ placeholderLabel . pickingMode = PickingMode . Ignore ;
207+ placeholderLabel . AddToClassList ( "input-action-importer-editor__placeholder" ) ;
208+
209+ textField . RegisterCallback < GeometryChangedEvent > ( _ =>
210+ {
211+ var textInput = textField . Q ( "unity-text-input" ) ;
212+ if ( textInput != null && placeholderLabel . parent != textInput )
213+ {
214+ textInput . Add ( placeholderLabel ) ;
215+ UpdatePlaceholder ( textField , placeholderLabel ) ;
216+ }
217+ } ) ;
218+
219+ textField . RegisterValueChangedCallback ( _ => UpdatePlaceholder ( textField , placeholderLabel ) ) ;
220+ textField . RegisterCallback < FocusInEvent > ( _ => placeholderLabel . style . display = DisplayStyle . None ) ;
221+ textField . RegisterCallback < FocusOutEvent > ( _ => UpdatePlaceholder ( textField , placeholderLabel ) ) ;
222+ }
223+
224+ private static void UpdatePlaceholder ( TextField textField , Label placeholder )
225+ {
226+ placeholder . style . display = string . IsNullOrEmpty ( textField . value )
227+ ? DisplayStyle . Flex : DisplayStyle . None ;
102228 }
103229
104230 private InputActionAsset GetAsset ( )
@@ -131,7 +257,6 @@ private string GetOpenEditorButtonText(InputActionAsset asset)
131257
132258 private static void OpenEditor ( InputActionAsset asset )
133259 {
134- // Redirect to Project-settings Input Actions editor if this is the project-wide actions asset
135260 if ( IsProjectWideActionsAsset ( asset ) )
136261 {
137262 SettingsService . OpenProjectSettings ( InputSettingsPath . kSettingsRootPath ) ;
@@ -140,11 +265,6 @@ private static void OpenEditor(InputActionAsset asset)
140265
141266 InputActionsEditorWindow . OpenEditor ( asset ) ;
142267 }
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" ) ;
148268 }
149269}
150270#endif // UNITY_EDITOR
0 commit comments