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,212 @@ 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
22- // ScriptedImporterEditor in 2019.2 now requires explicitly updating the SerializedObject
23- // like in other types of editors.
24- serializedObject . Update ( ) ;
24+ if ( inputActionAsset == null )
25+ {
26+ root . Add ( new HelpBox (
27+ "The currently selected object is not an editable input action asset." ,
28+ HelpBoxMessageType . Info ) ) ;
29+ }
2530
26- EditorGUILayout . Space ( ) ;
31+ var editButton = new Button ( ( ) => OpenEditor ( inputActionAsset ) )
32+ {
33+ text = GetOpenEditorButtonText ( inputActionAsset )
34+ } ;
35+ editButton . style . height = 30 ;
36+ editButton . style . marginTop = 4 ;
37+ editButton . style . marginBottom = 4 ;
38+ editButton . SetEnabled ( inputActionAsset != null ) ;
39+ root . Add ( editButton ) ;
2740
28- if ( inputActionAsset == null )
29- EditorGUILayout . HelpBox ( "The currently selected object is not an editable input action asset." ,
30- MessageType . Info ) ;
41+ var projectWideContainer = new VisualElement ( ) ;
42+ projectWideContainer . style . marginTop = 6 ;
43+ projectWideContainer . style . marginBottom = 6 ;
44+ root . Add ( projectWideContainer ) ;
45+ BuildProjectWideSection ( projectWideContainer , inputActionAsset ) ;
46+
47+ BuildCodeGenerationSection ( root , inputActionAsset ) ;
48+
49+ root . Add ( new IMGUIContainer ( ( ) =>
50+ {
51+ serializedObject . ApplyModifiedProperties ( ) ;
52+ ApplyRevertGUI ( ) ;
53+ } ) ) ;
54+
55+ return root ;
56+ }
57+
58+ private void BuildProjectWideSection ( VisualElement container , InputActionAsset inputActionAsset )
59+ {
60+ container . Clear ( ) ;
61+
62+ var currentActions = InputSystem . actions ;
3163
32- // Button to pop up window to edit the asset.
33- using ( new EditorGUI . DisabledScope ( inputActionAsset == null ) )
64+ if ( currentActions == inputActionAsset )
3465 {
35- if ( GUILayout . Button ( GetOpenEditorButtonText ( inputActionAsset ) , GUILayout . Height ( 30 ) ) )
36- OpenEditor ( inputActionAsset ) ;
66+ container . Add ( new HelpBox (
67+ "These actions are assigned as the Project-wide Input Actions." ,
68+ HelpBoxMessageType . Info ) ) ;
69+ return ;
70+ }
71+
72+ var message = "These actions are not assigned as the Project-wide Input Actions for the Input System." ;
73+ if ( currentActions != null )
74+ {
75+ var currentPath = AssetDatabase . GetAssetPath ( currentActions ) ;
76+ if ( ! string . IsNullOrEmpty ( currentPath ) )
77+ message += $ " The actions currently assigned as the Project-wide Input Actions are: { currentPath } . ";
3778 }
3879
39- EditorGUILayout . Space ( ) ;
80+ container . Add ( new HelpBox ( message , HelpBoxMessageType . Warning ) ) ;
81+
82+ var assignButton = new Button ( ( ) =>
83+ {
84+ InputSystem . actions = inputActionAsset ;
85+ BuildProjectWideSection ( container , inputActionAsset ) ;
86+ } )
87+ {
88+ text = "Assign as the Project-wide Input Actions"
89+ } ;
90+ assignButton . SetEnabled ( ! EditorApplication . isPlayingOrWillChangePlaymode ) ;
91+ container . Add ( assignButton ) ;
92+ }
4093
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 ) ;
94+ private void BuildCodeGenerationSection ( VisualElement root , InputActionAsset inputActionAsset )
95+ {
96+ var generateField = new PropertyField (
97+ serializedObject . FindProperty ( "m_GenerateWrapperCode" ) , "Generate C# Class" ) ;
98+ root . Add ( generateField ) ;
4599
46- EditorGUILayout . Space ( ) ;
100+ var codeGenContainer = new VisualElement ( ) ;
101+ root . Add ( codeGenContainer ) ;
47102
48- // Importer settings UI.
49- var generateWrapperCodeProperty = serializedObject . FindProperty ( "m_GenerateWrapperCode" ) ;
50- EditorGUILayout . PropertyField ( generateWrapperCodeProperty , m_GenerateWrapperCodeLabel ) ;
51- if ( generateWrapperCodeProperty . boolValue )
103+ // File path with browse button
104+ string defaultFileName = "" ;
105+ if ( inputActionAsset != null )
52106 {
53- var wrapperCodePathProperty = serializedObject . FindProperty ( "m_WrapperCodePath" ) ;
54- var wrapperClassNameProperty = serializedObject . FindProperty ( "m_WrapperClassName" ) ;
55- var wrapperCodeNamespaceProperty = serializedObject . FindProperty ( "m_WrapperCodeNamespace" ) ;
107+ var assetPath = AssetDatabase . GetAssetPath ( inputActionAsset ) ;
108+ defaultFileName = Path . ChangeExtension ( assetPath , ".cs" ) ;
109+ }
110+
111+ var pathRow = new VisualElement ( ) ;
112+ pathRow . style . flexDirection = FlexDirection . Row ;
113+ pathRow . style . alignItems = Align . Center ;
114+ codeGenContainer . Add ( pathRow ) ;
56115
57- EditorGUILayout . BeginHorizontal ( ) ;
116+ var pathField = new TextField ( "C# Class File" ) { bindingPath = "m_WrapperCodePath" } ;
117+ pathField . style . flexGrow = 1 ;
118+ SetupPlaceholder ( pathField , defaultFileName ) ;
119+ pathRow . Add ( pathField ) ;
58120
59- string defaultFileName = "" ;
60- if ( inputActionAsset != null )
121+ var browseButton = new Button ( ( ) =>
122+ {
123+ var fileName = EditorUtility . SaveFilePanel ( "Location for generated C# file" ,
124+ Path . GetDirectoryName ( defaultFileName ) ,
125+ Path . GetFileName ( defaultFileName ) , "cs" ) ;
126+ if ( ! string . IsNullOrEmpty ( fileName ) )
61127 {
62- var assetPath = AssetDatabase . GetAssetPath ( inputActionAsset ) ;
63- defaultFileName = Path . ChangeExtension ( assetPath , ".cs" ) ;
128+ if ( fileName . StartsWith ( Application . dataPath ) )
129+ fileName = "Assets/" + fileName . Substring ( Application . dataPath . Length + 1 ) ;
130+
131+ var prop = serializedObject . FindProperty ( "m_WrapperCodePath" ) ;
132+ prop . stringValue = fileName ;
133+ serializedObject . ApplyModifiedProperties ( ) ;
64134 }
135+ } )
136+ {
137+ text = "…"
138+ } ;
139+ browseButton . style . width = 25 ;
140+ browseButton . style . minWidth = 25 ;
141+ pathRow . Add ( browseButton ) ;
65142
66- wrapperCodePathProperty . PropertyFieldWithDefaultText ( m_WrapperCodePathLabel , defaultFileName ) ;
143+ // Class name
144+ string typeName = inputActionAsset != null
145+ ? CSharpCodeHelpers . MakeTypeName ( inputActionAsset . name )
146+ : null ;
67147
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 ( ) ;
148+ var classNameField = new TextField ( "C# Class Name" ) { bindingPath = "m_WrapperClassName" } ;
149+ SetupPlaceholder ( classNameField , typeName ?? "<Class name>" ) ;
150+ codeGenContainer . Add ( classNameField ) ;
82151
83- string typeName = null ;
84- if ( inputActionAsset != null )
85- typeName = CSharpCodeHelpers . MakeTypeName ( inputActionAsset ? . name ) ;
86- wrapperClassNameProperty . PropertyFieldWithDefaultText ( m_WrapperClassNameLabel , typeName ?? "<Class name>" ) ;
152+ var classNameError = new HelpBox ( "Must be a valid C# identifier" , HelpBoxMessageType . Error ) ;
153+ codeGenContainer . Add ( classNameError ) ;
87154
88- if ( ! CSharpCodeHelpers . IsEmptyOrProperIdentifier ( wrapperClassNameProperty . stringValue ) )
89- EditorGUILayout . HelpBox ( "Must be a valid C# identifier" , MessageType . Error ) ;
155+ var classNameProp = serializedObject . FindProperty ( "m_WrapperClassName" ) ;
156+ classNameError . style . display = ! CSharpCodeHelpers . IsEmptyOrProperIdentifier ( classNameProp . stringValue )
157+ ? DisplayStyle . Flex : DisplayStyle . None ;
90158
91- wrapperCodeNamespaceProperty . PropertyFieldWithDefaultText ( m_WrapperCodeNamespaceLabel , "<Global namespace>" ) ;
159+ classNameField . RegisterValueChangedCallback ( evt =>
160+ {
161+ classNameError . style . display = ! CSharpCodeHelpers . IsEmptyOrProperIdentifier ( evt . newValue )
162+ ? DisplayStyle . Flex : DisplayStyle . None ;
163+ } ) ;
92164
93- if ( ! CSharpCodeHelpers . IsEmptyOrProperNamespaceName ( wrapperCodeNamespaceProperty . stringValue ) )
94- EditorGUILayout . HelpBox ( "Must be a valid C# namespace name" , MessageType . Error ) ;
95- }
165+ // Namespace
166+ var namespaceField = new TextField ( "C# Class Namespace" ) { bindingPath = "m_WrapperCodeNamespace" } ;
167+ SetupPlaceholder ( namespaceField , "<Global namespace>" ) ;
168+ codeGenContainer . Add ( namespaceField ) ;
169+
170+ var namespaceError = new HelpBox ( "Must be a valid C# namespace name" , HelpBoxMessageType . Error ) ;
171+ codeGenContainer . Add ( namespaceError ) ;
96172
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 ( ) ;
173+ var namespaceProp = serializedObject . FindProperty ( "m_WrapperCodeNamespace" ) ;
174+ namespaceError . style . display = ! CSharpCodeHelpers . IsEmptyOrProperNamespaceName ( namespaceProp . stringValue )
175+ ? DisplayStyle . Flex : DisplayStyle . None ;
100176
101- ApplyRevertGUI ( ) ;
177+ namespaceField . RegisterValueChangedCallback ( evt =>
178+ {
179+ namespaceError . style . display = ! CSharpCodeHelpers . IsEmptyOrProperNamespaceName ( evt . newValue )
180+ ? DisplayStyle . Flex : DisplayStyle . None ;
181+ } ) ;
182+
183+ // Show/hide code gen fields based on toggle
184+ var generateProp = serializedObject . FindProperty ( "m_GenerateWrapperCode" ) ;
185+ codeGenContainer . style . display = generateProp . boolValue ? DisplayStyle . Flex : DisplayStyle . None ;
186+
187+ generateField . RegisterValueChangeCallback ( evt =>
188+ {
189+ codeGenContainer . style . display = evt . changedProperty . boolValue
190+ ? DisplayStyle . Flex : DisplayStyle . None ;
191+ } ) ;
192+ }
193+
194+ private static void SetupPlaceholder ( TextField textField , string placeholder )
195+ {
196+ if ( string . IsNullOrEmpty ( placeholder ) )
197+ return ;
198+
199+ var placeholderLabel = new Label ( placeholder ) ;
200+ placeholderLabel . pickingMode = PickingMode . Ignore ;
201+ placeholderLabel . style . position = Position . Absolute ;
202+ placeholderLabel . style . unityTextAlign = TextAnchor . MiddleLeft ;
203+ placeholderLabel . style . opacity = 0.5f ;
204+ placeholderLabel . style . paddingLeft = 2 ;
205+
206+ textField . RegisterCallback < GeometryChangedEvent > ( _ =>
207+ {
208+ var textInput = textField . Q ( "unity-text-input" ) ;
209+ if ( textInput != null && placeholderLabel . parent != textInput )
210+ {
211+ textInput . Add ( placeholderLabel ) ;
212+ UpdatePlaceholder ( textField , placeholderLabel ) ;
213+ }
214+ } ) ;
215+
216+ textField . RegisterValueChangedCallback ( _ => UpdatePlaceholder ( textField , placeholderLabel ) ) ;
217+ textField . RegisterCallback < FocusInEvent > ( _ => placeholderLabel . style . display = DisplayStyle . None ) ;
218+ textField . RegisterCallback < FocusOutEvent > ( _ => UpdatePlaceholder ( textField , placeholderLabel ) ) ;
219+ }
220+
221+ private static void UpdatePlaceholder ( TextField textField , Label placeholder )
222+ {
223+ placeholder . style . display = string . IsNullOrEmpty ( textField . value )
224+ ? DisplayStyle . Flex : DisplayStyle . None ;
102225 }
103226
104227 private InputActionAsset GetAsset ( )
@@ -131,7 +254,6 @@ private string GetOpenEditorButtonText(InputActionAsset asset)
131254
132255 private static void OpenEditor ( InputActionAsset asset )
133256 {
134- // Redirect to Project-settings Input Actions editor if this is the project-wide actions asset
135257 if ( IsProjectWideActionsAsset ( asset ) )
136258 {
137259 SettingsService . OpenProjectSettings ( InputSettingsPath . kSettingsRootPath ) ;
@@ -140,11 +262,6 @@ private static void OpenEditor(InputActionAsset asset)
140262
141263 InputActionsEditorWindow . OpenEditor ( asset ) ;
142264 }
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" ) ;
148265 }
149266}
150267#endif // UNITY_EDITOR
0 commit comments