Skip to content

Commit 7e6a029

Browse files
NEW: View-only UIToolkit version of Input Actions Asset Editor (#1668)
* Change yamato config to build develop-v2 branch, but only build against trunk * Basic UITK version of input asset editor Mostly read only for now. Doesn't contain things like drag and drop for list views, adding/delete/editing action maps, action, or bindings. * Implemented a view architecture for views to only update when relevant view state has changed * Fixed some styling across light and dark themes, and other styling issues * Show correct icons on actions and bindings * Fix InputControlPicker changes not being saved * Removing meta file that should have been removed before * State is now closer to properly immutable and removed usages of ReactiveProperty * Removed ReactiveProperty properties from state. All changes now go through the state container, and that should be the only way to get state change events. * Added the With method to GlobalInputActionsEditorState to make it easier to mutate into new instances. * Mutating state actions now return new instance of the state and the state container ensures that the latest version is used everywhere. * Remove Global from all names and moved everything to UITKAssetEditor folder * Add control scheme editor Also renamed UIToolkitView to ViewBase * Remove temporary test assets * Removed test editor window and made the new editor the default for opening input assets * Added project context menu to open legacy editor from input asset * Added ability to assign bindings to control schemes * Formatting * Fix issue in CompositeBindingPropertoesView where parameter values would not be updated correctly * Review changes * Added missing UNITY_EDITOR ifdefs * Change dry run CI config to use 2022.2.0a10 * Only show the UITK editor when the feature flag is set, and only on 2022.1 * Some changes to get things compiling on versions of Unity less than 2022.1 * More changes to fix build on 2019.4 * Update package to 1.5 The InputParameterEditor class had OnDrawVisualElements public method added to it. * Fix incorrect UIToolkit uss reference * Revert "Change yamato config to build develop-v2 branch, but only build against trunk" This reverts commit aaa54c1. * Revert "Update package to 1.5" This reverts commit 8657d1e. * Moved some UITK editor specific code behind 2022.1 conditionals The UITK asset editor is only usable on those versions of Unity * NEW: Add save and auto-save buttons * NEW: Add save and auto-save buttons * NEW: Recreate editor UI after domain reload * Review changes: restore SerializeObject in InputActionEditorState * Review changes: Move save logic out of the View * Review changes: Move save logic into the Window * NEW: UITK Editor separate action maps and actions views (#1661) * Replaced action map drop down with ListView and bindings view with tree view showing actions and bindings * Formatter * Disabled code for renaming items in action maps and actions view * Formatter * Replaced action map drop down with ListView and bindings view with tree view showing actions and bindings --------- Co-authored-by: Andrew O'Connor <andrew.oconnor@unity3d.com> * fix merge error (again) * fix formatting * fix compilation issues on 2019 * fix exception during API validation tests * fix missing public docs tests * fix Publish to Internal tests by restoring editor version * remove new menu item for legacy asset editor * replace removed code that seems necessary --------- Co-authored-by: Andrew O'Connor <andrew.oconnor@unity3d.com>
1 parent df3aa91 commit 7e6a029

117 files changed

Lines changed: 5106 additions & 5 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Assets/Samples/CustomComposite/CustomComposite.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using UnityEngine;
23
using UnityEngine.InputSystem;
34
using UnityEngine.InputSystem.Layouts;
@@ -6,6 +7,7 @@
67
#if UNITY_EDITOR
78
using UnityEditor;
89
using UnityEngine.InputSystem.Editor;
10+
using UnityEngine.UIElements;
911
#endif
1012

1113
// Let's say we want to have a composite that takes an axis and uses
@@ -175,6 +177,30 @@ public override void OnGUI()
175177
target.scaleFactor = EditorGUILayout.Slider(m_ScaleFactorLabel, currentValue, 0, 2);
176178
}
177179

180+
#if UNITY_2022_1_OR_NEWER
181+
public override void OnDrawVisualElements(VisualElement root, Action onChangedCallback)
182+
{
183+
var slider = new Slider(m_ScaleFactorLabel.text, 0, 2)
184+
{
185+
value = target.scaleFactor,
186+
showInputField = true
187+
};
188+
189+
// Note: For UIToolkit sliders, as of Feb 2022, we can't register for the mouse up event directly
190+
// on the slider because an element inside the slider captures the event. The workaround is to
191+
// register for the event on the slider container. This will be fixed in a future version of
192+
// UIToolkit.
193+
slider.Q("unity-drag-container").RegisterCallback<MouseUpEvent>(evt =>
194+
{
195+
target.scaleFactor = slider.value;
196+
onChangedCallback?.Invoke();
197+
});
198+
199+
root.Add(slider);
200+
}
201+
202+
#endif
203+
178204
private GUIContent m_ScaleFactorLabel = new GUIContent("Scale Factor");
179205
}
180206
#endif

Assets/Tests/InputSystem.Editor.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
#if UNITY_2022_1_OR_NEWER
2+
using NUnit.Framework;
3+
using UnityEngine.InputSystem;
4+
using UnityEngine.InputSystem.Editor;
5+
6+
public class ControlSchemesEditorTests
7+
{
8+
[Test]
9+
[Category("AssetEditor")]
10+
public void AddRequirementCommand_AddsDeviceRequirements()
11+
{
12+
var state = TestData.editorState.Generate()
13+
.With(selectedControlScheme: TestData.controlScheme.WithOptionalDevice().Generate());
14+
15+
16+
var deviceRequirement = TestData.deviceRequirement.Generate();
17+
var newState = ControlSchemeCommands.AddDeviceRequirement(deviceRequirement)(in state);
18+
19+
20+
Assert.That(newState.selectedControlScheme.deviceRequirements.Count, Is.EqualTo(2));
21+
Assert.That(newState.selectedControlScheme.deviceRequirements[1].m_ControlPath, Is.EqualTo(deviceRequirement.controlPath));
22+
}
23+
24+
[Test]
25+
[Category("AssetEditor")]
26+
public void RemoveRequirementCommand_RemovesDeviceRequirements()
27+
{
28+
var controlScheme = TestData.controlScheme.WithOptionalDevices(TestData.N(TestData.deviceRequirement, 2)).Generate();
29+
var state = TestData.editorState.Generate().With(selectedControlScheme: controlScheme);
30+
31+
32+
var newState = ControlSchemeCommands.RemoveDeviceRequirement(0)(in state);
33+
34+
35+
Assert.That(newState.selectedControlScheme.deviceRequirements.Count, Is.EqualTo(1));
36+
Assert.That(newState.selectedControlScheme.deviceRequirements[0].m_ControlPath,
37+
Is.EqualTo(controlScheme.deviceRequirements[1].controlPath));
38+
39+
40+
newState = ControlSchemeCommands.RemoveDeviceRequirement(0)(in newState);
41+
42+
43+
Assert.That(newState.selectedControlScheme.deviceRequirements.Count, Is.Zero);
44+
}
45+
46+
[Test]
47+
[Category("AssetEditor")]
48+
public void ChangeRequirementCommand_ChangesSelectedRequirement()
49+
{
50+
var state = TestData.editorState.Generate().With(
51+
selectedControlScheme: TestData.controlScheme.Generate()
52+
.WithRequiredDevice()
53+
.WithOptionalDevice());
54+
55+
56+
var newState = ControlSchemeCommands.ChangeDeviceRequirement(0, true)(in state);
57+
newState = ControlSchemeCommands.ChangeDeviceRequirement(1, false)(in newState);
58+
59+
60+
Assert.That(newState.selectedControlScheme.deviceRequirements[0].isOptional, Is.False);
61+
Assert.That(newState.selectedControlScheme.deviceRequirements[1].isOptional, Is.True);
62+
}
63+
64+
[Test]
65+
[Category("AssetEditor")]
66+
public void AddNewControlSchemeCommand_ClearsSelectedControlScheme()
67+
{
68+
var state = TestData.editorState.Generate().With(selectedControlScheme: TestData.controlScheme.Generate());
69+
70+
71+
var newState = ControlSchemeCommands.AddNewControlScheme()(in state);
72+
73+
74+
Assert.That(newState.selectedControlScheme, Is.EqualTo(new InputControlScheme("New Control Scheme")));
75+
}
76+
77+
[Test]
78+
[Category("AssetEditor")]
79+
public void AddNewControlSchemeCommand_GeneratesUniqueControlSchemeName()
80+
{
81+
var state = TestData.EditorStateWithAsset(TestData.inputActionAsset
82+
.WithControlScheme(TestData.controlScheme.Select(s => s.WithName("New Control Scheme")))
83+
.Generate())
84+
.Generate();
85+
86+
87+
var newState = ControlSchemeCommands.AddNewControlScheme()(in state);
88+
89+
90+
Assert.That(newState.selectedControlScheme.name, Is.EqualTo("New Control Scheme1"));
91+
}
92+
93+
[Test]
94+
[Category("AssetEditor")]
95+
public void SaveControlSchemeCommand_PersistsControlSchemeInSerializedObject()
96+
{
97+
var state = TestData.editorState.Generate()
98+
.With(selectedControlScheme: TestData.controlScheme
99+
.Generate()
100+
.WithOptionalDevice()
101+
.WithRequiredDevice());
102+
103+
104+
ControlSchemeCommands.SaveControlScheme()(in state);
105+
106+
107+
state.serializedObject.Update();
108+
109+
var serializedArray = state.serializedObject.FindProperty(nameof(InputActionAsset.m_ControlSchemes));
110+
var deviceRequirements = serializedArray.FirstOrDefault().FindPropertyRelative(nameof(InputControlScheme.m_DeviceRequirements));
111+
var first = deviceRequirements.GetArrayElementAtIndex(0);
112+
var second = deviceRequirements.GetArrayElementAtIndex(1);
113+
114+
Assert.That(serializedArray.arraySize, Is.EqualTo(1));
115+
Assert.That(serializedArray.FirstOrDefault().FindPropertyRelative(nameof(InputControlScheme.m_Name)).stringValue,
116+
Is.EqualTo(state.selectedControlScheme.name));
117+
118+
Assert.That(first.FindPropertyRelative(nameof(InputControlScheme.DeviceRequirement.m_ControlPath)).stringValue,
119+
Is.EqualTo(state.selectedControlScheme.deviceRequirements[0].controlPath));
120+
Assert.That(first.FindPropertyRelative(nameof(InputControlScheme.DeviceRequirement.m_Flags)).enumValueIndex,
121+
Is.EqualTo((int)InputControlScheme.DeviceRequirement.Flags.Optional));
122+
123+
Assert.That(second.FindPropertyRelative(nameof(InputControlScheme.DeviceRequirement.m_ControlPath)).stringValue,
124+
Is.EqualTo(state.selectedControlScheme.deviceRequirements[1].controlPath));
125+
Assert.That(second.FindPropertyRelative(nameof(InputControlScheme.DeviceRequirement.m_Flags)).enumValueIndex,
126+
Is.EqualTo((int)InputControlScheme.DeviceRequirement.Flags.None));
127+
}
128+
129+
[Test]
130+
[Category("AssetEditor")]
131+
public void SaveControlSchemeCommand_EnsuresUniqueControlSchemeName()
132+
{
133+
var asset = TestData.inputActionAsset
134+
.WithControlScheme(TestData.controlScheme.Select(s => s.WithName("Test")))
135+
.Generate();
136+
var state = TestData.EditorStateWithAsset(asset).Generate().With(selectedControlScheme: asset.controlSchemes[0]);
137+
138+
139+
var newState = ControlSchemeCommands.SaveControlScheme()(in state);
140+
141+
142+
var serializedControlScheme = newState.serializedObject
143+
.FindProperty(nameof(InputActionAsset.m_ControlSchemes))
144+
.FirstOrDefault(sp => sp.FindPropertyRelative(nameof(InputControlScheme.m_Name)).stringValue == "Test1");
145+
Assert.That(serializedControlScheme, Is.Not.Null);
146+
}
147+
148+
[Test]
149+
[Category("AssetEditor")]
150+
public void SaveControlSchemeCommand_SelectsNewControlSchemeAfterSaving()
151+
{
152+
var state = TestData.EditorStateWithAsset(
153+
TestData.inputActionAsset
154+
.WithControlSchemes(TestData.N(TestData.controlScheme, 2))
155+
.Generate()
156+
)
157+
.Generate()
158+
.With(selectedControlScheme: TestData.controlScheme.Generate());
159+
160+
161+
var newState = ControlSchemeCommands.SaveControlScheme()(in state);
162+
163+
164+
Assert.That(newState.selectedControlSchemeIndex, Is.EqualTo(2));
165+
}
166+
167+
[Test]
168+
[Category("AssetEditor")]
169+
public void WhenControlSchemeIsSelected_SelectedControlSchemeIndexIsSet()
170+
{
171+
var state = TestData.EditorStateWithAsset(
172+
TestData.inputActionAsset
173+
.WithControlSchemes(TestData.N(TestData.controlScheme, 2))
174+
.Generate()
175+
)
176+
.Generate();
177+
178+
var newState = ControlSchemeCommands.SelectControlScheme(1)(in state);
179+
180+
181+
Assert.That(newState.selectedControlSchemeIndex, Is.EqualTo(1));
182+
}
183+
184+
[Test]
185+
[Category("AssetEditor")]
186+
public void WhenControlSchemeIsSelected_SelectedControlSchemeIsPopulatedWithSelection()
187+
{
188+
var asset = TestData.inputActionAsset
189+
.WithControlSchemes(TestData.N(TestData.controlScheme, 2))
190+
.Generate();
191+
var state = TestData.EditorStateWithAsset(asset).Generate();
192+
193+
194+
var newState = ControlSchemeCommands.SelectControlScheme(0)(in state);
195+
196+
197+
Assert.That(newState.selectedControlScheme, Is.EqualTo(asset.controlSchemes[0]));
198+
199+
200+
newState = ControlSchemeCommands.SelectControlScheme(1)(in state);
201+
202+
203+
Assert.That(newState.selectedControlScheme, Is.EqualTo(asset.controlSchemes[1]));
204+
}
205+
206+
[Test]
207+
[Category("AssetEditor")]
208+
public void DuplicateControlSchemeCommand_CreatesCopyOfControlSchemeWithUniqueName()
209+
{
210+
var asset = TestData.inputActionAsset.WithControlScheme(TestData.controlScheme).Generate();
211+
var state = TestData.EditorStateWithAsset(asset).Generate().With(selectedControlScheme: asset.controlSchemes[0]);
212+
213+
214+
var newState = ControlSchemeCommands.DuplicateSelectedControlScheme()(in state);
215+
216+
217+
Assert.That(newState.selectedControlScheme.name, Is.EqualTo(state.selectedControlScheme.name + "1"));
218+
Assert.That(newState.selectedControlScheme.deviceRequirements, Is.EqualTo(state.selectedControlScheme.deviceRequirements));
219+
}
220+
221+
[Test]
222+
[Category("AssetEditor")]
223+
public void DeleteControlSchemeCommand_DeletesSelectedControlScheme()
224+
{
225+
var asset = TestData.inputActionAsset.WithControlScheme(TestData.controlScheme.WithOptionalDevice()).Generate();
226+
var state = TestData.EditorStateWithAsset(asset).Generate().With(selectedControlScheme: asset.controlSchemes[0]);
227+
228+
229+
var newState = ControlSchemeCommands.DeleteSelectedControlScheme()(in state);
230+
231+
232+
state.serializedObject.Update();
233+
var serializedArray = state.serializedObject.FindProperty(nameof(InputActionAsset.m_ControlSchemes));
234+
Assert.That(serializedArray.arraySize, Is.Zero);
235+
Assert.That(newState.selectedControlSchemeIndex, Is.EqualTo(-1));
236+
Assert.That(newState.selectedControlScheme, Is.EqualTo(new InputControlScheme()));
237+
}
238+
239+
[Test]
240+
[TestCase(3, 1, 1, "Test2")]
241+
[TestCase(3, 2, 1, "Test1")]
242+
[TestCase(1, 0, -1, null)]
243+
[Category("AssetEditor")]
244+
public void DeleteControlSchemeCommand_SelectsAnotherControlSchemeAfterDelete(
245+
int controlSchemeCount,
246+
int selectedControlSchemeIndex,
247+
int expectedNewSelectedControlSchemeIndex,
248+
string expectedNewSelectedControlSchemeName)
249+
{
250+
var asset = TestData.inputActionAsset.Generate();
251+
for (var i = 0; i < controlSchemeCount; i++)
252+
{
253+
asset.AddControlScheme(new InputControlScheme($"Test{i}"));
254+
}
255+
256+
var state = TestData.EditorStateWithAsset(asset)
257+
.Generate()
258+
.With(selectedControlScheme: asset.controlSchemes[selectedControlSchemeIndex]);
259+
260+
261+
var newState = ControlSchemeCommands.DeleteSelectedControlScheme()(in state);
262+
263+
264+
Assert.That(newState.selectedControlSchemeIndex, Is.EqualTo(expectedNewSelectedControlSchemeIndex));
265+
Assert.That(newState.selectedControlScheme.name, expectedNewSelectedControlSchemeName != null
266+
? Is.EqualTo(expectedNewSelectedControlSchemeName)
267+
: Is.EqualTo(null));
268+
}
269+
270+
[Test]
271+
[Category("AssetEditor")]
272+
public void ReorderDeviceRequirementsCommand_ChangesTheOrderOfTheSpecifiedRequirements()
273+
{
274+
var state = TestData.editorState.Generate()
275+
.With(selectedControlScheme: TestData.controlSchemeWithTwoDeviceRequirements.Generate());
276+
277+
278+
var newState = ControlSchemeCommands.ReorderDeviceRequirements(1, 0)(in state);
279+
280+
281+
Assert.That(newState.selectedControlScheme.deviceRequirements[0].controlPath,
282+
Is.EqualTo(state.selectedControlScheme.m_DeviceRequirements[1].controlPath));
283+
Assert.That(newState.selectedControlScheme.deviceRequirements[1].controlPath,
284+
Is.EqualTo(state.selectedControlScheme.m_DeviceRequirements[0].controlPath));
285+
}
286+
287+
[Test]
288+
[Category("AssetEditor")]
289+
public void ChangeBindingsControlSchemesCommand_CanAddControlSchemes()
290+
{
291+
var controlScheme = TestData.controlScheme.Generate();
292+
var state = TestData.EditorStateWithAsset(TestData.inputActionAsset
293+
.Generate()
294+
.WithControlScheme(controlScheme))
295+
.Generate()
296+
.With(selectedControlScheme: controlScheme, selectedActionMapIndex: 0, selectedActionIndex: 0,
297+
selectedBindingIndex: 0);
298+
299+
300+
ControlSchemeCommands.ChangeSelectedBindingsControlSchemes("TestControlScheme", true)(in state);
301+
302+
303+
var actionMapSO = state.serializedObject
304+
?.FindProperty(nameof(InputActionAsset.m_ActionMaps))
305+
?.GetArrayElementAtIndex(state.selectedActionMapIndex);
306+
var serializedProperty = actionMapSO?.FindPropertyRelative(nameof(InputActionMap.m_Bindings))
307+
?.GetArrayElementAtIndex(state.selectedBindingIndex);
308+
309+
var groupsProperty = serializedProperty.FindPropertyRelative(nameof(InputBinding.m_Groups));
310+
311+
Assert.That(groupsProperty.stringValue.Split(InputBinding.kSeparatorString), Contains.Item("TestControlScheme"));
312+
}
313+
314+
[Test]
315+
[Category("AssetEditor")]
316+
public void ChangeBindingsControlSchemesCommand_CanRemoveControlSchemes()
317+
{
318+
var controlScheme = TestData.controlScheme.Generate();
319+
var state = TestData.EditorStateWithAsset(TestData.inputActionAsset
320+
.Select(a =>
321+
{
322+
a.m_ActionMaps[0].m_Bindings[0].groups = "TestControlScheme";
323+
return a;
324+
})
325+
.Generate()
326+
.WithControlScheme(controlScheme))
327+
.Generate()
328+
.With(selectedControlScheme: controlScheme, selectedActionMapIndex: 0, selectedActionIndex: 0,
329+
selectedBindingIndex: 0);
330+
331+
332+
ControlSchemeCommands.ChangeSelectedBindingsControlSchemes("TestControlScheme", false)(in state);
333+
334+
335+
var actionMapSO = state.serializedObject
336+
?.FindProperty(nameof(InputActionAsset.m_ActionMaps))
337+
?.GetArrayElementAtIndex(state.selectedActionMapIndex);
338+
var serializedProperty = actionMapSO?.FindPropertyRelative(nameof(InputActionMap.m_Bindings))
339+
?.GetArrayElementAtIndex(state.selectedBindingIndex);
340+
341+
var groupsProperty = serializedProperty.FindPropertyRelative(nameof(InputBinding.m_Groups));
342+
343+
Assert.That(groupsProperty.stringValue, Is.EqualTo(string.Empty));
344+
}
345+
}
346+
#endif

0 commit comments

Comments
 (0)