Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 54 additions & 13 deletions Assets/Tests/InputSystem/CoreTests_ProjectWideActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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

Expand All @@ -41,6 +45,8 @@ public override void Setup()
var testAsset = ScriptableObject.CreateInstance<TestActionsAsset>();
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<InputActionAsset>();
Expand All @@ -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());

Expand Down Expand Up @@ -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"));
}

Expand All @@ -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
Expand All @@ -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();
Expand All @@ -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]
Expand All @@ -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));
Expand All @@ -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
Expand All @@ -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<InputActionAsset>();
Expand Down
3 changes: 3 additions & 0 deletions Packages/com.unity.inputsystem/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -107,6 +114,41 @@ private static void CreateInputActionReferences(InputActionAsset asset)
}
}

#if UNITY_2023_2_OR_NEWER
/// <summary>
/// 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.
/// </summary>
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

/// <summary>
/// Updates the input action references in the asset by updating names, removing dangling references
/// and adding new ones.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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;
Expand All @@ -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",
Expand Down