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

207 lines
9.0 KiB
C#

#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine.UIElements;
using PopupWindow = UnityEngine.UIElements.PopupWindow;
namespace UnityEngine.InputSystem.Editor
{
internal class ControlSchemesView : ViewBase<InputControlScheme>
{
//is used to save the new name of the control scheme when renaming
private string m_NewName;
public event Action<ViewBase<InputControlScheme>> OnClosing;
public ControlSchemesView(VisualElement root, StateContainer stateContainer, bool updateExisting = false)
: base(root, stateContainer)
{
m_UpdateExisting = updateExisting;
var controlSchemeEditor = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(
InputActionsEditorConstants.PackagePath +
InputActionsEditorConstants.ResourcesPath +
InputActionsEditorConstants.ControlSchemeEditorViewUxml);
var controlSchemeVisualElement = controlSchemeEditor.CloneTree();
controlSchemeVisualElement.Q<Button>(kCancelButton).clicked += Cancel;
controlSchemeVisualElement.Q<Button>(kSaveButton).clicked += SaveAndClose;
controlSchemeVisualElement.Q<TextField>(kControlSchemeNameTextField).RegisterCallback<FocusOutEvent>(evt =>
{
Dispatch((in InputActionsEditorState state) =>
{
// If the name is the same as the current name, don't change it
var newName = ((TextField)evt.currentTarget).value.Trim();
if (string.IsNullOrEmpty(newName) || String.Compare(newName, state.selectedControlScheme.name) == 0)
{
m_NewName = String.Empty;
// write back the value to the text field if the name was empty
((TextField)evt.currentTarget).value = state.selectedControlScheme.name;
}
else
{
m_NewName = ControlSchemeCommands.MakeUniqueControlSchemeName(state, newName);
// write back the value to the text field if the name was not unique
((TextField)evt.currentTarget).value = m_NewName;
}
return state.With(selectedControlScheme: state.selectedControlScheme);
});
});
m_ModalWindow = new VisualElement
{
style = { position = new StyleEnum<Position>(Position.Absolute) }
};
var popupWindow = new PopupWindow
{
text = "Add Control Scheme",
style = { position = new StyleEnum<Position>(Position.Absolute), maxWidth = new StyleLength(new Length(100, LengthUnit.Percent)) }
};
popupWindow.contentContainer.Add(controlSchemeVisualElement);
m_ModalWindow.Add(popupWindow);
root.Add(m_ModalWindow);
m_ModalWindow.StretchToParentSize();
popupWindow.RegisterCallback<ClickEvent>(evt => evt.StopPropagation());
m_ListView = controlSchemeVisualElement.Q<MultiColumnListView>(kControlSchemesListView);
m_ListView.columns[kDeviceTypeColumnName].makeCell = () => new Label();
m_ListView.columns[kDeviceTypeColumnName].bindCell = BindDeviceTypeCell;
m_ListView.columns[kRequiredColumnName].makeCell = MakeRequiredCell;
m_ListView.columns[kRequiredColumnName].bindCell = BindDeviceRequiredCell;
m_ListView.columns[kRequiredColumnName].unbindCell = UnbindDeviceRequiredCell;
m_ListView.Q<Button>(kUnityListViewAddButton).clickable = new Clickable(AddDeviceRequirement);
m_ListView.Q<Button>(kUnityListViewRemoveButton).clickable = new Clickable(RemoveDeviceRequirement);
m_ListView.itemIndexChanged += (oldPosition, newPosition) =>
{
Dispatch(ControlSchemeCommands.ReorderDeviceRequirements(oldPosition, newPosition));
};
m_ListView.itemsSource = new List<string>();
CreateSelector(s => s.selectedControlScheme,
(_, s) => s.selectedControlScheme);
}
private void AddDeviceRequirement()
{
var dropdown = new InputControlPickerDropdown(new InputControlPickerState(), path =>
{
var requirement = new InputControlScheme.DeviceRequirement { controlPath = path, isOptional = false };
Dispatch(ControlSchemeCommands.AddDeviceRequirement(requirement));
}, mode: InputControlPicker.Mode.PickDevice);
dropdown.Show(new Rect(Event.current.mousePosition, Vector2.zero));
}
private void RemoveDeviceRequirement()
{
if (m_ListView.selectedIndex == -1)
return;
Dispatch(ControlSchemeCommands.RemoveDeviceRequirement(m_ListView.selectedIndex));
}
public override void RedrawUI(InputControlScheme viewState)
{
rootElement.Q<TextField>(kControlSchemeNameTextField).value = string.IsNullOrEmpty(m_NewName) ? viewState.name : m_NewName;
m_ListView.itemsSource?.Clear();
m_ListView.itemsSource = viewState.deviceRequirements.Count > 0 ?
viewState.deviceRequirements.Select(r => (r.controlPath, r.isOptional)).ToList() :
new List<(string, bool)>();
m_ListView.Rebuild();
}
public override void DestroyView()
{
m_ModalWindow.RemoveFromHierarchy();
}
private void SaveAndClose()
{
// Persist the current ControlScheme values to the SerializedProperty
Dispatch(ControlSchemeCommands.SaveControlScheme(m_NewName, m_UpdateExisting));
CloseView();
}
private void Cancel()
{
// Reload the selected ControlScheme values from the SerilaizedProperty and throw away any changes
Dispatch(ControlSchemeCommands.ResetSelectedControlScheme());
CloseView();
}
private void CloseView()
{
// Closing the View without explicitly selecting "Save" or "Cancel" holds the values in the
// current UI state but won't persist them; the Asset Editor state isn't dirtied.
//
// This means accidentally clicking outside of the View (closes the dialog) won't immediately
// cause loss of work: the "Edit Control Scheme" option can be selected to re-open the View
// the changes retained. However, if a different ControlScheme is selected or the Asset
// Editor window is closed, then the changes are lost.
m_NewName = string.Empty;
OnClosing?.Invoke(this);
}
private VisualElement MakeRequiredCell()
{
var ve = new VisualElement
{
style =
{
flexDirection = FlexDirection.Column,
flexGrow = 1,
alignContent = new StyleEnum<Align>(Align.Center)
}
};
ve.Add(new Toggle());
return ve;
}
private void BindDeviceRequiredCell(VisualElement visualElement, int rowIndex)
{
var toggle = visualElement.Q<Toggle>();
var rowItem = ((string path, bool optional))m_ListView.itemsSource[rowIndex];
toggle.value = !rowItem.optional;
var eventCallback = (EventCallback<ChangeEvent<bool>>)(evt =>
Dispatch(ControlSchemeCommands.ChangeDeviceRequirement(rowIndex, evt.newValue)));
toggle.userData = eventCallback;
toggle.RegisterValueChangedCallback(eventCallback);
}
private void UnbindDeviceRequiredCell(VisualElement visualElement, int rowIndex)
{
var toggle = visualElement.Q<Toggle>();
toggle.UnregisterValueChangedCallback((EventCallback<ChangeEvent<bool>>)toggle.userData);
}
private void BindDeviceTypeCell(VisualElement visualElement, int rowIndex)
{
((Label)visualElement).text = (((string, bool))m_ListView.itemsSource[rowIndex]).Item1;
}
private readonly bool m_UpdateExisting;
private MultiColumnListView m_ListView;
private VisualElement m_ModalWindow;
private const string kControlSchemeNameTextField = "control-scheme-name";
private const string kCancelButton = "cancel-button";
private const string kSaveButton = "save-button";
private const string kControlSchemesListView = "control-schemes-list-view";
private const string kDeviceTypeColumnName = "device-type";
private const string kRequiredColumnName = "required";
private const string kUnityListViewAddButton = "unity-list-view__add-button";
private const string kUnityListViewRemoveButton = "unity-list-view__remove-button";
}
}
#endif