263 lines
12 KiB
C#
263 lines
12 KiB
C#
#if UNITY_EDITOR
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEditor;
|
|
|
|
namespace UnityEngine.InputSystem.Editor
|
|
{
|
|
// We want an empty editor in the inspector. Editing happens in a dedicated window.
|
|
[CustomEditor(typeof(InputActionAsset))]
|
|
internal class InputActionAssetEditor : UnityEditor.Editor
|
|
{
|
|
protected override void OnHeaderGUI()
|
|
{
|
|
}
|
|
|
|
public override void OnInspectorGUI()
|
|
{
|
|
}
|
|
|
|
#region Support abstract editor registration
|
|
|
|
private static readonly List<Type> s_EditorTypes = new List<Type>();
|
|
|
|
// Registers an asset editor type for receiving asset modification callbacks.
|
|
public static void RegisterType<T>() where T : IInputActionAssetEditor
|
|
{
|
|
if (!s_EditorTypes.Contains(typeof(T)))
|
|
s_EditorTypes.Add(typeof(T));
|
|
}
|
|
|
|
// Unregisters an asset editor type from receiving asset modification callbacks.
|
|
public static void UnregisterType<T>() where T : IInputActionAssetEditor
|
|
{
|
|
s_EditorTypes.Remove(typeof(T));
|
|
}
|
|
|
|
public static T FindOpenEditor<T>(string path) where T : EditorWindow
|
|
{
|
|
var openEditors = FindAllEditorsForPath(path);
|
|
foreach (var openEditor in openEditors)
|
|
{
|
|
if (openEditor.GetType() == typeof(T))
|
|
return (T)openEditor;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Finds all asset editors associated with the asset given by path.
|
|
public static IInputActionAssetEditor[] FindAllEditorsForPath(string path)
|
|
{
|
|
var guid = AssetDatabase.AssetPathToGUID(path);
|
|
return guid != null ? FindAllEditors((editor) => editor.assetGUID == guid) :
|
|
Array.Empty<IInputActionAssetEditor>();
|
|
}
|
|
|
|
// Finds all asset editors fulfilling the given predicate.
|
|
public static IInputActionAssetEditor[] FindAllEditors(Predicate<IInputActionAssetEditor> predicate = null)
|
|
{
|
|
List<IInputActionAssetEditor> editors = null;
|
|
foreach (var type in s_EditorTypes)
|
|
editors = FindAllEditors(type, predicate, editors);
|
|
return editors != null ? editors.ToArray() : Array.Empty<IInputActionAssetEditor>();
|
|
}
|
|
|
|
private static List<IInputActionAssetEditor> FindAllEditors(Type type,
|
|
Predicate<IInputActionAssetEditor> predicate = null,
|
|
List<IInputActionAssetEditor> result = null)
|
|
{
|
|
if (result == null)
|
|
result = new List<IInputActionAssetEditor>();
|
|
var editors = Resources.FindObjectsOfTypeAll(type);
|
|
foreach (var editor in editors)
|
|
{
|
|
if (editor is IInputActionAssetEditor actionsAssetEditor && (predicate == null || predicate(actionsAssetEditor)))
|
|
result.Add(actionsAssetEditor);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Asset modification processor to intercept Unity editor move or delete operations
|
|
// Asset modification processor designed to handle the following scenarios:
|
|
// - When an asset is about to get deleted, evaluate if there is a pending unsaved edited copy of the asset
|
|
// open in any associated editor and in this case, prompt the user that there are unsaved changes and allow
|
|
// the user to cancel the operation and allow to save the pending changes or confirm to delete the asset and
|
|
// discard the pending unsaved changes (via OnAssetDeleted() notification).
|
|
// - If the asset being deleted is not open in any editors or any open copies are not modified, no dialog
|
|
// prompt is displayed and the asset is deleted.
|
|
// - When an asset is about to get moved, notify any editors having the asset open about the move.
|
|
//
|
|
// See comments further down in this class for expected callback sequences.
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Intantiated through reflection by Unity")]
|
|
private class InputActionAssetModificationProcessor : UnityEditor.AssetModificationProcessor
|
|
{
|
|
public static AssetDeleteResult OnWillDeleteAsset(string path, RemoveAssetOptions options)
|
|
{
|
|
if (InputActionImporter.IsInputActionAssetPath(path))
|
|
{
|
|
// Find any open editors associated to the asset and if any of them holds unsaved changes
|
|
// allow the user to discard unsaved changes or cancel deletion.
|
|
var editorWithAssetOpen = InputActionAssetEditor.FindAllEditorsForPath(path);
|
|
foreach (var editor in editorWithAssetOpen)
|
|
{
|
|
if (editor.isDirty)
|
|
{
|
|
var result = Dialog.InputActionAsset.ShowDiscardUnsavedChanges(path);
|
|
if (result == Dialog.Result.Cancel)
|
|
return AssetDeleteResult.FailedDelete;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Notify all associated editors that asset will be deleted
|
|
foreach (var editor in editorWithAssetOpen)
|
|
editor.OnAssetDeleted();
|
|
}
|
|
|
|
return default;
|
|
}
|
|
|
|
public static AssetMoveResult OnWillMoveAsset(string sourcePath, string destinationPath)
|
|
{
|
|
if (InputActionImporter.IsInputActionAssetPath(sourcePath))
|
|
{
|
|
var editorWithAssetOpen = InputActionAssetEditor.FindAllEditorsForPath(sourcePath);
|
|
foreach (var editor in editorWithAssetOpen)
|
|
editor.OnAssetMoved();
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Asset post processor to react to internal or external asset import, move, delete events.
|
|
// Processor detecting any Unity editor internal or external (file system) changes to an asset and notifies
|
|
// any associated asset editors about those changes via callbacks.
|
|
//
|
|
// Note that any editor classes interested in receiving notifications need to be registered.
|
|
//
|
|
// For clarity, the tables below indicate the callback sequences of the asset modification processor and
|
|
// asset post-processor for various user operations done on assets.
|
|
//
|
|
// s = source file
|
|
// d = destination file
|
|
// * = operation may be aborted by user
|
|
//
|
|
// User operation: Callback sequence:
|
|
// ----------------------------------------------------------------------------------------
|
|
// Write (Save) Imported(s)
|
|
// Delete OnWillDelete(s), Deleted(s)*
|
|
// Copy Imported(s)
|
|
// Rename OnWillMove(s,d), Imported(d), Moved(s,d)
|
|
// Move (drag) / Cut+Paste OnWillMove(s,d), Moved(s,d)
|
|
// ------------------------------------------------------------------------------------------------------------
|
|
//
|
|
// External user operation: Callback/call sequence:
|
|
// ------------------------------------------------------------------------------------------------------------
|
|
// Save Imported(s)
|
|
// Delete Deleted(s)
|
|
// Copy Imported(s)
|
|
// Rename Imported(d), Deleted(s)
|
|
// Move(drag) / Cut+Paste Imported(d), Deleted(s)
|
|
// ------------------------------------------------------------------------------------------------------------
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Intantiated through reflection by Unity")]
|
|
private class InputActionAssetPostprocessor : AssetPostprocessor
|
|
{
|
|
private static bool s_DoNotifyEditorsScheduled;
|
|
private static List<string> s_Imported = new List<string>();
|
|
private static List<string> s_Deleted = new List<string>();
|
|
private static List<string> s_Moved = new List<string>();
|
|
|
|
private static void Notify(IReadOnlyCollection<string> assets,
|
|
IReadOnlyCollection<IInputActionAssetEditor> editors, Action<IInputActionAssetEditor> callback)
|
|
{
|
|
foreach (var asset in assets)
|
|
{
|
|
var assetGuid = AssetDatabase.AssetPathToGUID(asset);
|
|
foreach (var editor in editors)
|
|
{
|
|
if (editor.assetGUID != assetGuid)
|
|
continue;
|
|
|
|
try
|
|
{
|
|
callback(editor);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogException(e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void NotifyEditors()
|
|
{
|
|
try
|
|
{
|
|
// When the asset is modified outside of the editor and the importer settings are
|
|
// visible in the Inspector the asset references in the importer inspector need to be
|
|
// force rebuild (otherwise we gets lots of exceptions).
|
|
ActiveEditorTracker.sharedTracker.ForceRebuild();
|
|
|
|
// Unconditionally find all existing editors regardless of associated asset
|
|
var editors = InputActionAssetEditor.FindAllEditors();
|
|
|
|
// Abort if there are no available candidate editors
|
|
if (editors == null || editors.Length == 0)
|
|
return;
|
|
|
|
// Notify editors about asset changes
|
|
Notify(s_Imported, editors, (editor) => editor.OnAssetImported());
|
|
Notify(s_Deleted, editors, (editor) => editor.OnAssetDeleted());
|
|
Notify(s_Moved, editors, (editor) => editor.OnAssetMoved());
|
|
}
|
|
finally
|
|
{
|
|
s_Imported.Clear();
|
|
s_Deleted.Clear();
|
|
s_Moved.Clear();
|
|
|
|
s_DoNotifyEditorsScheduled = false;
|
|
}
|
|
}
|
|
|
|
private static void Process(string[] assets, ICollection<string> target)
|
|
{
|
|
foreach (var asset in assets)
|
|
{
|
|
// Ignore any assets with non matching extensions
|
|
if (!InputActionImporter.IsInputActionAssetPath(asset))
|
|
continue;
|
|
|
|
// Register asset in target collection for delay invocation
|
|
target.Add(asset);
|
|
|
|
// If a notification execution has already been scheduled do nothing apart from registration.
|
|
// We do this with delayed execution to avoid excessive updates interfering with ADB.
|
|
if (!s_DoNotifyEditorsScheduled)
|
|
{
|
|
EditorApplication.delayCall += NotifyEditors;
|
|
s_DoNotifyEditorsScheduled = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets,
|
|
string[] movedAssets, string[] movedFromAssetPaths, bool didDomainReload)
|
|
{
|
|
Process(importedAssets, s_Imported);
|
|
Process(deletedAssets, s_Deleted);
|
|
Process(movedAssets, s_Moved);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|
|
#endif // UNITY_EDITOR
|