user 41270a34f5 Refactor: 전체 에디터 UXML 전환 + 대시보드/런타임 UI + 한글화 + NanumGothic 폰트
- 모든 컨트롤러 에디터를 IMGUI → UI Toolkit(UXML/USS)으로 전환
  (Camera, Item, Event, Avatar, System, StreamDeck, OptiTrack, Facial)
- StreamingleCommon.uss 공통 테마 + 개별 에디터 USS 스타일시트
- SystemController 서브매니저 분리 (OptiTrack, Facial, Recording, Screenshot 등)
- 런타임 컨트롤 패널 (ESC 토글, 좌측 오버레이, 150% 스케일)
- 웹 대시보드 서버 (StreamingleDashboardServer) + 리타게팅 통합
- 설정 도구(StreamingleControllerSetupTool) UXML 재작성 + 원클릭 설정
- SimplePoseTransfer UXML 에디터 추가
- 전체 UXML 한글화 + NanumGothic 폰트 적용
- Streamingle.Debug → Streamingle.Debugging 네임스페이스 변경 (Debug.Log 충돌 해결)
- 불필요 코드 제거 (rawkey.cs, RetargetingHTTPServer, OptitrackSkeletonAnimator 등)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 02:51:43 +09:00

182 lines
5.9 KiB
C#

using UnityEngine;
using UnityEditor;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
[CustomEditor(typeof(EventController))]
public class EventControllerEditor : Editor
{
private const string UxmlPath = "Assets/Scripts/Streamingle/StreamingleControl/Editor/UXML/EventControllerEditor.uxml";
private const string CommonUssPath = "Assets/Scripts/Streamingle/StreamingleControl/Editor/UXML/StreamingleCommon.uss";
private VisualElement eventsContainer;
private Label eventsTitleLabel;
private EventController controller;
public override VisualElement CreateInspectorGUI()
{
controller = (EventController)target;
var root = new VisualElement();
var commonUss = AssetDatabase.LoadAssetAtPath<StyleSheet>(CommonUssPath);
if (commonUss != null) root.styleSheets.Add(commonUss);
var uxml = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(UxmlPath);
if (uxml != null) uxml.CloneTree(root);
BuildEventGroupsSection(root);
Undo.undoRedoPerformed += OnUndoRedo;
root.RegisterCallback<DetachFromPanelEvent>(_ => Undo.undoRedoPerformed -= OnUndoRedo);
return root;
}
private void OnUndoRedo()
{
if (controller == null) return;
serializedObject.Update();
RebuildEventList();
}
#region Event Groups List
private void BuildEventGroupsSection(VisualElement root)
{
var section = root.Q("eventGroupsSection");
if (section == null) return;
var header = new VisualElement();
header.AddToClassList("list-header");
eventsTitleLabel = new Label($"Event Groups ({controller.eventGroups.Count})");
eventsTitleLabel.AddToClassList("list-title");
header.Add(eventsTitleLabel);
var addBtn = new Button(AddEventGroup) { text = "+ 이벤트 추가" };
addBtn.AddToClassList("list-add-btn");
header.Add(addBtn);
section.Add(header);
eventsContainer = new VisualElement();
section.Add(eventsContainer);
RebuildEventList();
}
private void RebuildEventList()
{
if (eventsContainer == null || controller == null) return;
eventsContainer.Clear();
if (eventsTitleLabel != null)
eventsTitleLabel.text = $"Event Groups ({controller.eventGroups.Count})";
if (controller.eventGroups.Count == 0)
{
var empty = new Label("이벤트 그룹이 없습니다. '+ 이벤트 추가' 버튼을 눌러 추가하세요.");
empty.AddToClassList("list-empty");
eventsContainer.Add(empty);
return;
}
for (int i = 0; i < controller.eventGroups.Count; i++)
{
eventsContainer.Add(CreateEventGroupElement(i));
}
}
private VisualElement CreateEventGroupElement(int index)
{
var group = controller.eventGroups[index];
var item = new VisualElement();
item.AddToClassList("list-item");
// Header row
var headerRow = new VisualElement();
headerRow.AddToClassList("list-item-header");
var indexLabel = new Label($"{index + 1}");
indexLabel.AddToClassList("list-index");
headerRow.Add(indexLabel);
var nameField = new TextField();
nameField.value = group.groupName;
nameField.AddToClassList("list-name-field");
int idx = index;
nameField.RegisterValueChangedCallback(evt =>
{
Undo.RecordObject(target, "Rename Event Group");
controller.eventGroups[idx].groupName = evt.newValue;
EditorUtility.SetDirty(target);
});
headerRow.Add(nameField);
// Reorder buttons
var upBtn = new Button(() => SwapEvents(idx, idx - 1)) { text = "\u25B2" };
upBtn.AddToClassList("list-reorder-btn");
upBtn.SetEnabled(index > 0);
headerRow.Add(upBtn);
var downBtn = new Button(() => SwapEvents(idx, idx + 1)) { text = "\u25BC" };
downBtn.AddToClassList("list-reorder-btn");
downBtn.SetEnabled(index < controller.eventGroups.Count - 1);
headerRow.Add(downBtn);
var deleteBtn = new Button(() => DeleteEvent(idx)) { text = "X" };
deleteBtn.AddToClassList("list-delete-btn");
headerRow.Add(deleteBtn);
item.Add(headerRow);
// Fields - UnityEvent uses PropertyField for the built-in event drawer
var fields = new VisualElement();
fields.AddToClassList("list-fields");
var listProp = serializedObject.FindProperty("eventGroups");
var elementProp = listProp.GetArrayElementAtIndex(index);
var eventField = new PropertyField(elementProp.FindPropertyRelative("unityEvent"), "Event");
fields.Add(eventField);
item.Add(fields);
return item;
}
private void AddEventGroup()
{
Undo.RecordObject(target, "Add Event Group");
controller.eventGroups.Add(new EventController.EventGroup("New Event"));
EditorUtility.SetDirty(target);
serializedObject.Update();
RebuildEventList();
}
private void DeleteEvent(int index)
{
var group = controller.eventGroups[index];
if (EditorUtility.DisplayDialog("이벤트 삭제",
$"이벤트 '{group.groupName}'을(를) 삭제하시겠습니까?", "삭제", "취소"))
{
Undo.RecordObject(target, "Delete Event Group");
controller.eventGroups.RemoveAt(index);
EditorUtility.SetDirty(target);
serializedObject.Update();
RebuildEventList();
}
}
private void SwapEvents(int a, int b)
{
Undo.RecordObject(target, "Reorder Event Groups");
(controller.eventGroups[a], controller.eventGroups[b]) = (controller.eventGroups[b], controller.eventGroups[a]);
EditorUtility.SetDirty(target);
serializedObject.Update();
RebuildEventList();
}
#endregion
}