diff --git a/Assets/Tests/InputSystem/CoreTests_ProjectWideActions.cs b/Assets/Tests/InputSystem/CoreTests_ProjectWideActions.cs index 5ead638bbc..47a2795dc3 100644 --- a/Assets/Tests/InputSystem/CoreTests_ProjectWideActions.cs +++ b/Assets/Tests/InputSystem/CoreTests_ProjectWideActions.cs @@ -4,10 +4,13 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using NUnit.Framework; using UnityEditor; using UnityEngine; using UnityEngine.InputSystem; +using UnityEngine.InputSystem.Utilities; +using UnityEngine.TestTools; #if UNITY_EDITOR using UnityEngine.InputSystem.Editor; @@ -20,10 +23,11 @@ internal partial class CoreTests string m_TemplateAssetPath; #if UNITY_EDITOR - const int initialActionCount = 2; - const int initialMapCount = 1; + const int initialTotalActionCount = 12; + const int initialMapCount = 2; + const int initialFirstActionMapCount = 2; #else - const int initialActionCount = 19; + const int initialTotalActionCount = 19; const int initialMapCount = 2; #endif @@ -41,6 +45,8 @@ public override void Setup() var testAsset = ScriptableObject.CreateInstance(); AssetDatabase.CreateAsset(testAsset, TestAssetPath); + var defaultUIMapTemplate = ProjectWideActionsAsset.GetDefaultUIActionMap(); + // Create a template `InputActionAsset` containing some test actions. // This will then be used to populate the initially empty `TestActionsAsset` when it is first acessed. var templateActions = ScriptableObject.CreateInstance(); @@ -49,6 +55,9 @@ public override void Setup() map.AddAction("InitialActionOne"); map.AddAction("InitialActionTwo"); + // Add the default UI map to the template + templateActions.AddActionMap(defaultUIMapTemplate); + m_TemplateAssetPath = Path.Combine(Environment.CurrentDirectory, "Assets/ProjectWideActionsTemplate.inputactions"); File.WriteAllText(m_TemplateAssetPath, templateActions.ToJson()); @@ -82,7 +91,7 @@ public void ProjectWideActionsAsset_TemplateAssetIsInstalledOnFirstUse() Assert.That(asset, Is.Not.Null); Assert.That(asset.actionMaps.Count, Is.EqualTo(initialMapCount)); - Assert.That(asset.actionMaps[0].actions.Count, Is.EqualTo(initialActionCount)); + Assert.That(asset.actionMaps[0].actions.Count, Is.EqualTo(initialFirstActionMapCount)); Assert.That(asset.actionMaps[0].actions[0].name, Is.EqualTo("InitialActionOne")); } @@ -94,7 +103,7 @@ public void ProjectWideActionsAsset_CanModifySaveAndLoadAsset() Assert.That(asset, Is.Not.Null); Assert.That(asset.actionMaps.Count, Is.EqualTo(initialMapCount)); - Assert.That(asset.actionMaps[0].actions.Count, Is.EqualTo(initialActionCount)); + Assert.That(asset.actionMaps[0].actions.Count, Is.EqualTo(initialFirstActionMapCount)); Assert.That(asset.actionMaps[0].actions[0].name, Is.EqualTo("InitialActionOne")); asset.Disable(); // Cannot modify active actions @@ -107,7 +116,7 @@ public void ProjectWideActionsAsset_CanModifySaveAndLoadAsset() asset.actionMaps[0].actions[0].Rename("FirstAction"); // Add another map - asset.AddActionMap("ActionMapTwo").AddAction("AnotherAction"); + asset.AddActionMap("ActionMapThree").AddAction("AnotherAction"); // Save AssetDatabase.SaveAssets(); @@ -117,12 +126,44 @@ public void ProjectWideActionsAsset_CanModifySaveAndLoadAsset() Assert.That(asset, Is.Not.Null); Assert.That(asset.actionMaps.Count, Is.EqualTo(initialMapCount + 1)); - Assert.That(asset.actionMaps[0].actions.Count, Is.EqualTo(initialActionCount + 2)); - Assert.That(asset.actionMaps[1].actions.Count, Is.EqualTo(1)); + Assert.That(asset.actionMaps[0].actions.Count, Is.EqualTo(initialFirstActionMapCount + 2)); + Assert.That(asset.actionMaps[1].actions.Count, Is.EqualTo(10)); Assert.That(asset.actionMaps[0].actions[0].name, Is.EqualTo("FirstAction")); - Assert.That(asset.actionMaps[1].actions[0].name, Is.EqualTo("AnotherAction")); + Assert.That(asset.actionMaps[2].actions[0].name, Is.EqualTo("AnotherAction")); } + #if UNITY_2023_2_OR_NEWER + [Test] + [Category(TestCategory)] + public void ProjectWideActions_ShowsErrorWhenUIActionMapHasNameChanges() // This test is only relevant for the InputForUI module + { + var asset = ProjectWideActionsAsset.GetOrCreate(); + var indexOf = asset.m_ActionMaps.IndexOf(x => x.name == "UI"); + var uiMap = asset.m_ActionMaps[indexOf]; + + // Change the name of the UI action map + uiMap.m_Name = "UI2"; + + ProjectWideActionsAsset.CheckForDefaultUIActionMapChanges(); + + LogAssert.Expect(LogType.Warning, new Regex("The action map named 'UI' does not exist")); + + // Change the name of some UI map back to default and change the name of the actions + uiMap.m_Name = "UI"; + var defaultActionName0 = uiMap.m_Actions[0].m_Name; + var defaultActionName1 = uiMap.m_Actions[1].m_Name; + + uiMap.m_Actions[0].Rename("Navigation"); + uiMap.m_Actions[1].Rename("Show"); + + ProjectWideActionsAsset.CheckForDefaultUIActionMapChanges(); + + LogAssert.Expect(LogType.Warning, new Regex($"The UI action '{defaultActionName0}' name has been modified")); + LogAssert.Expect(LogType.Warning, new Regex($"The UI action '{defaultActionName1}' name has been modified")); + } + + #endif + #endif [Test] @@ -141,7 +182,7 @@ public void ProjectWideActions_ContainsTemplateActions() Assert.That(InputSystem.actions.actionMaps.Count, Is.EqualTo(initialMapCount)); #if UNITY_EDITOR - Assert.That(InputSystem.actions.actionMaps[0].actions.Count, Is.EqualTo(initialActionCount)); + Assert.That(InputSystem.actions.actionMaps[0].actions.Count, Is.EqualTo(initialFirstActionMapCount)); Assert.That(InputSystem.actions.actionMaps[0].actions[0].name, Is.EqualTo("InitialActionOne")); #else Assert.That(InputSystem.actions.actionMaps[0].actions.Count, Is.EqualTo(9)); @@ -154,14 +195,14 @@ public void ProjectWideActions_ContainsTemplateActions() public void ProjectWideActions_AppearInEnabledActions() { var enabledActions = InputSystem.ListEnabledActions(); - Assert.That(enabledActions, Has.Count.EqualTo(initialActionCount)); + Assert.That(enabledActions, Has.Count.EqualTo(initialTotalActionCount)); // Add more actions also work var action = new InputAction(name: "standaloneAction"); action.Enable(); enabledActions = InputSystem.ListEnabledActions(); - Assert.That(enabledActions, Has.Count.EqualTo(initialActionCount + 1)); + Assert.That(enabledActions, Has.Count.EqualTo(initialTotalActionCount + 1)); Assert.That(enabledActions, Has.Exactly(1).SameAs(action)); // Disabling works @@ -179,7 +220,7 @@ public void ProjectWideActions_CanReplaceExistingActions() Assert.That(InputSystem.actions, Is.Not.Null); Assert.That(InputSystem.actions.enabled, Is.True); var enabledActions = InputSystem.ListEnabledActions(); - Assert.That(enabledActions, Has.Count.EqualTo(initialActionCount)); + Assert.That(enabledActions, Has.Count.EqualTo(initialTotalActionCount)); // Build new asset var asset = ScriptableObject.CreateInstance(); diff --git a/Packages/com.unity.inputsystem/CHANGELOG.md b/Packages/com.unity.inputsystem/CHANGELOG.md index 7022a95f7f..3f9377e1f3 100644 --- a/Packages/com.unity.inputsystem/CHANGELOG.md +++ b/Packages/com.unity.inputsystem/CHANGELOG.md @@ -10,6 +10,9 @@ however, it has to be formatted properly to pass verification tests. ## [Unreleased] +### Changed +- From 2023.2 forward: UI toolkit now uses the "UI" action map of project-wide actions as their default input actions. Previously, the actions were hardcoded and were based on `DefaultInputActions` asset which didn't allow user changes. Also, removing bindings or renaming the 'UI' action map of project wide actions will break UI input for UI toolkit. + ### Fixed - Fixed missing confirmation popup when deleting a control scheme. - Fixed support for menu bar/customisable keyboard shortcuts used when interacting with Actions and Action Maps. diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/ProjectWideActions/ProjectWideActionsAsset.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/ProjectWideActions/ProjectWideActionsAsset.cs index dd3ba85952..e20bedefd4 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/ProjectWideActions/ProjectWideActionsAsset.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/ProjectWideActions/ProjectWideActionsAsset.cs @@ -93,6 +93,13 @@ internal static InputActionAsset CreateNewActionAsset() return asset; } + internal static InputActionMap GetDefaultUIActionMap() + { + var json = File.ReadAllText(FileUtil.GetPhysicalPath(s_DefaultAssetPath)); + var actionMaps = InputActionMap.FromJson(json); + return actionMaps[actionMaps.IndexOf(x => x.name == "UI")]; + } + private static void CreateInputActionReferences(InputActionAsset asset) { var maps = asset.actionMaps; @@ -107,6 +114,41 @@ private static void CreateInputActionReferences(InputActionAsset asset) } } + #if UNITY_2023_2_OR_NEWER + /// + /// Checks if the default UI action map has been modified or removed, to let the user know if their changes will + /// break the UI input at runtime, when using the UI Toolkit. + /// + internal static void CheckForDefaultUIActionMapChanges() + { + var asset = GetOrCreate(); + if (asset != null) + { + var defaultUIActionMap = GetDefaultUIActionMap(); + var uiMapIndex = asset.actionMaps.IndexOf(x => x.name == "UI"); + + // "UI" action map has been removed or renamed. + if (uiMapIndex == -1) + { + Debug.LogWarning("The action map named 'UI' does not exist.\r\n " + + "This will break the UI input at runtime. Please revert the changes to have an action map named 'UI'."); + return; + } + var uiMap = asset.m_ActionMaps[uiMapIndex]; + foreach (var action in defaultUIActionMap.actions) + { + // "UI" actions have been modified. + if (uiMap.FindAction(action.name) == null) + { + Debug.LogWarning($"The UI action '{action.name}' name has been modified.\r\n" + + $"This will break the UI input at runtime. Please make sure the action name with '{action.name}' exists."); + } + } + } + } + + #endif + /// /// Updates the input action references in the asset by updating names, removing dangling references /// and adding new ones. diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/InputActionsEditorWindowUtils.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/InputActionsEditorWindowUtils.cs index 937e0f1d7e..c22c899afc 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/InputActionsEditorWindowUtils.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/InputActionsEditorWindowUtils.cs @@ -18,6 +18,9 @@ public static void SaveAsset(SerializedObject serializedAsset) // For project-wide actions asset save works differently. The asset is in YAML format, not JSON. if (asset.name == ProjectWideActionsAsset.kAssetName) { +#if UNITY_2023_2_OR_NEWER + ProjectWideActionsAsset.CheckForDefaultUIActionMapChanges(); +#endif ProjectWideActionsAsset.UpdateInputActionReferences(); AssetDatabase.SaveAssets(); return; diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/InputForUI/InputSystemProvider.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/InputForUI/InputSystemProvider.cs index ebc4cabdca..e3e27c7ebf 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/InputForUI/InputSystemProvider.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/InputForUI/InputSystemProvider.cs @@ -78,7 +78,6 @@ public void Initialize() m_TouchState.Reset(); m_SeenTouchEvents = false; - // TODO should UITK somehow override this? m_Cfg = Configuration.GetDefaultConfiguration(); RegisterActions(m_Cfg); } @@ -564,7 +563,7 @@ void UnregisterNextPreviousAction() void RegisterActions(Configuration cfg) { - m_InputActionAsset = InputActionAsset.FromJson(cfg.InputActionAssetAsJson); + m_InputActionAsset = cfg.ActionAsset; m_PointAction = InputActionReference.Create(m_InputActionAsset.FindAction(m_Cfg.PointAction)); m_MoveAction = InputActionReference.Create(m_InputActionAsset.FindAction(m_Cfg.MoveAction)); @@ -641,17 +640,11 @@ void UnregisterActions(Configuration cfg) // The Next/Previous action is not part of the input actions asset UnregisterNextPreviousAction(); - -#if UNITY_EDITOR - UnityEngine.Object.DestroyImmediate(m_InputActionAsset); -#else - UnityEngine.Object.Destroy(m_InputActionAsset); -#endif } public struct Configuration { - public string InputActionAssetAsJson; + public InputActionAsset ActionAsset; public string PointAction; public string MoveAction; public string SubmitAction; @@ -663,14 +656,9 @@ public struct Configuration public static Configuration GetDefaultConfiguration() { - // TODO this is a weird way of doing that, is there an easier way? - var asset = new DefaultInputActions(); - var json = asset.asset.ToJson(); - UnityEngine.Object.DestroyImmediate(asset.asset); // TODO just Dispose doesn't work in edit mode - return new Configuration { - InputActionAssetAsJson = json, + ActionAsset = InputSystem.actions, PointAction = "UI/Point", MoveAction = "UI/Navigate", SubmitAction = "UI/Submit",