Skip to content

Commit 4b8f6a6

Browse files
Merge branch 'input/action-importer-uitoolkit' into input/uitoolkit-input-asset
2 parents 6c659ad + 55b4cfa commit 4b8f6a6

1 file changed

Lines changed: 187 additions & 63 deletions

File tree

Lines changed: 187 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
#if UNITY_EDITOR
2-
using System;
32
using System.IO;
43
using UnityEngine.InputSystem.Utilities;
54
using UnityEditor;
65
using 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

Comments
 (0)