#nullable enable using System; using Cysharp.Threading.Tasks; using UnityEngine; using UnityEngine.UIElements; using UVC.UIToolkit; using UVC.UI.Commands; using UVC.Log; namespace UVC.Sample.UIToolkit { /// /// UTKMenu 샘플 코드 /// UIToolkit 기반 TopMenu 시스템을 테스트합니다. /// public class UTKMenuSample : MonoBehaviour { [SerializeField] private UIDocument? _uiDocument; [SerializeField] [Tooltip("시작 시 적용할 테마")] private UTKTheme initialTheme = UTKTheme.Dark; private UTKToggle? _themeToggle; private VisualElement? _root; private UTKTopMenu? _menuView; private UTKTopMenuModel? _menuModel; private UTKTopMenu? _menuView2; private UTKTopMenuModel? _menuModel2; private UTKTopMenu? _menuView3; private UTKTopMenuModel? _menuModel3; // 상태 관리용 private bool _canUndo = false; private bool _canRedo = false; private bool _isFileOpen = false; private void Start() { // UIDocument 참조 확인 var doc = GetComponent(); if (doc == null) { Debug.LogError("UIDocument가 할당되지 않았습니다."); return; } _uiDocument = doc; var toggle = _uiDocument.rootVisualElement.Q("toggle"); if (toggle == null) { Debug.LogError("UXML에서 UTKToggle을 찾을 수 없습니다."); return; } _themeToggle = toggle; UTKThemeManager.Instance.RegisterRoot(_uiDocument.rootVisualElement); UTKThemeManager.Instance.SetTheme(initialTheme); _themeToggle.OnValueChanged += (isOn) => { UTKThemeManager.Instance.SetTheme(!isOn ? UTKTheme.Dark : UTKTheme.Light); }; _root = _uiDocument.rootVisualElement; CreateSampleUI(); } private void CreateSampleUI() { if (_root == null) return; // 1. UTKTopMenuView 생성 _menuView = new UTKTopMenu(); _menuView.style.position = Position.Absolute; _menuView.style.top = 0; _menuView.style.left = 50; _menuView2 = new UTKTopMenu(); _menuView2.style.position = Position.Absolute; _menuView2.style.top = 0; _menuView2.style.left = 0; _menuView2.SubMenuOffsetX = 30; _menuView2.SubMenuOffsetY = -20; _root.Add(_menuView2); _root.Add(_menuView); // 세로 정렬 메뉴 (Vertical Orientation + ItemSpacing) _menuView3 = new UTKTopMenu(); _menuView3.Orientation = UTKMenuOrientation.Vertical; _menuView3.ItemSpacing = 4f; _menuView3.SubMenuOffsetX = -10f; _menuView3.SubMenuOffsetY = 4f; _menuView3.style.position = Position.Absolute; _menuView3.style.top = 50; _menuView3.style.left = 0; _menuView3.style.width = 120; _root.Add(_menuView3); // 2. UTKTopMenuModel 생성 및 메뉴 아이템 추가 _menuModel = new UTKTopMenuModel(); _menuModel2 = new UTKTopMenuModel(); _menuModel3 = new UTKTopMenuModel(); CreateMenuItems(); // 3. View에 메뉴 생성 if (_menuView.MenuContainer != null) { _menuView.CreateMenuItems(_menuModel.MenuItems, _menuView.MenuContainer); } if (_menuView2.MenuContainer != null) { _menuView2.CreateMenuItems(_menuModel2.MenuItems, _menuView2.MenuContainer); } if (_menuView3.MenuContainer != null) { _menuView3.CreateMenuItems(_menuModel3.MenuItems, _menuView3.MenuContainer); } // 4. 이벤트 구독 _menuView.OnMenuItemClicked += HandleMenuItemClicked; _menuView2.OnMenuItemClicked += HandleMenuItemClicked2; _menuView3.OnMenuItemClicked += HandleMenuItemClicked3; // 5. 상태 테스트 버튼 생성 CreateTestButtons(); ULog.Debug("UTKTopMenu 샘플 UI 생성 완료"); } /// /// 메뉴 아이템들을 생성합니다. /// private void CreateMenuItems() { if (_menuModel == null || _menuModel2 == null || _menuModel3 == null) return; // 파일 메뉴 var fileMenu = new UTKMenuItemData("file", "파일"); fileMenu.AddSubMenuItem(new UTKMenuItemData( "file_new", "새 파일", new DebugLogCommand("새 파일 생성"), shortcut: "Ctrl+N" )); fileMenu.AddSubMenuItem(new UTKMenuItemData( "file_open", "열기", new DebugLogCommand("파일 열기"), shortcut: "Ctrl+O" )); fileMenu.AddSubMenuItem(UTKMenuItemData.CreateSeparator()); fileMenu.AddSubMenuItem(new UTKMenuItemData( "file_save", "저장", new DebugLogCommand("파일 저장"), isEnabled: false, // 초기에는 비활성화 shortcut: "Ctrl+S" )); fileMenu.AddSubMenuItem(new UTKMenuItemData( "file_save_as", "다른 이름으로 저장", new DebugLogCommand("다른 이름으로 저장"), isEnabled: false, shortcut: "Ctrl+Shift+S" )); fileMenu.AddSubMenuItem(UTKMenuItemData.CreateSeparator()); fileMenu.AddSubMenuItem(new UTKMenuItemData( "file_exit", "종료", new DebugLogCommand("애플리케이션 종료"), shortcut: "Alt+F4" )); _menuModel.AddMenuItem(fileMenu); // 편집 메뉴 var editMenu = new UTKMenuItemData("edit", "편집"); editMenu.AddSubMenuItem(new UTKMenuItemData( "edit_undo", "실행 취소", new DebugLogCommand("실행 취소"), isEnabled: false, shortcut: "Ctrl+Z" )); editMenu.AddSubMenuItem(new UTKMenuItemData( "edit_redo", "다시 실행", new DebugLogCommand("다시 실행"), isEnabled: false, shortcut: "Ctrl+Y" )); editMenu.AddSubMenuItem(UTKMenuItemData.CreateSeparator()); editMenu.AddSubMenuItem(new UTKMenuItemData( "edit_cut", "잘라내기", new DebugLogCommand("잘라내기"), shortcut: "Ctrl+X" )); editMenu.AddSubMenuItem(new UTKMenuItemData( "edit_copy", "복사", new DebugLogCommand("복사"), shortcut: "Ctrl+C" )); editMenu.AddSubMenuItem(new UTKMenuItemData( "edit_paste", "붙여넣기", new DebugLogCommand("붙여넣기"), shortcut: "Ctrl+V" )); _menuModel.AddMenuItem(editMenu); // 보기 메뉴 (하위 메뉴 테스트) var viewMenu = new UTKMenuItemData("view", "보기"); var layoutMenu = new UTKMenuItemData("view_layout", "레이아웃"); layoutMenu.AddSubMenuItem(new UTKMenuItemData("view_layout_default", "기본", new DebugLogCommand("기본 레이아웃"))); layoutMenu.AddSubMenuItem(new UTKMenuItemData("view_layout_wide", "와이드", new DebugLogCommand("와이드 레이아웃"))); layoutMenu.AddSubMenuItem(new UTKMenuItemData("view_layout_compact", "컴팩트", new DebugLogCommand("컴팩트 레이아웃"))); viewMenu.AddSubMenuItem(layoutMenu); viewMenu.AddSubMenuItem(UTKMenuItemData.CreateSeparator()); viewMenu.AddSubMenuItem(new UTKMenuItemData("view_fullscreen", "전체 화면", new DebugLogCommand("전체 화면 전환"), shortcut: "F11")); _menuModel.AddMenuItem(viewMenu); // 도구 메뉴 var toolsMenu = new UTKMenuItemData("tools", "도구"); toolsMenu.AddSubMenuItem(new UTKMenuItemData("tools_options", "옵션", new DebugLogCommand("옵션 열기"))); toolsMenu.AddSubMenuItem(new UTKMenuItemData("tools_settings", "설정", new DebugLogCommand("설정 열기"))); _menuModel.AddMenuItem(toolsMenu); // 도움말 메뉴 var helpMenu = new UTKMenuItemData("help", "도움말"); helpMenu.AddSubMenuItem(new UTKMenuItemData("help_documentation", "문서", new DebugLogCommand("문서 열기"), shortcut: "F1")); helpMenu.AddSubMenuItem(UTKMenuItemData.CreateSeparator()); helpMenu.AddSubMenuItem(new UTKMenuItemData("help_about", "정보", new DebugLogCommand("정보 표시"))); _menuModel.AddMenuItem(helpMenu); // 이미지 메뉴 (4 depth 테스트) var imageMenu = new UTKMenuImageItemData( "image_menu", UTKMaterialIcons.Settings, // Material Icon 사용 useMaterialIcon: true, imageSize: 24f ); // Depth 1 아이템 var depth1Item = new UTKMenuItemData("depth1", "레벨 1 메뉴"); var depth1Item1 = new UTKMenuItemData("depth11", "레벨 1 1메뉴"); var depth1Item2 = new UTKMenuItemData("depth12", "레벨 1 2메뉴"); var depth1Item3 = new UTKMenuItemData("depth13", "레벨 1 3메뉴"); // Depth 2 아이템 var depth2Item = new UTKMenuItemData("depth2", "레벨 2 메뉴"); var depth2Item1 = new UTKMenuItemData("depth21", "레벨 2 1 메뉴"); var depth2Item2 = new UTKMenuItemData("depth22", "레벨 2 2 메뉴"); var depth2Item3 = new UTKMenuItemData("depth23", "레벨 2 3 메뉴"); // Depth 3 아이템 var depth3Item = new UTKMenuItemData("depth3", "레벨 3 메뉴"); // Depth 4 아이템들 (실제 액션) var depth4Action1 = new UTKMenuItemData("depth4_action1", "액션 1", new DebugLogCommand("4 Depth 액션 1 실행")); var depth4Action2 = new UTKMenuItemData("depth4_action2", "액션 2", new DebugLogCommand("4 Depth 액션 2 실행")); var depth4Action3 = new UTKMenuItemData("depth4_action3", "액션 3", new DebugLogCommand("4 Depth 액션 3 실행")); // 계층 구조 구성 (역순으로) depth3Item.AddSubMenuItem(depth4Action1); depth3Item.AddSubMenuItem(depth4Action2); depth3Item.AddSubMenuItem(depth4Action3); depth2Item.AddSubMenuItem(depth3Item); depth1Item.AddSubMenuItem(depth2Item); depth1Item.AddSubMenuItem(depth2Item1); depth1Item.AddSubMenuItem(depth2Item2); depth1Item.AddSubMenuItem(depth2Item3); imageMenu.AddSubMenuItem(depth1Item1); imageMenu.AddSubMenuItem(depth1Item2); imageMenu.AddSubMenuItem(depth1Item3); imageMenu.AddSubMenuItem(depth1Item); _menuModel2.AddMenuItem(imageMenu); // === 세로 메뉴 (menuView3) === var vertFileMenu = new UTKMenuItemData("v_file", "파일"); vertFileMenu.AddSubMenuItem(new UTKMenuItemData("v_file_new", "새 파일", new DebugLogCommand("세로 메뉴: 새 파일"), shortcut: "Ctrl+N")); vertFileMenu.AddSubMenuItem(new UTKMenuItemData("v_file_open", "열기", new DebugLogCommand("세로 메뉴: 열기"), shortcut: "Ctrl+O")); vertFileMenu.AddSubMenuItem(UTKMenuItemData.CreateSeparator()); vertFileMenu.AddSubMenuItem(new UTKMenuItemData("v_file_save", "저장", new DebugLogCommand("세로 메뉴: 저장"), shortcut: "Ctrl+S")); _menuModel3.AddMenuItem(vertFileMenu); var vertEditMenu = new UTKMenuItemData("v_edit", "편집"); vertEditMenu.AddSubMenuItem(new UTKMenuItemData("v_edit_undo", "실행 취소", new DebugLogCommand("세로 메뉴: 실행 취소"), shortcut: "Ctrl+Z")); vertEditMenu.AddSubMenuItem(new UTKMenuItemData("v_edit_redo", "다시 실행", new DebugLogCommand("세로 메뉴: 다시 실행"), shortcut: "Ctrl+Y")); _menuModel3.AddMenuItem(vertEditMenu); var vertViewMenu = new UTKMenuItemData("v_view", "보기"); vertViewMenu.AddSubMenuItem(new UTKMenuItemData("v_view_fullscreen", "전체 화면", new DebugLogCommand("세로 메뉴: 전체 화면"), shortcut: "F11")); _menuModel3.AddMenuItem(vertViewMenu); var vertHelpMenu = new UTKMenuItemData("v_help", "도움말"); vertHelpMenu.AddSubMenuItem(new UTKMenuItemData("v_help_about", "정보", new DebugLogCommand("세로 메뉴: 정보"))); _menuModel3.AddMenuItem(vertHelpMenu); ULog.Debug($"메뉴 아이템 생성 완료: {_menuModel.MenuItems.Count}개 최상위 메뉴, 세로 메뉴: {_menuModel3.MenuItems.Count}개"); } /// /// 테스트 버튼들을 생성합니다. /// private void CreateTestButtons() { if (_root == null) return; // 테스트 버튼 컨테이너 var buttonContainer = new VisualElement(); buttonContainer.style.position = Position.Absolute; buttonContainer.style.top = 60; buttonContainer.style.left = 300; buttonContainer.style.flexDirection = FlexDirection.Column; buttonContainer.style.width = 250; _root.Insert(0, buttonContainer); // 타이틀 var title = new Label("메뉴 상태 테스트"); title.style.fontSize = 16; title.style.unityFontStyleAndWeight = FontStyle.Bold; title.style.marginBottom = 10; buttonContainer.Add(title); // 파일 열기/닫기 버튼 var fileToggleBtn = new Button(() => ToggleFileOpen()) { text = "파일 열기 (Save 활성화)" }; fileToggleBtn.style.marginBottom = 5; buttonContainer.Add(fileToggleBtn); // Undo/Redo 토글 버튼 var undoToggleBtn = new Button(() => ToggleUndoRedo()) { text = "Undo/Redo 활성화" }; undoToggleBtn.style.marginBottom = 5; buttonContainer.Add(undoToggleBtn); // 모든 하위 메뉴 닫기 var closeAllBtn = new Button(() => _menuView?.CloseAllOpenSubMenus()) { text = "모든 하위 메뉴 닫기" }; closeAllBtn.style.marginBottom = 5; buttonContainer.Add(closeAllBtn); // 메뉴 아이템 추가 var addMenuBtn = new Button(() => AddDynamicMenu()) { text = "동적 메뉴 추가" }; addMenuBtn.style.marginBottom = 5; buttonContainer.Add(addMenuBtn); // 메뉴 아이템 제거 var removeMenuBtn = new Button(() => RemoveDynamicMenu()) { text = "동적 메뉴 제거" }; removeMenuBtn.style.marginBottom = 5; buttonContainer.Add(removeMenuBtn); // 단축키 변경 var changeShortcutBtn = new Button(() => ChangeShortcut()) { text = "Save 단축키 변경" }; changeShortcutBtn.style.marginBottom = 5; buttonContainer.Add(changeShortcutBtn); // ExecuteCommand 테스트 var executeCommandBtn = new Button(() => TestExecuteCommand()) { text = "ExecuteCommand 테스트" }; executeCommandBtn.style.marginBottom = 5; buttonContainer.Add(executeCommandBtn); // TryGetMenuItemData 테스트 var getDataBtn = new Button(() => TestTryGetMenuItemData()) { text = "TryGetMenuItemData 테스트" }; getDataBtn.style.marginBottom = 5; buttonContainer.Add(getDataBtn); // 상태 표시 레이블 var statusLabel = new Label(); statusLabel.style.marginTop = 20; statusLabel.style.fontSize = 12; statusLabel.style.color = new Color(0.7f, 0.7f, 0.7f); buttonContainer.Add(statusLabel); UpdateStatusLabel(statusLabel); } /// /// 파일 열기/닫기 상태를 토글합니다. /// private void ToggleFileOpen() { _isFileOpen = !_isFileOpen; var saveItem = _menuModel?.FindMenuItem("file_save"); var saveAsItem = _menuModel?.FindMenuItem("file_save_as"); if (saveItem != null) { saveItem.IsEnabled = _isFileOpen; if (_menuView != null && _menuView.TryGetMenuItemElement("file_save", out var element)) { var button = element.Q