Files
MMORPG/Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/Views/ContextMenu.cs
cooney ce83f21c93 UI 자동화를 위해 바인딩 기능 구현
- 유니티 에셋 인증 오류로 meta 재생성
2026-01-25 01:31:34 +09:00

239 lines
12 KiB
C#

#if UNITY_EDITOR
using System;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.Utilities;
using UnityEngine.UIElements;
namespace UnityEngine.InputSystem.Editor
{
internal static class ContextMenu
{
private static readonly string copy_String = "Copy";
private static readonly string cut_String = "Cut";
private static readonly string paste_String = "Paste";
private static readonly string rename_String = "Rename";
private static readonly string duplicate_String = "Duplicate";
private static readonly string delete_String = "Delete";
private static readonly string add_Action_Map_String = "Add Action Map";
private static readonly string add_Action_String = "Add Action";
private static readonly string add_Binding_String = "Add Binding";
#region ActionMaps
// Determine whether current clipboard contents can can pasted into the ActionMaps view
//
// can always paste an ActionMap
// need an existing map to be able to paste an Action
//
private static bool CanPasteIntoActionMaps(ActionMapsView mapView)
{
bool haveMap = mapView.GetMapCount() > 0;
var copiedType = CopyPasteHelper.GetCopiedClipboardType();
bool copyIsMap = copiedType == typeof(InputActionMap);
bool copyIsAction = copiedType == typeof(InputAction);
bool hasPastableData = (copyIsMap || (copyIsAction && haveMap));
return hasPastableData;
}
public static void GetContextMenuForActionMapItem(ActionMapsView mapView, InputActionMapsTreeViewItem treeViewItem, int index)
{
treeViewItem.OnContextualMenuPopulateEvent = (menuEvent =>
{
// TODO: AddAction should enable m_RenameOnActionAdded
menuEvent.menu.AppendAction(add_Action_String, _ => mapView.Dispatch(Commands.AddAction()));
menuEvent.menu.AppendSeparator();
menuEvent.menu.AppendAction(rename_String, _ => mapView.RenameActionMap(index));
menuEvent.menu.AppendAction(duplicate_String, _ => mapView.DuplicateActionMap(index));
menuEvent.menu.AppendAction(delete_String, _ => mapView.DeleteActionMap(index));
menuEvent.menu.AppendSeparator();
menuEvent.menu.AppendAction(copy_String, _ => mapView.CopyItems());
menuEvent.menu.AppendAction(cut_String, _ => mapView.CutItems());
if (CanPasteIntoActionMaps(mapView))
{
bool copyIsAction = CopyPasteHelper.GetCopiedClipboardType() == typeof(InputAction);
if (CopyPasteHelper.HasPastableClipboardData(typeof(InputActionMap)))
menuEvent.menu.AppendAction(paste_String, _ => mapView.PasteItems(copyIsAction));
}
menuEvent.menu.AppendSeparator();
menuEvent.menu.AppendAction(add_Action_Map_String, _ => mapView.AddActionMap());
});
}
// Add "Add Action Map" option to empty space under the ListView. Matches with old IMGUI style (ISX-1519).
// Include Paste here as well, since it makes sense for adding ActionMaps.
public static void GetContextMenuForActionMapsEmptySpace(ActionMapsView mapView, VisualElement element)
{
_ = new ContextualMenuManipulator(menuEvent =>
{
if (CanPasteIntoActionMaps(mapView))
{
bool copyIsAction = CopyPasteHelper.GetCopiedClipboardType() == typeof(InputAction);
menuEvent.menu.AppendAction(paste_String, _ => mapView.PasteItems(copyIsAction));
menuEvent.menu.AppendSeparator();
}
menuEvent.menu.AppendAction(add_Action_Map_String, _ => mapView.AddActionMap());
}) { target = element };
}
#endregion
#region Actions
// Determine whether current clipboard contents can pasted into the Actions TreeView
//
// item selected => can paste either Action or Binding (depends on selected item context)
// empty view => can only paste Action
// no selection => can only paste Action
//
private static bool CanPasteIntoActions(TreeView treeView)
{
bool hasPastableData = false;
bool selected = treeView.selectedIndex != -1;
if (selected)
{
var item = treeView.GetItemDataForIndex<ActionOrBindingData>(treeView.selectedIndex);
var itemType = item.isAction ? typeof(InputAction) : typeof(InputBinding);
hasPastableData = CopyPasteHelper.HasPastableClipboardData(itemType);
}
else
{
// Cannot paste Binding when no Action is selected or into an empty view
bool copyIsBinding = CopyPasteHelper.GetCopiedClipboardType() == typeof(InputBinding);
hasPastableData = !copyIsBinding && CopyPasteHelper.HasPastableClipboardData(typeof(InputAction));
}
return hasPastableData;
}
// Add the "Paste" option to all elements in the Action area.
public static void GetContextMenuForActionListView(ActionsTreeView actionsTreeView, TreeView treeView, VisualElement target)
{
_ = new ContextualMenuManipulator(menuEvent =>
{
bool haveMap = actionsTreeView.GetMapCount() > 0;
if (haveMap)
{
bool hasPastableData = CanPasteIntoActions(treeView);
if (hasPastableData)
{
menuEvent.menu.AppendAction(paste_String, _ => actionsTreeView.PasteItems());
}
menuEvent.menu.AppendSeparator();
menuEvent.menu.AppendAction(add_Action_String, _ => actionsTreeView.AddAction());
}
}) { target = target };
}
// Add "Add Action" option to empty space under the TreeView. Matches with old IMGUI style (ISX-1519).
// Include Paste here as well, since it makes sense for Actions; thus users would expect it for Bindings too.
public static void GetContextMenuForActionsEmptySpace(ActionsTreeView actionsTreeView, TreeView treeView, VisualElement target, bool onlyShowIfTreeIsEmpty = false)
{
_ = new ContextualMenuManipulator(menuEvent =>
{
bool haveMap = actionsTreeView.GetMapCount() > 0;
if (haveMap && (!onlyShowIfTreeIsEmpty || treeView.GetTreeCount() == 0))
{
bool hasPastableData = CanPasteIntoActions(treeView);
if (hasPastableData)
{
menuEvent.menu.AppendAction(paste_String, _ => actionsTreeView.PasteItems());
menuEvent.menu.AppendSeparator();
}
menuEvent.menu.AppendAction(add_Action_String, _ => actionsTreeView.AddAction());
}
}) { target = target };
}
public static void GetContextMenuForActionItem(ActionsTreeView treeView, InputActionsTreeViewItem treeViewItem, string controlLayout, int index)
{
treeViewItem.OnContextualMenuPopulateEvent = (menuEvent =>
{
menuEvent.menu.AppendAction(add_Binding_String, _ => treeView.AddBinding(index));
AppendCompositeMenuItems(treeView, controlLayout, index, (name, action) => menuEvent.menu.AppendAction(name, _ => action.Invoke()));
menuEvent.menu.AppendSeparator();
AppendRenameAction(menuEvent, treeView, index);
AppendDuplicateDeleteCutAndCopyActionsSection(menuEvent, treeView, index);
});
}
public static Action GetContextMenuForActionAddItem(ActionsTreeView treeView, string controlLayout, int index)
{
return () =>
{
GenericMenu menu = new GenericMenu();
menu.AddItem(new GUIContent(add_Binding_String), false, () => treeView.AddBinding(index));
AppendCompositeMenuItems(treeView, controlLayout, index, (name, action) => menu.AddItem(new GUIContent(name), false, action.Invoke));
menu.ShowAsContext();
};
}
private static void AppendCompositeMenuItems(ActionsTreeView treeView, string expectedControlLayout, int index, Action<string, Action> addToMenuAction)
{
foreach (var compositeName in InputBindingComposite.s_Composites.internedNames.Where(x =>
!InputBindingComposite.s_Composites.aliases.Contains(x)).OrderBy(x => x))
{
// Skip composites we should hide
var compositeType = InputBindingComposite.s_Composites.LookupTypeRegistration(compositeName);
var designTimeVisible = compositeType.GetCustomAttribute<DesignTimeVisibleAttribute>();
if (designTimeVisible != null && !designTimeVisible.Visible)
continue;
// Skip composites that don't match the expected control layout
// NOTE: "Any" is a special case and expected to be null
if (!string.IsNullOrEmpty(expectedControlLayout))
{
var valueType = InputBindingComposite.GetValueType(compositeName);
if (valueType != null &&
!InputControlLayout.s_Layouts.ValueTypeIsAssignableFrom(
new InternedString(expectedControlLayout), valueType))
continue;
}
var displayName = compositeType.GetCustomAttribute<DisplayNameAttribute>();
var niceName = displayName != null ? displayName.DisplayName.Replace('/', '\\') : ObjectNames.NicifyVariableName(compositeName) + " Composite";
addToMenuAction.Invoke($"Add {niceName}", () => treeView.AddComposite(index, compositeName));
}
}
public static void GetContextMenuForCompositeItem(ActionsTreeView treeView, InputActionsTreeViewItem treeViewItem, int index)
{
treeViewItem.OnContextualMenuPopulateEvent = (menuEvent =>
{
AppendRenameAction(menuEvent, treeView, index);
AppendDuplicateDeleteCutAndCopyActionsSection(menuEvent, treeView, index);
});
}
public static void GetContextMenuForBindingItem(ActionsTreeView treeView, InputActionsTreeViewItem treeViewItem, int index)
{
treeViewItem.OnContextualMenuPopulateEvent = (menuEvent =>
{
AppendDuplicateDeleteCutAndCopyActionsSection(menuEvent, treeView, index);
});
}
private static void AppendRenameAction(ContextualMenuPopulateEvent menuEvent, ActionsTreeView treeView, int index)
{
menuEvent.menu.AppendAction(rename_String, _ => treeView.RenameActionItem(index));
}
// These actions are always either all present, or all missing, so we can group their Append calls here.
private static void AppendDuplicateDeleteCutAndCopyActionsSection(ContextualMenuPopulateEvent menuEvent, ActionsTreeView actionsTreeView, int index)
{
menuEvent.menu.AppendAction(duplicate_String, _ => actionsTreeView.DuplicateItem(index));
menuEvent.menu.AppendAction(delete_String, _ => actionsTreeView.DeleteItem(index));
menuEvent.menu.AppendSeparator();
menuEvent.menu.AppendAction(copy_String, _ => actionsTreeView.CopyItems());
menuEvent.menu.AppendAction(cut_String, _ => actionsTreeView.CutItems());
}
#endregion
}
}
#endif