단축키, Command 설정 개발 중
This commit is contained in:
@@ -49,12 +49,6 @@ namespace UVC.UI.List.Tree
|
||||
[SerializeField]
|
||||
protected Button childExpand;
|
||||
|
||||
/// <summary>
|
||||
/// 자식 아이템들을 포함하는 컨테이너 GameObject.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
protected GameObject childContainer;
|
||||
|
||||
/// <summary>
|
||||
/// 자식 TreeListItem이 배치될 부모 RectTransform.
|
||||
/// </summary>
|
||||
@@ -145,7 +139,6 @@ namespace UVC.UI.List.Tree
|
||||
childRootLayoutGroup = childRoot.GetComponent<VerticalLayoutGroup>();
|
||||
}
|
||||
|
||||
|
||||
if (data.Children.Count > 0)
|
||||
{
|
||||
// 각 자식 데이터에 대해 UI 생성
|
||||
@@ -214,7 +207,7 @@ namespace UVC.UI.List.Tree
|
||||
|
||||
if (changedType == ChangedType.Expanded)
|
||||
{
|
||||
childContainer.SetActive(data.IsExpanded);
|
||||
childRoot.gameObject.SetActive(data.IsExpanded);
|
||||
// 펼침/접힘 상태 변경 처리
|
||||
SetExpand();
|
||||
return;
|
||||
@@ -521,7 +514,7 @@ namespace UVC.UI.List.Tree
|
||||
|
||||
// 펼침 버튼
|
||||
childExpand.gameObject.SetActive(data!.Children.Count > 0);
|
||||
childContainer.SetActive(data!.Children.Count > 0 && data!.IsExpanded);
|
||||
childRoot.gameObject.SetActive(data!.Children.Count > 0 && data!.IsExpanded);
|
||||
|
||||
// DORotate(목표 각도, 지속 시간)
|
||||
// IsExpanded가 true면 0도 (▼), false면 90도 (▶)
|
||||
@@ -550,7 +543,7 @@ namespace UVC.UI.List.Tree
|
||||
CreateItem(data);
|
||||
|
||||
// 2️. 자식 컨테이너 활성화 (표시)
|
||||
childContainer.SetActive(true);
|
||||
childRoot.gameObject.SetActive(true);
|
||||
|
||||
// 3️. 0.3초에 걸쳐 펼침 애니메이션 실행
|
||||
// 사용자가 새 자식이 추가되었음을 자연스럽게 인식
|
||||
|
||||
@@ -121,6 +121,12 @@ namespace UVC.UI.List.Tree
|
||||
/// </summary>
|
||||
public string ExternalKey { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 외부에서 사용하기 위한 사용자 정의 데이터 태그
|
||||
/// Transform, GameObject 등 임의 객체 참조에 사용
|
||||
/// </summary>
|
||||
public object? Tag { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 생성자 (Constructors)
|
||||
|
||||
@@ -322,6 +322,52 @@ namespace UVC.UI.Menu
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 메뉴 아이템의 단축키를 동적으로 변경합니다.
|
||||
/// 이 메서드는 모델(<see cref="TopMenuModel"/>)의 데이터를 변경하고,
|
||||
/// 변경 사항을 뷰(<see cref="TopMenuView"/>)에 반영합니다.
|
||||
/// </summary>
|
||||
/// <param name="itemId">단축키를 변경할 메뉴 아이템의 고유 ID입니다.</param>
|
||||
/// <param name="shortcut">새로운 단축키 문자열입니다.</param>
|
||||
public virtual void SetMenuItemShortcut(string itemId, string shortcut)
|
||||
{
|
||||
if (model == null || model.MenuItems == null)
|
||||
{
|
||||
ULog.Warning("모델이 초기화되지 않아 메뉴 아이템 단축키를 변경할 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
MenuItemData targetItem = FindMenuItemRecursive(model.MenuItems, itemId);
|
||||
if (targetItem != null)
|
||||
{
|
||||
targetItem.Shortcut = shortcut;
|
||||
if (view != null && view.isActiveAndEnabled)
|
||||
{
|
||||
view.UpdateShortcutText(itemId, shortcut);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ULog.Warning($"ID가 '{itemId}'인 메뉴 아이템을 모델에서 찾을 수 없어 단축키를 변경할 수 없습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모든 메뉴 아이템의 단축키를 최신 데이터로 업데이트합니다.
|
||||
/// Setting에서 단축키가 변경된 후 호출하여 UI를 갱신합니다.
|
||||
/// </summary>
|
||||
public virtual void RefreshAllShortcuts()
|
||||
{
|
||||
if (model == null || model.MenuItems == null || view == null)
|
||||
{
|
||||
ULog.Warning("모델 또는 뷰가 초기화되지 않아 단축키를 갱신할 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
view.UpdateAllShortcuts(model.MenuItems);
|
||||
ULog.Debug("모든 메뉴 아이템의 단축키가 갱신되었습니다.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 제공된 메뉴 아이템 리스트(<paramref name="items"/>)와 그 하위 메뉴들을 재귀적으로 탐색하여
|
||||
/// 지정된 ID(<paramref name="itemId"/>)를 가진 <see cref="MenuItemData"/>를 찾습니다.
|
||||
|
||||
@@ -55,6 +55,13 @@ namespace UVC.UI.Menu
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 메뉴 아이템의 단축키 문자열입니다.
|
||||
/// 예: "Ctrl+S", "Ctrl+Shift+N" 등
|
||||
/// 단축키가 없는 경우 null 또는 빈 문자열입니다.
|
||||
/// </summary>
|
||||
public string? Shortcut { get; set; }
|
||||
|
||||
private int depth = 0; // 메뉴 아이템의 깊이 (하위 메뉴의 레벨)
|
||||
/// <summary>
|
||||
/// 계층 구조에서 현재 개체의 깊이를 가져옵니다.
|
||||
@@ -85,7 +92,8 @@ namespace UVC.UI.Menu
|
||||
/// <param name="subMenuItems">하위 메뉴 아이템 목록 (선택 사항).</param>
|
||||
/// <param name="isSeparator">구분선 여부 (선택 사항, 기본값: false).</param>
|
||||
/// <param name="isEnabled">활성화 상태 (선택 사항, 기본값: true).</param>
|
||||
public MenuItemData(string itemId, string displayName, ICommand? command = null, object? commandParameter = null, List<MenuItemData>? subMenuItems = null, bool isSeparator = false, bool isEnabled = true, int depth = 0)
|
||||
/// <param name="shortcut">단축키 문자열 (선택 사항). 예: "Ctrl+S"</param>
|
||||
public MenuItemData(string itemId, string displayName, ICommand? command = null, object? commandParameter = null, List<MenuItemData>? subMenuItems = null, bool isSeparator = false, bool isEnabled = true, int depth = 0, string? shortcut = null)
|
||||
{
|
||||
ItemId = itemId;
|
||||
DisplayName = displayName;
|
||||
@@ -95,6 +103,7 @@ namespace UVC.UI.Menu
|
||||
IsSeparator = isSeparator;
|
||||
IsEnabled = isEnabled;
|
||||
this.depth = depth; // 메뉴 아이템의 깊이 설정
|
||||
Shortcut = shortcut; // 단축키 문자열 설정
|
||||
|
||||
SetupDepthAndParent();
|
||||
}
|
||||
|
||||
@@ -79,6 +79,9 @@ namespace UVC.UI.Menu
|
||||
[Tooltip("하위 메뉴가 있음을 나타내는 화살표 UI GameObject의 이름입니다. (메뉴 아이템 프리팹 내부에 존재)")]
|
||||
[SerializeField] public string subMenuArrowName = "SubMenuArrow";
|
||||
|
||||
[Tooltip("단축키를 표시하는 TextMeshProUGUI의 이름입니다. (SubMenuItemPrefab 내부에 존재)")]
|
||||
[SerializeField] public string shortcutTextName = "ShorcutText";
|
||||
|
||||
[Header("Layout Settings")]
|
||||
[Tooltip("1차 메뉴 아이템과 그 하위 메뉴 컨테이너 간의 간격 (수평, 수직)")]
|
||||
[SerializeField] public Vector2 menuDepthSpace = new Vector2(0, -5);
|
||||
@@ -289,11 +292,23 @@ namespace UVC.UI.Menu
|
||||
menuItemInstance.name = $"MenuItem_{itemData.ItemId}_Depth{depth}";
|
||||
_menuItemObjects[itemData.ItemId] = menuItemInstance; // 관리 목록에 추가
|
||||
|
||||
// 메뉴 아이템 텍스트 설정
|
||||
TextMeshProUGUI buttonText = menuItemInstance.GetComponentInChildren<TextMeshProUGUI>(true); // 비활성화된 자식도 검색
|
||||
// 메뉴 아이템 버튼 기능 설정
|
||||
Button button = menuItemInstance.GetComponent<Button>();
|
||||
|
||||
// 메뉴 아이템 텍스트 설정
|
||||
// 먼저 "Text"라는 이름의 자식을 찾고, 없으면 GetComponentInChildren으로 찾기
|
||||
Transform textTransform = menuItemInstance.transform.Find("Text");
|
||||
TextMeshProUGUI? buttonText = null;
|
||||
if (textTransform != null)
|
||||
{
|
||||
buttonText = textTransform.GetComponent<TextMeshProUGUI>();
|
||||
}
|
||||
else
|
||||
{
|
||||
// "Text" 이름의 자식이 없으면 자식 중에서 TextMeshProUGUI를 찾음
|
||||
buttonText = menuItemInstance.GetComponentInChildren<TextMeshProUGUI>(true);
|
||||
}
|
||||
|
||||
if (buttonText != null && !string.IsNullOrEmpty(itemData.DisplayName))
|
||||
{
|
||||
buttonText.alpha = itemData.IsEnabled ? 1 : 0.25f;
|
||||
@@ -309,6 +324,29 @@ namespace UVC.UI.Menu
|
||||
}
|
||||
}
|
||||
|
||||
// 2차 메뉴 이상(depth > 0)일 때 단축키 텍스트 설정
|
||||
if (depth > 0 && !string.IsNullOrEmpty(shortcutTextName))
|
||||
{
|
||||
Debug.Log($"Setting shortcut for menu item '{itemData.ItemId}': '{itemData.Shortcut}'");
|
||||
Transform shortcutTransform = menuItemInstance.transform.Find(shortcutTextName);
|
||||
if (shortcutTransform != null)
|
||||
{
|
||||
TextMeshProUGUI shortcutText = shortcutTransform.GetComponent<TextMeshProUGUI>();
|
||||
if (shortcutText != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(itemData.Shortcut))
|
||||
{
|
||||
shortcutText.text = itemData.Shortcut;
|
||||
shortcutText.alpha = itemData.IsEnabled ? 1 : 0.25f;
|
||||
}
|
||||
else
|
||||
{
|
||||
shortcutText.text = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// isShrinkMenuItemWidth가 true일 때 1차 메뉴 아이템의 너비를 텍스트에 맞게 조절합니다.
|
||||
// 텍스트가 설정된 후에 이 로직을 실행해야 정확한 너비를 계산할 수 있습니다.
|
||||
if (depth == 0 && isShrinkMenuItemWidth && buttonText != null)
|
||||
@@ -727,5 +765,56 @@ namespace UVC.UI.Menu
|
||||
{
|
||||
return _menuItemObjects.TryGetValue(itemId, out menuItemGO);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 메뉴 아이템의 단축키 텍스트를 업데이트합니다.
|
||||
/// </summary>
|
||||
/// <param name="itemId">업데이트할 메뉴 아이템의 고유 ID입니다.</param>
|
||||
/// <param name="shortcut">새로운 단축키 문자열입니다.</param>
|
||||
public virtual void UpdateShortcutText(string itemId, string shortcut)
|
||||
{
|
||||
if (_menuItemObjects.TryGetValue(itemId, out GameObject menuItemInstance))
|
||||
{
|
||||
Transform shortcutTransform = menuItemInstance.transform.Find(shortcutTextName);
|
||||
if (shortcutTransform != null)
|
||||
{
|
||||
TextMeshProUGUI shortcutText = shortcutTransform.GetComponent<TextMeshProUGUI>();
|
||||
if (shortcutText != null)
|
||||
{
|
||||
shortcutText.text = shortcut ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모든 메뉴 아이템의 단축키 텍스트를 업데이트합니다.
|
||||
/// </summary>
|
||||
/// <param name="items">최상위 메뉴 아이템들의 데이터 리스트입니다.</param>
|
||||
public virtual void UpdateAllShortcuts(List<MenuItemData> items)
|
||||
{
|
||||
UpdateShortcutsRecursive(items);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 재귀적으로 메뉴 아이템들의 단축키 텍스트를 업데이트합니다.
|
||||
/// </summary>
|
||||
/// <param name="items">단축키를 업데이트할 메뉴 아이템들의 데이터 리스트입니다.</param>
|
||||
protected virtual void UpdateShortcutsRecursive(List<MenuItemData> items)
|
||||
{
|
||||
if (items == null) return;
|
||||
|
||||
foreach (var itemData in items)
|
||||
{
|
||||
if (itemData.IsSeparator) continue;
|
||||
|
||||
UpdateShortcutText(itemData.ItemId, itemData.Shortcut);
|
||||
|
||||
if (itemData.SubMenuItems != null && itemData.SubMenuItems.Count > 0)
|
||||
{
|
||||
UpdateShortcutsRecursive(itemData.SubMenuItems);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,65 @@ namespace UVC.UI.Window.PropertyWindow.UI
|
||||
_yInputField.onEndEdit.RemoveAllListeners();
|
||||
_xInputField.onEndEdit.AddListener(OnValueSubmitted);
|
||||
_yInputField.onEndEdit.AddListener(OnValueSubmitted);
|
||||
|
||||
// Tab 키 네비게이션 설정: X → Y 순환
|
||||
SetupTabNavigation();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tab 키로 X → Y 순서로 포커스가 이동하도록 네비게이션을 설정합니다.
|
||||
/// </summary>
|
||||
private void SetupTabNavigation()
|
||||
{
|
||||
// X InputField 네비게이션 설정
|
||||
var xNav = _xInputField.navigation;
|
||||
xNav.mode = Navigation.Mode.Explicit;
|
||||
xNav.selectOnRight = _yInputField;
|
||||
xNav.selectOnDown = _yInputField;
|
||||
_xInputField.navigation = xNav;
|
||||
|
||||
// Y InputField 네비게이션 설정
|
||||
var yNav = _yInputField.navigation;
|
||||
yNav.mode = Navigation.Mode.Explicit;
|
||||
yNav.selectOnLeft = _xInputField;
|
||||
yNav.selectOnUp = _xInputField;
|
||||
_yInputField.navigation = yNav;
|
||||
|
||||
// Tab 키 입력 처리를 위한 onSelect 이벤트 등록
|
||||
_xInputField.onSelect.RemoveAllListeners();
|
||||
_yInputField.onSelect.RemoveAllListeners();
|
||||
_xInputField.onSelect.AddListener(_ => _currentFocusedField = _xInputField);
|
||||
_yInputField.onSelect.AddListener(_ => _currentFocusedField = _yInputField);
|
||||
}
|
||||
|
||||
private TMP_InputField _currentFocusedField;
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// Tab 키 입력 처리
|
||||
if (Input.GetKeyDown(KeyCode.Tab) && _currentFocusedField != null)
|
||||
{
|
||||
bool shiftHeld = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
|
||||
|
||||
if (shiftHeld)
|
||||
{
|
||||
// Shift+Tab: 역방향 이동
|
||||
if (_currentFocusedField == _yInputField)
|
||||
{
|
||||
_xInputField.Select();
|
||||
_xInputField.ActivateInputField();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Tab: 정방향 이동
|
||||
if (_currentFocusedField == _xInputField)
|
||||
{
|
||||
_yInputField.Select();
|
||||
_yInputField.ActivateInputField();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -129,8 +188,17 @@ namespace UVC.UI.Window.PropertyWindow.UI
|
||||
/// </summary>
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_xInputField != null) _xInputField.onEndEdit.RemoveAllListeners();
|
||||
if (_yInputField != null) _yInputField.onEndEdit.RemoveAllListeners();
|
||||
if (_xInputField != null)
|
||||
{
|
||||
_xInputField.onEndEdit.RemoveAllListeners();
|
||||
_xInputField.onSelect.RemoveAllListeners();
|
||||
}
|
||||
if (_yInputField != null)
|
||||
{
|
||||
_yInputField.onEndEdit.RemoveAllListeners();
|
||||
_yInputField.onSelect.RemoveAllListeners();
|
||||
}
|
||||
_currentFocusedField = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,6 +97,86 @@ namespace UVC.UI.Window.PropertyWindow.UI
|
||||
_xInputField.onEndEdit.AddListener(OnValueSubmitted);
|
||||
_yInputField.onEndEdit.AddListener(OnValueSubmitted);
|
||||
_zInputField.onEndEdit.AddListener(OnValueSubmitted);
|
||||
|
||||
// Tab 키 네비게이션 설정: X → Y → Z → X 순환
|
||||
SetupTabNavigation();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tab 키로 X → Y → Z 순서로 포커스가 이동하도록 네비게이션을 설정합니다.
|
||||
/// </summary>
|
||||
private void SetupTabNavigation()
|
||||
{
|
||||
// X InputField 네비게이션 설정
|
||||
var xNav = _xInputField.navigation;
|
||||
xNav.mode = Navigation.Mode.Explicit;
|
||||
xNav.selectOnRight = _yInputField;
|
||||
xNav.selectOnDown = _yInputField;
|
||||
_xInputField.navigation = xNav;
|
||||
|
||||
// Y InputField 네비게이션 설정
|
||||
var yNav = _yInputField.navigation;
|
||||
yNav.mode = Navigation.Mode.Explicit;
|
||||
yNav.selectOnLeft = _xInputField;
|
||||
yNav.selectOnUp = _xInputField;
|
||||
yNav.selectOnRight = _zInputField;
|
||||
yNav.selectOnDown = _zInputField;
|
||||
_yInputField.navigation = yNav;
|
||||
|
||||
// Z InputField 네비게이션 설정
|
||||
var zNav = _zInputField.navigation;
|
||||
zNav.mode = Navigation.Mode.Explicit;
|
||||
zNav.selectOnLeft = _yInputField;
|
||||
zNav.selectOnUp = _yInputField;
|
||||
_zInputField.navigation = zNav;
|
||||
|
||||
// Tab 키 입력 처리를 위한 onSelect 이벤트 등록
|
||||
_xInputField.onSelect.RemoveAllListeners();
|
||||
_yInputField.onSelect.RemoveAllListeners();
|
||||
_zInputField.onSelect.RemoveAllListeners();
|
||||
_xInputField.onSelect.AddListener(_ => _currentFocusedField = _xInputField);
|
||||
_yInputField.onSelect.AddListener(_ => _currentFocusedField = _yInputField);
|
||||
_zInputField.onSelect.AddListener(_ => _currentFocusedField = _zInputField);
|
||||
}
|
||||
|
||||
private TMP_InputField _currentFocusedField;
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// Tab 키 입력 처리
|
||||
if (Input.GetKeyDown(KeyCode.Tab) && _currentFocusedField != null)
|
||||
{
|
||||
bool shiftHeld = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
|
||||
|
||||
if (shiftHeld)
|
||||
{
|
||||
// Shift+Tab: 역방향 이동
|
||||
if (_currentFocusedField == _zInputField)
|
||||
{
|
||||
_yInputField.Select();
|
||||
_yInputField.ActivateInputField();
|
||||
}
|
||||
else if (_currentFocusedField == _yInputField)
|
||||
{
|
||||
_xInputField.Select();
|
||||
_xInputField.ActivateInputField();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Tab: 정방향 이동
|
||||
if (_currentFocusedField == _xInputField)
|
||||
{
|
||||
_yInputField.Select();
|
||||
_yInputField.ActivateInputField();
|
||||
}
|
||||
else if (_currentFocusedField == _yInputField)
|
||||
{
|
||||
_zInputField.Select();
|
||||
_zInputField.ActivateInputField();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -141,9 +221,22 @@ namespace UVC.UI.Window.PropertyWindow.UI
|
||||
/// </summary>
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_xInputField != null) _xInputField.onEndEdit.RemoveAllListeners();
|
||||
if (_yInputField != null) _yInputField.onEndEdit.RemoveAllListeners();
|
||||
if (_zInputField != null) _zInputField.onEndEdit.RemoveAllListeners();
|
||||
if (_xInputField != null)
|
||||
{
|
||||
_xInputField.onEndEdit.RemoveAllListeners();
|
||||
_xInputField.onSelect.RemoveAllListeners();
|
||||
}
|
||||
if (_yInputField != null)
|
||||
{
|
||||
_yInputField.onEndEdit.RemoveAllListeners();
|
||||
_yInputField.onSelect.RemoveAllListeners();
|
||||
}
|
||||
if (_zInputField != null)
|
||||
{
|
||||
_zInputField.onEndEdit.RemoveAllListeners();
|
||||
_zInputField.onSelect.RemoveAllListeners();
|
||||
}
|
||||
_currentFocusedField = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user