#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
namespace UnityEngine.InputSystem.Editor
{
interface IPasteListener
{
void OnPaste(InputActionsEditorState state);
}
internal class InputActionsEditorView : ViewBase<InputActionsEditorView.ViewState>, IPasteListener
{
private const string saveButtonId = "save-asset-toolbar-button";
private const string autoSaveToggleId = "auto-save-toolbar-toggle";
private const string menuButtonId = "asset-menu";
private readonly ToolbarMenu m_ControlSchemesToolbar;
private readonly ToolbarMenu m_DevicesToolbar;
private readonly ToolbarButton m_SaveButton;
private readonly Action m_SaveAction;
private ControlSchemesView m_ControlSchemesView;
public InputActionsEditorView(VisualElement root, StateContainer stateContainer, bool isProjectSettings,
Action saveAction)
: base(root, stateContainer)
{
m_SaveAction = saveAction;
var mainEditorAsset = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(
InputActionsEditorConstants.PackagePath +
InputActionsEditorConstants.ResourcesPath +
InputActionsEditorConstants.MainEditorViewNameUxml);
mainEditorAsset.CloneTree(root);
var actionsTreeView = new ActionsTreeView(root, stateContainer);
CreateChildView(new ActionMapsView(root, stateContainer));
CreateChildView(actionsTreeView);
CreateChildView(new PropertiesView(root, stateContainer));
m_ControlSchemesToolbar = root.Q<ToolbarMenu>("control-schemes-toolbar-menu");
m_ControlSchemesToolbar.menu.AppendAction("Add Control Scheme...", _ => AddOrUpdateControlScheme(root));
m_ControlSchemesToolbar.menu.AppendAction("Edit Control Scheme...", _ => AddOrUpdateControlScheme(root, true), DropdownMenuAction.Status.Disabled);
m_ControlSchemesToolbar.menu.AppendAction("Duplicate Control Scheme...", _ => DuplicateControlScheme(root), DropdownMenuAction.Status.Disabled);
m_ControlSchemesToolbar.menu.AppendAction("Delete Control Scheme...", DeleteControlScheme, DropdownMenuAction.Status.Disabled);
m_DevicesToolbar = root.Q<ToolbarMenu>("control-schemes-filter-toolbar-menu");
m_DevicesToolbar.SetEnabled(false);
m_SaveButton = root.Q<ToolbarButton>(name: saveButtonId);
m_SaveButton.SetEnabled(InputEditorUserSettings.autoSaveInputActionAssets == false);
m_SaveButton.clicked += OnSaveButton;
var autoSaveToggle = root.Q<ToolbarToggle>(name: autoSaveToggleId);
autoSaveToggle.value = InputEditorUserSettings.autoSaveInputActionAssets;
autoSaveToggle.RegisterValueChangedCallback(OnAutoSaveToggle);
// Hide save toolbar if there is no save action provided since we cannot support it
if (saveAction == null)
{
var element = root.Q("save-asset-toolbar-container");
if (element != null)
{
element.style.visibility = Visibility.Hidden;
element.style.display = DisplayStyle.None;
}
}
VisualElement assetMenuButton = null;
try
{
// This only exists in the project settings version
assetMenuButton = root.Q<VisualElement>(name: menuButtonId);
}
catch {}
if (assetMenuButton != null)
{
assetMenuButton.visible = isProjectSettings;
assetMenuButton.AddToClassList(EditorGUIUtility.isProSkin ? "asset-menu-button-dark-theme" : "asset-menu-button");
var _ = new ContextualMenuManipulator(menuEvent =>
{
menuEvent.menu.AppendAction("Reset to Defaults", _ => OnReset());
menuEvent.menu.AppendAction("Remove All Action Maps", _ => OnClearActionMaps());
})
{ target = assetMenuButton, activators = { new ManipulatorActivationFilter() { button = MouseButton.LeftMouse } } };
}
// only register the state changed event here in the parent. Changes will be cascaded
// into child views.
stateContainer.StateChanged += OnStateChanged;
CreateSelector(
s => s.selectedControlSchemeIndex,
s => new ViewStateCollection<InputControlScheme>(Selectors.GetControlSchemes(s)),
(_, controlSchemes, state) => new ViewState
{
controlSchemes = controlSchemes,
selectedControlSchemeIndex = state.selectedControlSchemeIndex,
selectedDeviceIndex = state.selectedDeviceRequirementIndex
});
s_OnPasteCutElements.Add(this);
Undo.undoRedoPerformed += CloseControlSchemeView;
}
private void CloseControlSchemeView()
{
m_ControlSchemesView?.Cancel();
}
public bool IsControlSchemeViewActive()
{
return m_ControlSchemesView != null;
}
private void OnReset()
{
Dispatch(Commands.ReplaceActionMaps(ProjectWideActionsAsset.GetDefaultAssetJson()));
}
private void OnClearActionMaps()
{
Dispatch(Commands.ClearActionMaps());
}
private void OnSaveButton()
{
Dispatch(Commands.SaveAsset(m_SaveAction));
// Don't let focus linger after clicking (ISX-1482). Ideally this would be only applied on mouse click,
// rather than if the user is using tab to navigate UI, but there doesn't seem to be a way to differentiate
// between those interactions at the moment.
m_SaveButton.Blur();
}
private void OnAutoSaveToggle(ChangeEvent<bool> evt)
{
Dispatch(Commands.ToggleAutoSave(evt.newValue, m_SaveAction));
}
public override void RedrawUI(ViewState viewState)
{
SetUpControlSchemesMenu(viewState);
SetUpDevicesMenu(viewState);
m_SaveButton.SetEnabled(InputEditorUserSettings.autoSaveInputActionAssets == false);
}
private string SetupControlSchemeName(string name)
{
//On Windows the '&' is considered an accelerator character and will always be stripped.
//Since the ControlScheme menu isn't creating hotkeys, it can be safely assumed that they are meant to be text
//so we want to escape the character for MenuItem
if (Application.platform == RuntimePlatform.WindowsEditor)
{
name = name.Replace("&", "&&");
}
return name;
}
private void SetUpControlSchemesMenu(ViewState viewState)
{
m_ControlSchemesToolbar.menu.MenuItems().Clear();
if (viewState.controlSchemes.Any())
{
var elementAtOrDefault = viewState.controlSchemes.ElementAtOrDefault(viewState.selectedControlSchemeIndex);
m_ControlSchemesToolbar.text = elementAtOrDefault == default ? "All Control Schemes" : elementAtOrDefault.name;
m_ControlSchemesToolbar.menu.AppendAction("All Control Schemes", _ => SelectControlScheme(-1),
viewState.selectedControlSchemeIndex == -1 ? DropdownMenuAction.Status.Checked : DropdownMenuAction.Status.Normal);
viewState.controlSchemes.ForEach((scheme, i) =>
m_ControlSchemesToolbar.menu.AppendAction(SetupControlSchemeName(scheme.name), _ => SelectControlScheme(i),
viewState.selectedControlSchemeIndex == i ? DropdownMenuAction.Status.Checked : DropdownMenuAction.Status.Normal));
m_ControlSchemesToolbar.menu.AppendSeparator();
}
m_ControlSchemesToolbar.menu.AppendAction("Add Control Scheme...", _ => AddOrUpdateControlScheme(rootElement));
m_ControlSchemesToolbar.menu.AppendAction("Edit Control Scheme...", _ => AddOrUpdateControlScheme(rootElement, true),
viewState.selectedControlSchemeIndex != -1 ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Disabled);
m_ControlSchemesToolbar.menu.AppendAction("Duplicate Control Scheme...", _ => DuplicateControlScheme(rootElement),
viewState.selectedControlSchemeIndex != -1 ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Disabled);
m_ControlSchemesToolbar.menu.AppendAction("Delete Control Scheme...", DeleteControlScheme,
viewState.selectedControlSchemeIndex != -1 ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Disabled);
}
private void SetUpDevicesMenu(ViewState viewState)
{
if (!viewState.controlSchemes.Any() || viewState.selectedControlSchemeIndex == -1)
{
m_DevicesToolbar.text = "All Devices";
m_DevicesToolbar.SetEnabled(false);
return;
}
m_DevicesToolbar.SetEnabled(true);
var currentControlScheme = viewState.controlSchemes.ElementAtOrDefault(viewState.selectedControlSchemeIndex);
if (viewState.selectedDeviceIndex == -1)
m_DevicesToolbar.text = "All Devices";
m_DevicesToolbar.menu.MenuItems().Clear();
m_DevicesToolbar.menu.AppendAction("All Devices", _ => SelectDevice(-1), viewState.selectedDeviceIndex == -1
? DropdownMenuAction.Status.Checked
: DropdownMenuAction.Status.Normal);
currentControlScheme.deviceRequirements.ForEach(
(device, i) =>
{
InputControlPath.ToHumanReadableString(device.controlPath, out var name, out _);
m_DevicesToolbar.menu.AppendAction(name, _ => SelectDevice(i),
viewState.selectedDeviceIndex == i
? DropdownMenuAction.Status.Checked
: DropdownMenuAction.Status.Normal);
if (viewState.selectedDeviceIndex == i)
m_DevicesToolbar.text = name;
});
}
private void AddOrUpdateControlScheme(VisualElement parent, bool updateExisting = false)
{
if (!updateExisting)
Dispatch(ControlSchemeCommands.AddNewControlScheme());
ShowControlSchemeEditor(parent, updateExisting);
}
private void DuplicateControlScheme(VisualElement parent)
{
Dispatch(ControlSchemeCommands.DuplicateSelectedControlScheme());
ShowControlSchemeEditor(parent);
}
private void DeleteControlScheme(DropdownMenuAction obj)
{
Dispatch(ControlSchemeCommands.DeleteSelectedControlScheme());
}
private void ShowControlSchemeEditor(VisualElement parent, bool updateExisting = false)
{
m_ControlSchemesView = CreateChildView(new ControlSchemesView(parent, stateContainer, updateExisting));
m_ControlSchemesView.UpdateView(stateContainer.GetState());
m_ControlSchemesView.OnClosing += _ =>
{
DestroyChildView(m_ControlSchemesView);
m_ControlSchemesView = null;
};
}
private void SelectControlScheme(int controlSchemeIndex)
{
Dispatch(ControlSchemeCommands.SelectControlScheme(controlSchemeIndex));
SelectDevice(-1);
}
private void SelectDevice(int deviceIndex)
{
Dispatch(ControlSchemeCommands.SelectDeviceRequirement(deviceIndex));
}
public class ViewState
{
public IEnumerable<InputControlScheme> controlSchemes;
public int selectedControlSchemeIndex;
public int selectedDeviceIndex;
}
internal static List<IPasteListener> s_OnPasteCutElements = new();
public override void DestroyView()
{
base.DestroyView();
s_OnPasteCutElements.Remove(this);
Undo.undoRedoPerformed -= CloseControlSchemeView;
}
public void OnPaste(InputActionsEditorState state)
{
if (state.Equals(stateContainer.GetState()))
return;
Dispatch(Commands.DeleteCutElements());
}
}
internal static partial class Selectors
{
public static IEnumerable<InputControlScheme> GetControlSchemes(InputActionsEditorState state)
{
var controlSchemesArray = state.serializedObject.FindProperty(nameof(InputActionAsset.m_ControlSchemes));
if (controlSchemesArray == null)
yield break;
foreach (SerializedProperty controlScheme in controlSchemesArray)
{
yield return new InputControlScheme(controlScheme);
}
}
}
}
#endif