Skip to content

Commit 63d1fcd

Browse files
author
Rene Damm
authored
FIX: Binding not getting sync'd when renaming/deleting control schemes (#802).
1 parent 4f50494 commit 63d1fcd

File tree

11 files changed

+201
-72
lines changed

11 files changed

+201
-72
lines changed

Assets/Tests/InputSystem/CoreTests_Actions.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2243,7 +2243,7 @@ public void Actions_CanConvertAssetToAndFromJson()
22432243
map2.AddAction("action3", binding: "<Gamepad>/leftTrigger");
22442244

22452245
asset.AddControlScheme("scheme1").WithBindingGroup("group1").WithRequiredDevice("<Gamepad>");
2246-
asset.AddControlScheme("scheme2").BasedOn("scheme1").WithBindingGroup("group2")
2246+
asset.AddControlScheme("scheme2").WithBindingGroup("group2")
22472247
.WithOptionalDevice("<Keyboard>").WithRequiredDevice("<Mouse>").OrWithRequiredDevice("<Pen>");
22482248

22492249
var json = asset.ToJson();
@@ -2279,8 +2279,6 @@ public void Actions_CanConvertAssetToAndFromJson()
22792279
Assert.That(asset.controlSchemes[1].name, Is.EqualTo("scheme2"));
22802280
Assert.That(asset.controlSchemes[0].bindingGroup, Is.EqualTo("group1"));
22812281
Assert.That(asset.controlSchemes[1].bindingGroup, Is.EqualTo("group2"));
2282-
Assert.That(asset.controlSchemes[0].baseScheme, Is.Null);
2283-
Assert.That(asset.controlSchemes[1].baseScheme, Is.EqualTo("scheme1"));
22842282
Assert.That(asset.controlSchemes[0].deviceRequirements, Has.Count.EqualTo(1));
22852283
Assert.That(asset.controlSchemes[1].deviceRequirements, Has.Count.EqualTo(3));
22862284
Assert.That(asset.controlSchemes[0].deviceRequirements[0].controlPath, Is.EqualTo("<Gamepad>"));

Assets/Tests/InputSystem/CoreTests_Editor.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,40 @@ public void Editor_InputAsset_CanChangeCompositeType()
451451
Assert.That(action1.bindings[5].name, Is.Empty);
452452
}
453453

454+
[Test]
455+
[Category("Editor")]
456+
public void Editor_InputAsset_CanReplaceBindingGroupThroughSerialization()
457+
{
458+
var map = new InputActionMap("map");
459+
var action = map.AddAction(name: "action1");
460+
action.AddBinding("Foo", groups: "A");
461+
action.AddBinding("Bar", groups: "B");
462+
action.AddBinding("Flub", groups: "A;B");
463+
var asset = ScriptableObject.CreateInstance<InputActionAsset>();
464+
asset.AddActionMap(map);
465+
466+
var obj = new SerializedObject(asset);
467+
InputActionSerializationHelpers.ReplaceBindingGroup(obj, "A", "C");
468+
obj.ApplyModifiedPropertiesWithoutUndo();
469+
470+
Assert.That(action.bindings[0].groups, Is.EqualTo("C"));
471+
Assert.That(action.bindings[1].groups, Is.EqualTo("B"));
472+
Assert.That(action.bindings[2].groups, Is.EqualTo("C;B"));
473+
474+
InputActionSerializationHelpers.ReplaceBindingGroup(obj, "C", "");
475+
obj.ApplyModifiedPropertiesWithoutUndo();
476+
477+
Assert.That(action.bindings[0].groups, Is.EqualTo(""));
478+
Assert.That(action.bindings[1].groups, Is.EqualTo("B"));
479+
Assert.That(action.bindings[2].groups, Is.EqualTo("B"));
480+
481+
InputActionSerializationHelpers.ReplaceBindingGroup(obj, "B", "", deleteOrphanedBindings: true);
482+
obj.ApplyModifiedPropertiesWithoutUndo();
483+
484+
Assert.That(map.bindings, Has.Count.EqualTo(1));
485+
Assert.That(map.bindings[0].groups, Is.EqualTo(""));
486+
}
487+
454488
private class MonoBehaviourWithEmbeddedAction : MonoBehaviour
455489
{
456490
public InputAction action;

Packages/com.unity.inputsystem/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ however, it has to be formatted properly to pass verification tests.
1515

1616
#### Actions
1717

18+
- When deleting a control scheme, bindings are now updated. A dialog is presented that allows choosing between deleting the bindings or just unassigning them from the control scheme.
19+
- When renaming a control scheme, bindings are now updated. Previously the old name was in place on bindings.
20+
- Control scheme names can no longer be set to empty strings.
1821
- `PlayerInput.Instantiate` now correctly sets up a given control scheme, if specified.
1922
* When passing a `controlScheme:` argument, the result used to be a correctly assigned control scheme at the `InputUser` level but no restrictions being actually applied to the bindings, i.e. every single binding was active regardless of the specified control scheme.
2023

@@ -27,6 +30,7 @@ however, it has to be formatted properly to pass verification tests.
2730
#### Actions
2831

2932
- When switching devices/controls on actions, the system will no longer subsequently force an initial state check on __all__ actions. Instead, every time an action's bindings get re-resolved, the system will simply cancel all on-going actions and then re-enable them the same way it would happen by manually calling `InputAction.Enable`.
33+
- Removed non-functional `InputControlScheme.baseScheme` API and `basedOn` serialized property. This was never fully implemented.
3034

3135
### Added
3236

Packages/com.unity.inputsystem/InputSystem/Actions/InputActionSetupExtensions.cs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -571,19 +571,6 @@ internal ControlSchemeSyntax(InputControlScheme controlScheme)
571571
m_ControlScheme = controlScheme;
572572
}
573573

574-
public ControlSchemeSyntax BasedOn(string baseControlScheme)
575-
{
576-
if (string.IsNullOrEmpty(baseControlScheme))
577-
throw new ArgumentNullException(nameof(baseControlScheme));
578-
579-
if (m_Asset == null)
580-
m_ControlScheme.m_BaseSchemeName = baseControlScheme;
581-
else
582-
m_Asset.m_ControlSchemes[m_ControlSchemeIndex].m_BaseSchemeName = baseControlScheme;
583-
584-
return this;
585-
}
586-
587574
public ControlSchemeSyntax WithBindingGroup(string bindingGroup)
588575
{
589576
if (string.IsNullOrEmpty(bindingGroup))

Packages/com.unity.inputsystem/InputSystem/Actions/InputControlScheme.cs

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@
88

99
////REVIEW: allow associating control schemes with platforms, too?
1010

11-
////REVIEW: move `baseScheme` entirely into JSON data only such that we resolve it during loading?
12-
//// (and thus support it only input assets only)
13-
1411
namespace UnityEngine.InputSystem
1512
{
1613
/// <summary>
@@ -35,19 +32,6 @@ public struct InputControlScheme : IEquatable<InputControlScheme>
3532
/// <seealso cref="InputActionAsset.AddControlScheme"/>
3633
public string name => m_Name;
3734

38-
////REVIEW: is this actually functional? if not, kill
39-
//problem: how do you do any subtractive operation? should we care?
40-
//problem: this won't allow resolving things on just an InputControlScheme itself; needs context
41-
/// <summary>
42-
/// Name of control scheme that this scheme is based on.
43-
/// </summary>
44-
/// <remarks>
45-
/// When the control scheme is enabled, all bindings from the base control
46-
/// scheme will also be enabled. At the same time, bindings act as overrides on
47-
/// bindings coming through from the base scheme.
48-
/// </remarks>
49-
public string baseScheme => m_BaseSchemeName;
50-
5135
/// <summary>
5236
/// Binding group that is associated with the control scheme.
5337
/// </summary>
@@ -74,14 +58,12 @@ public string bindingGroup
7458
/// </remarks>
7559
public ReadOnlyArray<DeviceRequirement> deviceRequirements => new ReadOnlyArray<DeviceRequirement>(m_DeviceRequirements);
7660

77-
public InputControlScheme(string name, string basedOn = null, IEnumerable<DeviceRequirement> devices = null)
61+
public InputControlScheme(string name, IEnumerable<DeviceRequirement> devices = null)
62+
: this()
7863
{
79-
m_Name = name;
80-
m_BaseSchemeName = string.Empty;
81-
m_BindingGroup = name; // Defaults to name.
82-
m_BaseSchemeName = basedOn;
83-
m_DeviceRequirements = null;
64+
SetNameAndBindingGroup(name);
8465

66+
m_DeviceRequirements = null;
8567
if (devices != null)
8668
{
8769
m_DeviceRequirements = devices.ToArray();
@@ -90,6 +72,14 @@ public InputControlScheme(string name, string basedOn = null, IEnumerable<Device
9072
}
9173
}
9274

75+
internal void SetNameAndBindingGroup(string name)
76+
{
77+
m_Name = name;
78+
bindingGroup = name.Contains(InputBinding.Separator)
79+
? name.Replace(InputBinding.kSeparatorString, "")
80+
: name;
81+
}
82+
9383
public static InputControlScheme? FindControlSchemeForDevice<TList>(InputDevice device, TList schemes)
9484
where TList : IEnumerable<InputControlScheme>
9585
{
@@ -288,7 +278,6 @@ public MatchResult PickDevicesFrom<TDevices>(TDevices devices)
288278
public bool Equals(InputControlScheme other)
289279
{
290280
if (!(string.Equals(m_Name, other.m_Name, StringComparison.InvariantCultureIgnoreCase) &&
291-
string.Equals(m_BaseSchemeName, other.m_BaseSchemeName, StringComparison.InvariantCultureIgnoreCase) &&
292281
string.Equals(m_BindingGroup, other.m_BindingGroup, StringComparison.InvariantCultureIgnoreCase)))
293282
return false;
294283

@@ -332,7 +321,6 @@ public override int GetHashCode()
332321
unchecked
333322
{
334323
var hashCode = (m_Name != null ? m_Name.GetHashCode() : 0);
335-
hashCode = (hashCode * 397) ^ (m_BaseSchemeName != null ? m_BaseSchemeName.GetHashCode() : 0);
336324
hashCode = (hashCode * 397) ^ (m_BindingGroup != null ? m_BindingGroup.GetHashCode() : 0);
337325
hashCode = (hashCode * 397) ^ (m_DeviceRequirements != null ? m_DeviceRequirements.GetHashCode() : 0);
338326
return hashCode;
@@ -376,7 +364,6 @@ public override string ToString()
376364
}
377365

378366
[SerializeField] internal string m_Name;
379-
[SerializeField] internal string m_BaseSchemeName;
380367
[SerializeField] internal string m_BindingGroup;
381368
[SerializeField] internal DeviceRequirement[] m_DeviceRequirements;
382369

@@ -770,8 +757,6 @@ public override int GetHashCode()
770757
internal struct SchemeJson
771758
{
772759
public string name;
773-
////TODO: nuke 'basedOn'
774-
public string basedOn;
775760
public string bindingGroup;
776761
public DeviceJson[] devices;
777762

@@ -817,7 +802,6 @@ public InputControlScheme ToScheme()
817802
return new InputControlScheme
818803
{
819804
m_Name = string.IsNullOrEmpty(name) ? null : name,
820-
m_BaseSchemeName = string.IsNullOrEmpty(basedOn) ? null : basedOn,
821805
m_BindingGroup = string.IsNullOrEmpty(bindingGroup) ? null : bindingGroup,
822806
m_DeviceRequirements = deviceRequirements,
823807
};
@@ -837,7 +821,6 @@ public static SchemeJson ToJson(InputControlScheme scheme)
837821
return new SchemeJson
838822
{
839823
name = scheme.m_Name,
840-
basedOn = scheme.m_BaseSchemeName,
841824
bindingGroup = scheme.m_BindingGroup,
842825
devices = devices,
843826
};

Packages/com.unity.inputsystem/InputSystem/Editor/AssetEditor/InputActionEditorToolbar.cs

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,10 @@ private void OnDeleteControlScheme()
204204
{
205205
Debug.Assert(m_SelectedControlSchemeIndex >= 0, "Control scheme must be selected");
206206

207-
// Ask for confirmation.
208207
var name = m_ControlSchemes[m_SelectedControlSchemeIndex].name;
208+
var bindingGroup = m_ControlSchemes[m_SelectedControlSchemeIndex].bindingGroup;
209+
210+
// Ask for confirmation.
209211
if (!EditorUtility.DisplayDialog("Delete scheme?", $"Do you want to delete control scheme '{name}'?",
210212
"Delete", "Cancel"))
211213
return;
@@ -222,8 +224,10 @@ private void OnDeleteControlScheme()
222224

223225
onControlSchemesChanged?.Invoke();
224226
onSelectedSchemeChanged?.Invoke();
227+
onControlSchemeDeleted?.Invoke(name, bindingGroup);
225228
}
226229

230+
////REVIEW: this does nothing to bindings; should this ask to duplicate bindings as well?
227231
private void OnDuplicateControlScheme(object position)
228232
{
229233
Debug.Assert(m_SelectedControlSchemeIndex >= 0, "Control scheme must be selected");
@@ -236,7 +240,6 @@ private void OnDuplicateControlScheme(object position)
236240
(s, _) => AddAndSelectControlScheme(s));
237241
}
238242

239-
////REVIEW: renaming a control scheme should probably update the binding group (also on all bindings)
240243
private void OnEditSelectedControlScheme(object position)
241244
{
242245
Debug.Assert(m_SelectedControlSchemeIndex >= 0, "Control scheme must be selected");
@@ -266,19 +269,30 @@ private void UpdateControlScheme(InputControlScheme scheme, int index)
266269
{
267270
Debug.Assert(index >= 0 && index < m_ControlSchemes.LengthSafe(), "Control scheme index out of range");
268271

272+
var renamed = false;
273+
string oldBindingGroup = null;
274+
string newBindingGroup = null;
275+
269276
// If given scheme has no name, preserve the existing one on the control scheme.
270277
if (string.IsNullOrEmpty(scheme.name))
271278
scheme.m_Name = m_ControlSchemes[index].name;
272279

273280
// If name is changing, make sure it's unique.
274281
else if (scheme.name != m_ControlSchemes[index].name)
275282
{
283+
renamed = true;
284+
oldBindingGroup = m_ControlSchemes[index].bindingGroup;
276285
m_ControlSchemes[index].m_Name = ""; // Don't want this to interfere with finding a unique name.
277-
m_ControlSchemes[index].m_Name = MakeUniqueControlSchemeName(scheme.name);
286+
var newName = MakeUniqueControlSchemeName(scheme.name);
287+
m_ControlSchemes[index].SetNameAndBindingGroup(newName);
288+
newBindingGroup = m_ControlSchemes[index].bindingGroup;
278289
}
279290

280291
m_ControlSchemes[index] = scheme;
281292
onControlSchemesChanged?.Invoke();
293+
294+
if (renamed)
295+
onControlSchemeRenamed?.Invoke(oldBindingGroup, newBindingGroup);
282296
}
283297

284298
private void SelectControlScheme(int index)
@@ -319,6 +333,8 @@ private static string DeviceRequirementToDisplayString(InputControlScheme.Device
319333
public Action onSelectedSchemeChanged;
320334
public Action onSelectedDeviceChanged;
321335
public Action onControlSchemesChanged;
336+
public Action<string, string> onControlSchemeRenamed;
337+
public Action<string, string> onControlSchemeDeleted;
322338
public Action onSave;
323339

324340
[SerializeField] private bool m_IsDirty;
@@ -478,11 +494,20 @@ private void DrawConfirmationButton()
478494
}
479495
if (GUILayout.Button("Save", GUILayout.ExpandWidth(true)))
480496
{
481-
m_ControlScheme = new InputControlScheme(m_ControlScheme.name,
482-
devices: m_DeviceList.Select(a => a.deviceRequirement));
497+
// Don't allow control scheme name to be empty.
498+
if (string.IsNullOrEmpty(m_ControlScheme.name))
499+
{
500+
////FIXME: On 2019.1 this doesn't display properly in the window; check 2019.3
501+
editorWindow.ShowNotification(new GUIContent("Control scheme must have a name"));
502+
}
503+
else
504+
{
505+
m_ControlScheme = new InputControlScheme(m_ControlScheme.name,
506+
devices: m_DeviceList.Select(a => a.deviceRequirement));
483507

484-
editorWindow.Close();
485-
m_OnApply(m_ControlScheme, m_ControlSchemeIndex);
508+
editorWindow.Close();
509+
m_OnApply(m_ControlScheme, m_ControlSchemeIndex);
510+
}
486511
}
487512
if (Event.current.type == EventType.Repaint)
488513
m_ButtonsAndLabelsHeights += GUILayoutUtility.GetLastRect().height;

Packages/com.unity.inputsystem/InputSystem/Editor/AssetEditor/InputActionEditorWindow.cs

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@ private void OnEnable()
203203
m_Toolbar.onSelectedDeviceChanged = OnControlSchemeSelectionChanged;
204204
m_Toolbar.onSave = SaveChangesToAsset;
205205
m_Toolbar.onControlSchemesChanged = OnControlSchemesModified;
206+
m_Toolbar.onControlSchemeRenamed = OnControlSchemeRenamed;
207+
m_Toolbar.onControlSchemeDeleted = OnControlSchemeDeleted;
206208
EditorApplication.wantsToQuit += EditorWantsToQuit;
207209

208210
// Initialize after assembly reload.
@@ -286,6 +288,43 @@ private void OnControlSchemeSelectionChanged()
286288
private void OnControlSchemesModified()
287289
{
288290
TransferControlSchemes(save: true);
291+
292+
// Control scheme changes may affect the search filter.
293+
OnToolbarSearchChanged();
294+
295+
ApplyAndReloadTrees();
296+
}
297+
298+
private void OnControlSchemeRenamed(string oldBindingGroup, string newBindingGroup)
299+
{
300+
InputActionSerializationHelpers.ReplaceBindingGroup(m_ActionAssetManager.serializedObject,
301+
oldBindingGroup, newBindingGroup);
302+
ApplyAndReloadTrees();
303+
}
304+
305+
private void OnControlSchemeDeleted(string name, string bindingGroup)
306+
{
307+
Debug.Assert(!string.IsNullOrEmpty(name), "Control scheme name should not be empty");
308+
Debug.Assert(!string.IsNullOrEmpty(bindingGroup), "Binding group should not be empty");
309+
310+
var asset = m_ActionAssetManager.m_AssetObjectForEditing;
311+
312+
var bindingMask = InputBinding.MaskByGroup(bindingGroup);
313+
var schemeHasBindings = asset.actionMaps.Any(m => m.bindings.Any(b => bindingMask.Matches(ref b)));
314+
if (!schemeHasBindings)
315+
return;
316+
317+
////REVIEW: offer to do nothing and leave all bindings as is?
318+
var deleteBindings =
319+
EditorUtility.DisplayDialog("Delete Bindings?",
320+
$"Delete bindings for '{name}' as well? If you select 'No', the bindings will only "
321+
+ $"be unassigned from the '{name}' control scheme but otherwise left as is. Note that bindings "
322+
+ $"that are assigned to '{name}' but also to other control schemes will be left in place either way.",
323+
"Yes", "No");
324+
325+
InputActionSerializationHelpers.ReplaceBindingGroup(m_ActionAssetManager.serializedObject, bindingGroup, "",
326+
deleteOrphanedBindings: deleteBindings);
327+
289328
ApplyAndReloadTrees();
290329
}
291330

@@ -441,7 +480,6 @@ private void LoadPropertiesForSelection()
441480
// Grab the action for the binding and see if we have an expected control layout
442481
// set on it. Pass that on to the control picking machinery.
443482
var isCompositePartBinding = item is PartOfCompositeBindingTreeItem;
444-
var isCompositeBinding = item is CompositeBindingTreeItem;
445483
var actionItem = (isCompositePartBinding ? item.parent.parent : item.parent) as ActionTreeItem;
446484
Debug.Assert(actionItem != null);
447485

@@ -451,7 +489,7 @@ private void LoadPropertiesForSelection()
451489
// The toolbar may constrain the set of devices we're currently interested in by either
452490
// having one specific device selected from the current scheme or having at least a control
453491
// scheme selected.
454-
var controlPathsToMatch = (IEnumerable<string>)null;
492+
IEnumerable<string> controlPathsToMatch;
455493
if (m_Toolbar.selectedDeviceRequirement != null)
456494
{
457495
// Single device selected from set of devices in control scheme.
@@ -478,7 +516,8 @@ private void LoadPropertiesForSelection()
478516
{
479517
if (change == InputBindingPropertiesView.k_PathChanged ||
480518
change == InputBindingPropertiesView.k_CompositePartAssignmentChanged ||
481-
change == InputBindingPropertiesView.k_CompositeTypeChanged)
519+
change == InputBindingPropertiesView.k_CompositeTypeChanged ||
520+
change == InputBindingPropertiesView.k_GroupsChanged)
482521
{
483522
ApplyAndReloadTrees();
484523
}

0 commit comments

Comments
 (0)