#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