#nullable enable
using System;
using System.Collections.Generic;
using RTGLite;
using UnityEngine;
using UnityEngine.InputSystem;
using UVC.Core;
using UVC.Studio.Config;
using UVC.UI.Commands;
namespace UVC.Studio.Manager
{
///
/// Studio 단축키 관리자
/// RTGLite의 RTInput과 UVC Setting 시스템을 연결하는 브릿지 역할을 합니다.
///
///
/// [ 개요 ]
/// RTInput의 카메라/그리드/기즈모 단축키는 그대로 유지하면서,
/// UVC의 메뉴와 도구 단축키를 Setting에서 관리합니다.
///
/// [ 사용 예시 ]
///
/// // 1. 단축키 등록
/// shortcutManager.RegisterMenuShortcut("newProject", () => new FileNewCommand().Execute());
///
/// // 2. 단축키 활성화/비활성화
/// shortcutManager.SetShortcutEnabled("newProject", false);
///
/// // 3. 설정 변경 후 갱신
/// shortcutManager.RefreshShortcuts();
///
///
[DefaultExecutionOrder(100)] // RTInput보다 늦게 실행
public class StudioShortcutManager : MonoBehaviour
{
#region Singleton
private static StudioShortcutManager? _instance;
public static StudioShortcutManager? Instance => _instance;
#endregion
#region Private Fields
private Setting? _setting;
// 메뉴 단축키 액션 매핑
private readonly Dictionary _menuShortcuts = new();
// 도구 단축키 액션 매핑
private readonly Dictionary _toolShortcuts = new();
// 단축키 활성화 상태
private readonly Dictionary _shortcutEnabled = new();
// 전역 단축키 비활성화 플래그 (모달 등에서 사용)
private bool _globalEnabled = true;
// 입력 필드 포커스 상태 캐싱
private bool _isInputFieldFocused = false;
#endregion
#region Public Properties
///
/// 단축키 전역 활성화 상태
///
public bool GlobalEnabled
{
get => _globalEnabled;
set => _globalEnabled = value;
}
#endregion
#region Unity Lifecycle
private void Awake()
{
if (_instance != null && _instance != this)
{
Destroy(gameObject);
return;
}
_instance = this;
}
private void Start()
{
// Setting 가져오기
if (InjectorAppContext.Instance != null)
{
_setting = InjectorAppContext.Instance.Get();
}
}
private void Update()
{
// 전역 비활성화 상태면 스킵
if (!_globalEnabled) return;
// 입력 필드에 포커스가 있으면 스킵
if (IsInputFieldFocused()) return;
// RTInput의 마우스 조작 중에는 스킵 (카메라 이동 등)
if (RTInput.get != null && RTInput.get.anyMBPressed) return;
// 단축키 처리
ProcessShortcuts();
}
private void OnDestroy()
{
if (_instance == this)
{
_instance = null;
}
}
#endregion
#region Public Methods
///
/// Setting을 주입합니다.
///
public void SetSetting(Setting setting)
{
_setting = setting;
}
///
/// 메뉴 단축키를 등록합니다.
///
/// 단축키 ID (Setting의 필드명과 매칭)
/// 실행할 액션
public void RegisterMenuShortcut(string shortcutId, Action action)
{
_menuShortcuts[shortcutId] = new ShortcutEntry(action);
_shortcutEnabled[shortcutId] = true;
}
///
/// 메뉴 단축키를 ICommand와 함께 등록합니다.
///
public void RegisterMenuShortcut(string shortcutId, ICommand command)
{
RegisterMenuShortcut(shortcutId, () => command.Execute());
}
///
/// 도구 단축키를 등록합니다.
///
public void RegisterToolShortcut(string shortcutId, Action action)
{
_toolShortcuts[shortcutId] = new ShortcutEntry(action);
_shortcutEnabled[shortcutId] = true;
}
///
/// 도구 단축키를 ICommand와 함께 등록합니다.
///
public void RegisterToolShortcut(string shortcutId, ICommand command)
{
RegisterToolShortcut(shortcutId, () => command.Execute());
}
///
/// 단축키 활성화 상태를 설정합니다.
///
public void SetShortcutEnabled(string shortcutId, bool enabled)
{
_shortcutEnabled[shortcutId] = enabled;
}
///
/// 모든 단축키의 활성화 상태를 조회합니다.
///
public bool IsShortcutEnabled(string shortcutId)
{
return _shortcutEnabled.TryGetValue(shortcutId, out var enabled) && enabled;
}
///
/// Setting에서 단축키 정보를 다시 읽어옵니다.
///
public void RefreshShortcuts()
{
if (_setting == null && InjectorAppContext.Instance != null)
{
_setting = InjectorAppContext.Instance.Get();
}
Debug.Log("[StudioShortcutManager] 단축키가 갱신되었습니다.");
}
///
/// 등록된 단축키를 제거합니다.
///
public void UnregisterShortcut(string shortcutId)
{
_menuShortcuts.Remove(shortcutId);
_toolShortcuts.Remove(shortcutId);
_shortcutEnabled.Remove(shortcutId);
}
///
/// 모든 등록된 단축키를 제거합니다.
///
public void ClearAllShortcuts()
{
_menuShortcuts.Clear();
_toolShortcuts.Clear();
_shortcutEnabled.Clear();
}
#endregion
#region Private Methods
///
/// 단축키를 처리합니다.
///
private void ProcessShortcuts()
{
if (_setting == null) return;
var shortcuts = _setting.Data?.shortcuts;
if (shortcuts == null) return;
// 메뉴 단축키 처리
ProcessMenuShortcuts(shortcuts.menu);
// 도구 단축키 처리 (카메라 Fly 모드가 아닐 때만)
if (RTCamera.get == null || RTCamera.get.navigationMode == ECameraNavigationMode.None)
{
ProcessToolShortcuts(shortcuts.tools);
}
}
///
/// 메뉴 단축키를 처리합니다.
///
private void ProcessMenuShortcuts(MenuShortcuts? menu)
{
if (menu == null) return;
TryExecuteShortcut("newProject", menu.newProject?.key, _menuShortcuts);
TryExecuteShortcut("openProject", menu.openProject?.key, _menuShortcuts);
TryExecuteShortcut("saveProject", menu.saveProject?.key, _menuShortcuts);
TryExecuteShortcut("saveAsProject", menu.saveAsProject?.key, _menuShortcuts);
TryExecuteShortcut("insertDb", menu.insertDb?.key, _menuShortcuts);
TryExecuteShortcut("exportLayout", menu.exportLayout?.key, _menuShortcuts);
TryExecuteShortcut("exportMeta", menu.exportMeta?.key, _menuShortcuts);
TryExecuteShortcut("exportGltf", menu.exportGltf?.key, _menuShortcuts);
TryExecuteShortcut("undo", menu.undo?.key, _menuShortcuts);
TryExecuteShortcut("redo", menu.redo?.key, _menuShortcuts);
TryExecuteShortcut("duplicate", menu.duplicate?.key, _menuShortcuts);
TryExecuteShortcut("delete", menu.delete?.key, _menuShortcuts);
TryExecuteShortcut("createPlane", menu.createPlane?.key, _menuShortcuts);
}
///
/// 도구 단축키를 처리합니다.
///
private void ProcessToolShortcuts(ToolShortcuts? tools)
{
if (tools == null) return;
TryExecuteShortcut("select", tools.select?.key, _toolShortcuts);
TryExecuteShortcut("move", tools.move?.key, _toolShortcuts);
TryExecuteShortcut("rotate", tools.rotate?.key, _toolShortcuts);
TryExecuteShortcut("scale", tools.scale?.key, _toolShortcuts);
TryExecuteShortcut("snap", tools.snap?.key, _toolShortcuts);
TryExecuteShortcut("guide", tools.guide?.key, _toolShortcuts);
TryExecuteShortcut("node", tools.node?.key, _toolShortcuts);
TryExecuteShortcut("link", tools.link?.key, _toolShortcuts);
TryExecuteShortcut("arc", tools.arc?.key, _toolShortcuts);
}
///
/// 단축키가 눌렸는지 확인하고 액션을 실행합니다.
///
private void TryExecuteShortcut(string shortcutId, string? keyCombo, Dictionary shortcuts)
{
if (string.IsNullOrEmpty(keyCombo)) return;
if (!shortcuts.TryGetValue(shortcutId, out var entry)) return;
if (!IsShortcutEnabled(shortcutId)) return;
if (IsKeyComboPressed(keyCombo))
{
entry.Action?.Invoke();
}
}
///
/// 키 조합이 눌렸는지 확인합니다.
///
/// "Ctrl+S", "Ctrl+Shift+Z", "W" 형식의 키 조합
private bool IsKeyComboPressed(string keyCombo)
{
var binding = ParseKeyCombo(keyCombo);
return IsBindingActive(binding);
}
///
/// 키 조합 문자열을 파싱합니다.
///
private ShortcutBinding ParseKeyCombo(string keyCombo)
{
var binding = new ShortcutBinding();
if (string.IsNullOrEmpty(keyCombo)) return binding;
// 공백 제거 및 '+' 로 분리
var parts = keyCombo.Replace(" ", "").Split('+');
foreach (var part in parts)
{
var p = part.ToLower();
switch (p)
{
case "ctrl":
case "control":
binding.NeedCtrl = true;
break;
case "shift":
binding.NeedShift = true;
break;
case "alt":
binding.NeedAlt = true;
break;
case "cmd":
case "command":
binding.NeedCmd = true;
break;
default:
// 메인 키 파싱
if (KeyEx.KeyFromText(p, out Key key))
{
binding.MainKey = key;
}
else
{
// 특수 키 처리
binding.MainKey = ParseSpecialKey(p);
}
break;
}
}
return binding;
}
///
/// 특수 키를 파싱합니다.
///
private Key ParseSpecialKey(string keyText)
{
return keyText.ToLower() switch
{
"delete" => Key.Delete,
"backspace" => Key.Backspace,
"enter" => Key.Enter,
"return" => Key.Enter,
"escape" => Key.Escape,
"esc" => Key.Escape,
"space" => Key.Space,
"tab" => Key.Tab,
"up" => Key.UpArrow,
"down" => Key.DownArrow,
"left" => Key.LeftArrow,
"right" => Key.RightArrow,
"f1" => Key.F1,
"f2" => Key.F2,
"f3" => Key.F3,
"f4" => Key.F4,
"f5" => Key.F5,
"f6" => Key.F6,
"f7" => Key.F7,
"f8" => Key.F8,
"f9" => Key.F9,
"f10" => Key.F10,
"f11" => Key.F11,
"f12" => Key.F12,
_ => Key.None
};
}
///
/// 바인딩이 활성화되었는지 확인합니다.
///
private bool IsBindingActive(ShortcutBinding binding)
{
var rtInput = RTInput.get;
if (rtInput == null) return false;
// 수정자 키 확인
if (binding.NeedCtrl != rtInput.ctrlPressed) return false;
if (binding.NeedShift != rtInput.shiftPressed) return false;
if (binding.NeedAlt != rtInput.altPresed) return false;
if (binding.NeedCmd != rtInput.cmdPressed) return false;
// 메인 키 확인 (KeyWentDown으로 단발 입력만 감지)
if (binding.MainKey != Key.None && !rtInput.KeyWentDown(binding.MainKey))
{
return false;
}
return true;
}
///
/// 입력 필드에 포커스가 있는지 확인합니다.
///
private bool IsInputFieldFocused()
{
// Unity EventSystem에서 현재 선택된 객체 확인
var eventSystem = UnityEngine.EventSystems.EventSystem.current;
if (eventSystem == null) return false;
var selected = eventSystem.currentSelectedGameObject;
if (selected == null) return false;
// TMP_InputField 또는 InputField 컴포넌트 확인
var tmpInput = selected.GetComponent();
if (tmpInput != null && tmpInput.isFocused) return true;
var legacyInput = selected.GetComponent();
if (legacyInput != null && legacyInput.isFocused) return true;
return false;
}
#endregion
#region Nested Types
///
/// 단축키 바인딩 정보
///
private struct ShortcutBinding
{
public bool NeedCtrl;
public bool NeedShift;
public bool NeedAlt;
public bool NeedCmd;
public Key MainKey;
}
///
/// 단축키 엔트리
///
private class ShortcutEntry
{
public Action? Action { get; }
public ShortcutEntry(Action action)
{
Action = action;
}
}
#endregion
}
}