modal 개발 중

This commit is contained in:
logonkhi
2025-06-12 19:25:33 +09:00
parent 466dbdbcad
commit e8d52b3e90
434 changed files with 822421 additions and 451 deletions

View File

@@ -1,131 +1,254 @@
using UVC.UI.Menu;
using System.Collections.Generic;
using UVC.UI.Commands; // DebugLogCommand 등 기본 Command 사용 시
// using SampleProject.UI.Commands; // 프로젝트별 Command 사용 시
using System.Collections.Generic;
using UVC.Log; // 필요에 따라 UVC.Log 또는 프로젝트별 로깅 시스템 사용
using System;
using UVC.UI.Commands;
using UVC.UI.Menu;
namespace SampleProject.UI.Menu
{
/// <summary>
/// <see cref="TopMenuController"/>를 상속받아 특정 프로젝트(예: SampleProject)에 맞게
/// 상단 메뉴의 동작을 커스터마이징하는 컨트롤러 클래스입니다.
/// 이 클래스는 프로젝트 고유의 메뉴 구조를 정의하고, 특정 View(<see cref="SampleProjectTopMenuView"/>)와 상호작용하며,
/// 메뉴 아이템 클릭 시 프로젝트별 추가 로직을 수행할 수 있습니다.
/// </summary>
/// <remarks>
/// 이 컨트롤러는 <see cref="SampleProjectTopMenuView"/>와 함께 사용되도록 설계되었습니다.
/// 만약 <see cref="SampleProjectTopMenuView"/>를 찾지 못하면, 부모 클래스의 로직에 따라
/// 기본 <see cref="TopMenuView"/>를 사용하려고 시도할 수 있습니다.
/// </remarks>
public class SampleProjectTopMenuController : TopMenuController
{
protected override void Awake()
{
// 1. 동일한 GameObject에 SampleProjectTopMenuView 컴포넌트 검색
// 1. GameObject에 연결된 SampleProjectTopMenuView 컴포넌트를 찾습니다.
// view 필드는 부모 클래스 TopMenuController에 protected TopMenuView view; 로 선언되어 있습니다.
// SampleProjectTopMenuView는 TopMenuView를 상속해야 이 할당이 유효합니다.
view = GetComponent<SampleProjectTopMenuView>();
// 2. 동일한 GameObject에 없다면, 자식 GameObject에서 검색
// 2. 만약 현재 GameObject에 없다면, 자식 GameObject들 중에서 SampleProjectTopMenuView 컴포넌트를 찾습니다.
if (view == null)
{
view = GetComponentInChildren<SampleProjectTopMenuView>();
}
// view가 여전히 null이라면, 부모 타입(TopMenuView)으로 다시 한번 검색 (선택 사항)
// 3. 위 두 단계에서 SampleProjectTopMenuView를 찾지 못한 경우 (view가 여전히 null인 경우),
// 부모 클래스의 Awake()를 호출합니다. 이는 부모 클래스가 정의한 방식대로
// (예: 기본 TopMenuView 타입으로) View를 찾도록 시도합니다.
// 이것은 SampleProjectTopMenuView가 필수는 아니지만 권장되는 상황을 위한 대비책일 수 있습니다.
if (view == null)
{
ULog.Warning("SampleProjectTopMenuView를 찾을 수 없어 TopMenuView로 검색합니다.");
base.Awake(); // 부모의 Awake를 호출하여 TopMenuView를 찾도록 시도
ULog.Warning("SampleProjectTopMenuView를 찾을 수 없어, 부모 클래스의 Awake()를 통해 TopMenuView로 검색을 시도합니다.");
base.Awake(); // 부모 클래스(TopMenuController)의 Awake 로직 실행
}
}
/// <summary>
/// MonoBehaviour의 Start 메시지입니다.
/// 이 프로젝트(<c>SampleProject</c>)에 맞는 메뉴 시스템을 초기화합니다.
/// 모델 생성, <see cref="LocalizationManager"/> 인스턴스 설정, 메뉴 아이템 데이터 정의,
/// View에 메뉴 UI 생성 요청, 그리고 필요한 이벤트 핸들러들을 등록합니다.
/// </summary>
/// <remarks>
/// 이 메서드는 부모 클래스 <see cref="TopMenuController"/>의 <c>Start()</c> 메서드를 호출하지 않고,
/// 메뉴 구성을 완전히 새로 정의합니다. 만약 부모의 <c>Start()</c> 로직 일부를 재사용하고 싶다면,
/// <c>base.Start()</c>를 적절한 위치에 호출하고, 필요한 부분만 수정해야 합니다.
/// </remarks>
protected override void Start()
{
// model과 _locManager는 부모 클래스에 protected로 선언되어 접근 가능
// 만약 부모의 Start 로직을 완전히 대체하고 싶지 않다면 base.Start()를 호출할 수 있습니다.
// 여기서는 메뉴 아이템 구성을 완전히 새로 하므로 base.Start()를 호출하지 않거나,
// 호출 후 model.MenuItems.Clear()를 다시 수행합니다.
// 모델(model)지역화 관리자(_locManager) 필드는 부모 클래스에 protected로 선언되어 있어 직접 접근 가능합니다.
model = new TopMenuModel(); // 새 모델 인스턴스 또는 프로젝트별 모델 사용
_locManager = UVC.Locale.LocalizationManager.Instance; // LocalizationManager 인스턴스 가져오기
// 1. 메뉴 데이터 모델 인스턴스 생성
// (새 TopMenuModel을 사용하거나, 이 프로젝트를 위한 커스텀 모델이 있다면 그것을 사용합니다.)
model = new TopMenuModel();
// 2. 다국어 처리(Localization)를 위한 LocalizationManager 인스턴스 가져오기
_locManager = UVC.Locale.LocalizationManager.Instance;
// 3. View 컴포넌트 유효성 검사 (Awake에서 할당 시도)
if (view == null)
{
ULog.Error("SampleProjectTopMenuView가 Inspector에서 할당되지 않았거나 찾을 수 없습니다.");
return;
ULog.Error("View 컴포넌트(SampleProjectTopMenuView 또는 TopMenuView)가 Inspector에서 할당되지 않았거나 찾을 수 없습니다. 메뉴가 표시되지 않습니다.");
return; // View가 없으면 메뉴 시스템을 초기화할 수 없음
}
// 4. LocalizationManager 인스턴스 유효성 검사
if (_locManager == null)
{
ULog.Error("LocalizationManager 인스턴스를 찾을 수 없습니다. 메뉴 텍스트가 올바르게 표시되지 않을 수 있습니다.");
// _locManager가 없어도 메뉴 자체는 표시될 수 있지만, 텍스트가 키 값으로 나오거나 언어 변경이 안 될 수 있습니다.
ULog.Error("LocalizationManager 인스턴스를 찾을 수 없습니다. 메뉴 텍스트가 올바르게 표시되지 않거나 언어 변경 기능이 작동하지 않을 수 있습니다.");
}
InitializeMenuItems(); // 프로젝트별 메뉴 아이템 초기화 메서드 호출
// 5. 이 프로젝트에 특화된 메뉴 아이템들로 모델을 채웁니다.
InitializeMenuItems(); // 아래에 정의된 프로젝트별 메뉴 초기화 메서드 호출
// 6. View에 기존 메뉴 아이템 UI를 모두 제거하도록 요청
view.ClearMenuItems();
// 7. View에 현재 모델 데이터를 기반으로 새로운 메뉴 UI를 생성하도록 요청
// view.MenuContainer는 View 내에서 메뉴 아이템들이 배치될 부모 Transform을 가리킵니다.
view.CreateMenuItems(model.MenuItems, view.MenuContainer);
view.OnMenuItemClicked += HandleMenuItemClicked; // 부모의 핸들러 재사용 또는 override
// 8. View에서 발생하는 이벤트들에 대한 핸들러(메서드)를 등록합니다.
// 부모 클래스(TopMenuController)에 정의된 핸들러를 재사용하거나,
// 이 클래스에서 override하여 프로젝트별 로직을 추가할 수 있습니다.
view.OnMenuItemClicked += HandleMenuItemClicked; // 메뉴 아이템 클릭 이벤트 처리
if (_locManager != null)
{
_locManager.OnLanguageChanged += HandleLanguageChanged; // 부모의 핸들러 재사용 또는 override
_locManager.OnLanguageChanged += HandleLanguageChanged; // 언어 변경 이벤트 처리
}
ULog.Debug("SampleProjectTopMenuController Start 실행됨");
ULog.Debug("SampleProjectTopMenuController의 Start 메서드가 실행 완료되었습니다.");
}
// 부모의 OnDestroy를 그대로 사용하거나, 필요시 override하여 로직 추가/변경
// protected new void OnDestroy()
// 부모 클래스(TopMenuController)의 OnDestroy 메서드를 그대로 사용합니다.
// 만약 이 프로젝트에서만 특별히 해제해야 할 리소스가 있다면,
// OnDestroy를 override하고 base.OnDestroy()를 호출한 후 추가 로직을 작성할 수 있습니다.
// 예시:
// protected override void OnDestroy()
// {
// base.OnDestroy();
// // 프로젝트별 리소스 해제 로직 추가
// base.OnDestroy(); // 부모 클래스의 리소스 해제 로직 실행 (이벤트 핸들러 해제 등)
// ULog.Debug("[SampleProject] OnDestroy 호출됨. 프로젝트별 리소스 해제 로직 수행 가능.");
// // 여기에 SampleProjectTopMenuController에서만 할당한 리소스가 있다면 해제합니다.
// }
/// <summary>
/// 프로젝트에 특화된 메뉴 아이템들을 초기화합니다.
/// TopMenuControllerInitializeMenuItems를 override하거나,
/// 이처럼 새로운 메서드를 만들어 사용할 수 있습니다.
/// <c>SampleProject</c>에 특화된 메뉴 아이템들을 정의하고 모델(<see cref="model"/>)에 추가합니다.
/// 이 메서드는 <see cref="TopMenuController.InitializeMenuItems"/>를 오버라이드하여
/// 프로젝트 고유의 메뉴 구조를 설정합니다.
/// </summary>
protected override void InitializeMenuItems()
{
model.MenuItems.Clear(); // 기존 메뉴 아이템 제거
model.MenuItems.Clear(); // 메뉴를 새로 구성하므로, 기존에 있을 수 있는 아이템들을 모두 제거합니다.
// 예시: "파일" 메뉴는 유지하되, "새 프로젝트" 제거 및 "프로젝트 설정" 추가
model.MenuItems.Add(new MenuItemData("file", "menu_file", subMenuItems: new List<MenuItemData>
// '메인 메뉴' (가상) 아래에 '파일', '편집', '도움말', '언어' 등의 주 메뉴를 구성합니다.
// 실제 UI에서는 'menu'라는 최상위 아이템 없이 바로 '파일', '편집' 등이 표시될 수 있습니다.
// 여기서는 구조화를 위해 하나의 루트 아이템 아래에 다른 메뉴들을 추가하는 형태로 구성합니다.
// 만약 최상위 메뉴가 여러 개 병렬로 존재해야 한다면, model.MenuItems.Add()를 여러 번 호출하면 됩니다.
// 이 예제에서는 하나의 "menu"라는 논리적 그룹 아래 모든 것을 배치합니다.
// 실제 표시 방식은 TopMenuView의 CreateMenuItems 로직에 따라 달라집니다.
// 현재 TopMenuController의 InitializeMenuItems는 여러 최상위 메뉴를 직접 model.MenuItems에 추가하므로,
// 그 방식을 따르려면 아래의 "menu" 그룹 없이 바로 model.MenuItems.Add(new MenuItemData("file", ...)) 등을 호출합니다.
// 여기서는 TopMenuController와 유사하게 직접 최상위 메뉴들을 추가하는 방식으로 수정합니다.
model.MenuItems.Add(new MenuItemData("menu", "menu", new DebugLogCommand("[SampleProject] 프로젝트 설정 선택됨"), subMenuItems: new List<MenuItemData>
{
new MenuItemData("file_new_file", "menu_file_new_file", new DebugLogCommand("[SampleProject] 새 파일 선택됨")),
new MenuItemData("file_open", "menu_file_open", new DebugLogCommand("[SampleProject] 파일 열기 선택됨")),
MenuItemData.CreateSeparator("file_sep_sample1"),
new MenuItemData("project_settings", "project_settings_key", new DebugLogCommand("[SampleProject] 프로젝트 설정 선택됨")), // 프로젝트별 메뉴 아이템
MenuItemData.CreateSeparator("file_sep_sample2"),
new MenuItemData("file_exit", "menu_file_exit", new QuitApplicationCommand())
}));
// "편집" 메뉴는 그대로 사용 (부모 클래스의 메뉴 아이템 구성을 재활용하거나 여기서 다시 정의)
model.MenuItems.Add(new MenuItemData("edit", "menu_edit", subMenuItems: new List<MenuItemData>
{
new MenuItemData("edit_undo", "menu_edit_undo", new DebugLogCommand("[SampleProject] 실행 취소 선택됨")),
new MenuItemData("edit_redo", "menu_edit_redo", new DebugLogCommand("[SampleProject] 다시 실행 선택됨"))
}));
// "도움말" 메뉴 추가 (프로젝트별)
model.MenuItems.Add(new MenuItemData("help", "menu_help_key", subMenuItems: new List<MenuItemData>
{
new MenuItemData("help_about", "menu_about_key", new DebugLogCommand("[SampleProject] 프로그램 정보 선택됨"))
}));
if (_locManager != null)
{
// 언어 메뉴는 부모와 동일하게 유지하거나, 프로젝트별로 변경 가능
model.MenuItems.Add(new MenuItemData("language", "menu_language", subMenuItems: new List<MenuItemData>
new MenuItemData("file", "menu_file", subMenuItems: new List<MenuItemData>
{
new MenuItemData("lang_ko", "menu_lang_korean", new ChangeLanguageCommand("ko-KR", _locManager)),
new MenuItemData("lang_en", "menu_lang_english", new ChangeLanguageCommand("en-US", _locManager)),
// new MenuItemData("lang_jp", "menu_lang_japanese", new ChangeLanguageCommand("ja-JP", _locManager)) // 예: 일본어 추가
}));
}
else
{
ULog.Warning("[SampleProject] LocalizationManager가 null이므로 언어 변경 메뉴를 초기화할 수 없습니다.");
}
new MenuItemData("file_new_file", "menu_file_new_file", new DebugLogCommand("[SampleProject] 새 파일 선택됨")),
// "파일 열기" 메뉴 아이템 생성 시 isEnabled: false로 설정하여 비활성화 상태로 초기화합니다.
// 부모 TopMenuController의 HandleMenuItemClicked에서 이 상태를 확인하여 클릭을 무시합니다.
new MenuItemData("file_open", "menu_file_open", new DebugLogCommand("[SampleProject] 파일 열기 선택됨"), isEnabled: false),
MenuItemData.CreateSeparator("file_sep_sample1"),
new MenuItemData("project_settings", "project_settings", new DebugLogCommand("[SampleProject] 프로젝트 설정 선택됨")), // 프로젝트별 메뉴 아이템
MenuItemData.CreateSeparator("file_sep_sample2"),
new MenuItemData("file_exit", "menu_file_exit", new QuitApplicationCommand())
}),
new MenuItemData("edit", "menu_edit", subMenuItems: new List<MenuItemData>
{
new MenuItemData("edit_undo", "menu_edit_undo", new DebugLogCommand("[SampleProject] 실행 취소 선택됨")),
new MenuItemData("edit_redo", "menu_edit_redo", new DebugLogCommand("[SampleProject] 다시 실행 선택됨"))
}),
new MenuItemData("help", "menu_help", subMenuItems: new List<MenuItemData>
{
new MenuItemData("help_about", "menu_about", new DebugLogCommand("[SampleProject] 프로그램 정보 선택됨"))
}),
new MenuItemData("language", "menu_language", subMenuItems: new List<MenuItemData>
{
new MenuItemData("lang_ko", "menu_lang_korean", new ChangeLanguageCommand("ko-KR")),
new MenuItemData("lang_en", "menu_lang_english", new ChangeLanguageCommand("en-US")),
// new MenuItemData("lang_jp", "menu_lang_japanese", new ChangeLanguageCommand("ja-JP")) // 예: 일본어 추가
})
}));
// 특정 조건에 따라 메뉴 아이템의 활성화 상태를 동적으로 변경하는 예시입니다.
// 예를 들어, 특정 기능이 사용 가능한 상태일 때만 관련 메뉴를 활성화할 수 있습니다.
// bool isProjectLoaded = CheckProjectLoadedStatus(); // 실제 프로젝트 로드 상태를 확인하는 가상 메서드
// if (isProjectLoaded)
// {
// // SetMenuItemEnabled 메서드는 부모 클래스 TopMenuController로부터 상속받아 사용합니다.
// // 이 메서드는 메뉴 모델의 IsEnabled 값을 변경하고, View의 해당 버튼 UI 상태(interactable)도 업데이트합니다.
// SetMenuItemEnabled("project_settings", true);
// ULog.Debug("[SampleProject] '프로젝트 설정' 메뉴가 활성화되었습니다 (조건부 로직).");
// }
}
// HandleMenuItemClicked와 HandleLanguageChanged는 필요에 따라 override하여
// 프로젝트별 특별한 처리를 추가할 수 있습니다.
// 예시:
// HandleMenuItemClicked와 HandleLanguageChanged 메서드
// 부모 클래스(TopMenuController)에 이미 구현된 기본적인 처리 로직을 사용합니다.
// (메뉴 아이템의 IsEnabled 상태 체크, Command 실행, 메뉴 텍스트 업데이트 등)
// 만약 이 프로젝트에서만 특별히 추가하거나 변경해야 할 동작이 있다면,
// 아래와 같이 해당 메서드들을 override하여 커스터마이징할 수 있습니다.
// 예시: 메뉴 아이템 클릭 시 추가적인 프로젝트별 로직 수행
// protected override void HandleMenuItemClicked(MenuItemData clickedItemData)
// {
// base.HandleMenuItemClicked(clickedItemData); // 부모 로직 실행
// ULog.Debug($"[SampleProject] 메뉴 아이템 클릭됨: {clickedItemData.ItemId}");
// // 프로젝트별 추가 클릭 처리 로직
// // 1. 부모 클래스의 HandleMenuItemClicked를 호출하여 기본적인 처리(IsEnabled 체크, Command 실행 등)를 수행합니다.
// base.HandleMenuItemClicked(clickedItemData);
//
// // 2. 부모 로직 실행 후, 이 프로젝트에 필요한 추가적인 동작을 수행합니다.
// // 주의: base.HandleMenuItemClicked 내부에서 IsEnabled가 false이면 Command가 실행되지 않고 반환될 수 있습니다.
// // 따라서, 여기서도 IsEnabled를 다시 한번 확인하거나, Command가 실제로 실행되었는지 여부를 알 수 있는 방법이 필요할 수 있습니다.
// // (예: base.HandleMenuItemClicked가 bool 값을 반환하도록 수정)
// // 현재는 부모 메서드가 void이므로, 아래 로직은 부모 메서드 실행 후 항상 시도됩니다.
// if (clickedItemData.IsEnabled && !clickedItemData.IsSeparator) // 활성화된 실제 메뉴 아이템에 대해서만
// {
// ULog.Debug($"[SampleProject] 메뉴 아이템 '{clickedItemData.ItemId}' 클릭됨 (SampleProjectController에서 추가 처리)");
//
// // 프로젝트별 추가 클릭 처리 로직 예시
// if (clickedItemData.ItemId == "project_settings")
// {
// // OpenProjectSpecificSettingsWindow(); // 프로젝트 설정 창을 여는 메서드 호출 등
// ULog.Debug("[SampleProject] '프로젝트 설정' 메뉴에 대한 특별한 동작 수행!");
// }
// }
// }
// 예시: 언어 변경 시 추가적인 프로젝트별 UI 업데이트 수행
// protected override void HandleLanguageChanged(string newLanguageCode)
// {
// // 1. 부모 클래스의 HandleLanguageChanged를 호출하여 기본 메뉴 텍스트 업데이트를 수행합니다.
// base.HandleLanguageChanged(newLanguageCode);
//
// // 2. 이 프로젝트에 필요한 추가적인 언어 변경 관련 동작을 수행합니다.
// ULog.Debug($"[SampleProject] 언어가 '{newLanguageCode}'(으)로 변경됨 (SampleProjectController에서 추가 처리)");
// // 예: 메뉴 외 다른 UI 요소들의 텍스트도 업데이트
// // UpdateOtherProjectSpecificUITexts(newLanguageCode);
// }
// --- 아래는 메뉴 아이템의 활성화 상태를 외부에서 제어하는 예시 메서드들입니다. ---
// (주석 처리되어 있으며, 필요시 주석을 해제하고 실제 로직을 구현하여 사용할 수 있습니다.)
/// <summary>
/// (예시 메서드) 프로젝트 설정 메뉴의 접근 가능성(활성화 상태)을 업데이트합니다.
/// 예를 들어, 프로젝트가 로드되었을 때만 "프로젝트 설정" 메뉴를 활성화할 수 있습니다.
/// </summary>
/// <param name="projectLoaded">프로젝트가 로드되었는지 여부입니다.</param>
// public void UpdateProjectSettingsMenuAccess(bool projectLoaded)
// {
// // "project_settings"라는 ID를 가진 메뉴 아이템의 활성화 상태를 변경합니다.
// // SetMenuItemEnabled 메서드는 부모 클래스 TopMenuController에 정의되어 있으며,
// // 모델 데이터 변경 및 UI(버튼의 interactable 속성) 업데이트를 모두 처리합니다.
// SetMenuItemEnabled("project_settings", projectLoaded);
//
// if (projectLoaded)
// {
// ULog.Debug("[SampleProject] '프로젝트 설정' 메뉴가 활성화되었습니다.");
// }
// else
// {
// ULog.Debug("[SampleProject] '프로젝트 설정' 메뉴가 비활성화되었습니다.");
// }
// }
/// <summary>
/// (예시 메서드) 실제 프로젝트 로드 상태를 확인하는 로직입니다.
/// </summary>
/// <returns>프로젝트가 로드되었으면 true, 아니면 false를 반환합니다.</returns>
// private bool CheckProjectLoadedStatus()
// {
// // 여기에 실제 프로젝트 로드 상태를 확인하는 코드를 구현합니다.
// // 예: return ProjectManager.Instance.IsProjectCurrentlyLoaded;
// return true; // 이 예시에서는 항상 true를 반환하도록 되어 있습니다. 실제 구현 필요.
// }
// --- 예시 코드 끝 ---
}
}

View File

@@ -1,57 +1,267 @@
using UVC.UI.Menu;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UVC.Log; // 필요에 따라 UVC.Log 또는 프로젝트별 로깅 시스템 사용
using UVC.UI.Menu;
namespace SampleProject.UI.Menu
{
/// <summary>
/// <see cref="TopMenuView"/>를 상속받아 특정 프로젝트(이 경우 <c>SampleProject</c>)에 맞게
/// 상단 메뉴의 시각적 표현과 일부 동작을 커스터마이징하는 View 클래스입니다.
/// 이 클래스는 프로젝트 고유의 메뉴 아이템 프리팹을 사용하도록 지정하거나,
/// 메뉴 아이템의 레이아웃, 스타일(예: 비활성화 상태 표시) 등을 프로젝트의 요구사항에 맞게 조정하는 데 사용됩니다.
/// 주로 <see cref="SampleProjectTopMenuController"/>와 함께 작동하여 메뉴 시스템을 구성합니다.
/// </summary>
/// <remarks>
/// 이 클래스는 <see cref="TopMenuView"/>의 유연성을 활용하여,
/// 핵심 메뉴 생성 및 관리 로직은 부모 클래스로부터 재사용하면서
/// UI/UX의 세부적인 부분을 프로젝트 특성에 맞게 차별화할 수 있도록 설계되었습니다.
/// </remarks>
/// <example>
/// 다음은 <c>SampleProjectTopMenuView</c>를 더욱 확장하여,
/// 게임의 특정 테마나 상태(예: "고대비 모드")에 따라 메뉴의 모양을
/// 더욱 세밀하게 제어하는 View 클래스를 만드는 가상적인 예제입니다.
/// <code>
/// public class ThemedSampleProjectTopMenuView : SampleProjectTopMenuView
/// {
/// // 가상의 현재 애플리케이션 테마 상태 (실제로는 별도의 테마 관리 시스템을 통해 관리될 수 있음)
/// private bool useHighContrastTheme = false;
///
/// // 예시: 현재 테마에 따라 다른 메뉴 아이템 프리팹 경로를 반환하도록 오버라이드
/// protected override string MenuItemPrefabPath
/// {
/// get
/// {
/// if (useHighContrastTheme)
/// {
/// // 고대비 테마용 특별한 메뉴 아이템 프리팹 경로
/// return "Prefabs/UI/Menu/SampleProjectMenuItem_HighContrast";
/// }
/// // 기본 SampleProject 메뉴 아이템 프리팹 경로 사용 (부모 클래스의 값)
/// return base.MenuItemPrefabPath;
/// }
/// }
///
/// protected override void Awake()
/// {
/// base.Awake(); // 부모 클래스(SampleProjectTopMenuView)의 Awake 로직 실행
/// ULog.Debug("ThemedSampleProjectTopMenuView Awake: 테마에 맞는 설정 적용 준비됨.");
///
/// // 여기에 테마별 추가 초기화 로직을 넣을 수 있습니다.
/// // 예: 현재 테마 상태(useHighContrastTheme)를 외부 설정이나 이벤트로부터 받아옴
/// // useHighContrastTheme = ApplicationThemeManager.IsHighContrastEnabled; // 가상의 테마 관리자
/// }
///
/// // 메뉴 아이템 생성 후, 테마에 맞는 추가 스타일링 적용 예시
/// public override Vector2 CreateMenuItems(List&lt;MenuItemData&gt; items, Transform parentContainer, int depth = 0)
/// {
/// // 부모 클래스(SampleProjectTopMenuView)의 메뉴 생성 로직을 먼저 실행
/// Vector2 calculatedSize = base.CreateMenuItems(items, parentContainer, depth);
///
/// // 생성된 메뉴 아이템들에 대해 테마별 추가 스타일링 적용
/// if (useHighContrastTheme)
/// {
/// foreach (MenuItemData itemData in items)
/// {
/// if (itemData.IsSeparator) continue;
///
/// if (_menuItemObjects.TryGetValue(itemData.ItemId, out GameObject menuItemInstance))
/// {
/// TextMeshProUGUI buttonText = menuItemInstance.GetComponentInChildren&lt;TextMeshProUGUI&gt;(true);
/// if (buttonText != null)
/// {
/// // 예시: 고대비 테마일 경우 텍스트 색상을 밝은 노란색으로 변경
/// // buttonText.color = Color.yellow;
/// }
/// // Image background = menuItemInstance.GetComponent&lt;Image&gt;();
/// // if (background != null) background.color = Color.black; // 배경을 검게
/// }
/// }
/// ULog.Debug("ThemedSampleProjectTopMenuView: 고대비 테마 스타일이 적용되었습니다.");
/// }
/// return calculatedSize;
/// }
///
/// // 외부에서 테마 변경을 알리고 메뉴를 다시 그리도록 하는 메서드 (예시)
/// public void ApplyTheme(bool highContrast)
/// {
/// useHighContrastTheme = highContrast;
/// ULog.Debug($"ThemedSampleProjectTopMenuView: 테마 변경됨 (고대비: {useHighContrastTheme}). 메뉴 UI를 업데이트합니다.");
/// // 메뉴를 다시 그리려면 Controller를 통해 Model 데이터를 받아와서 ClearMenuItems 후 CreateMenuItems를 다시 호출해야 함.
/// // 이 View 클래스 단독으로는 전체 메뉴를 다시 그리는 로직을 직접 트리거하기 어려울 수 있음.
/// // 보통 Controller가 이러한 상태 변경을 감지하고 View 업데이트를 지시함.
/// }
/// }
/// </code>
/// </example>
public class SampleProjectTopMenuView : TopMenuView
{
// 프로젝트별 MenuItem 프리팹 경로 (덮어쓰기 예시)
// private new const string MenuItemPrefabPath = "Prefabs/SampleProject/UI/Menu/SampleMenuItem";
// 프로젝트별 MenuSeparator 프리팹 경로 (덮어쓰기 예시)
/// <summary>
/// 이 프로젝트(<c>SampleProject</c>)에서 사용할 기본 메뉴 아이템 프리팹의 Resources 폴더 내 경로입니다.
/// 부모 클래스 <see cref="TopMenuView"/>의 <see cref="TopMenuView.MenuItemPrefabPath"/> 속성을 오버라이드하여
/// 기본 프리팹 대신 여기에 지정된 프리팹을 사용하도록 합니다.
/// </summary>
protected override string MenuItemPrefabPath => "Prefabs/SampleProject/UI/Menu/SampleProjectMenuItem";
// 주석 처리된 예시 코드:
// 만약 MenuItemPrefabPath 속성을 오버라이드하는 대신,
// Awake에서 직접 프리팹을 로드하고 싶다면 아래와 같이 'new' 키워드를 사용하여
// 부모 클래스와는 별개의 새로운 상수 또는 변수를 선언하고 사용할 수 있습니다.
// 하지만 속성 오버라이드가 일반적으로 더 깔끔하고 권장되는 방식입니다.
//
// /// <summary>
// /// (덮어쓰기 예시) 프로젝트별 MenuItem 프리팹 경로입니다. 'new'를 사용하여 부모의 변수를 가립니다.
// /// </summary>
// private new const string MenuItemPrefabPath = "Prefabs/SampleProject/UI/Menu/SampleMenuItem";
//
// /// <summary>
// /// (덮어쓰기 예시) 프로젝트별 MenuSeparator 프리팹 경로입니다.
// /// </summary>
// private new const string MenuSeparatorPrefabPath = "Prefabs/SampleProject/UI/Menu/SampleMenuSeparator";
/// <summary>
/// MonoBehaviour의 Awake 메시지입니다. 스크립트 인스턴스가 로드될 때 호출됩니다.
/// 부모 클래스 <see cref="TopMenuView"/>의 <c>Awake()</c> 로직을 먼저 실행하여
/// 기본적인 프리팹 로드 및 초기 설정을 수행한 후,
/// 이 <c>SampleProjectTopMenuView</c>만의 추가적인 초기화 로직을 수행할 수 있습니다.
/// </summary>
protected override void Awake()
{
base.Awake(); // 부모 클래스의 Awake 로직 실행
// 1. 부모 클래스(TopMenuView)의 Awake() 메서드를 호출합니다.
// - 이렇게 하면 TopMenuView에 정의된 기본 프리팹 경로들(MenuItemPrefabPath, SubMenuItemPrefabPath 등)을 사용하여
// 프리팹들을 로드하고, menuContainer를 찾는 등의 기본 초기화 작업이 수행됩니다.
// - 만약 이 클래스(SampleProjectTopMenuView)에서 MenuItemPrefabPath와 같은 속성을 오버라이드했다면,
// 부모의 Awake()는 오버라이드된 경로를 사용하여 프리팹을 로드합니다.
base.Awake();
// 여기에 SampleProjectTopMenuView만의 초기화 로직 추가
// 예: 프로젝트별 프리팹 경로를 사용하도록 설정 변경
// if (!string.IsNullOrEmpty(MenuItemPrefabPath))
// 2. 여기에 SampleProjectTopMenuView만의 추가적인 초기화 코드를 작성합니다.
// (아래는 주석 처리된 예시들입니다.)
// 예시 1: 만약 위에서 MenuItemPrefabPath 속성을 오버라이드하지 않고,
// 'new'로 선언된 별도의 경로 변수(예: private new const string MenuItemPrefabPath)를 사용했다면,
// 부모가 로드한 menuItemPrefab을 여기서 다시 로드하여 덮어써야 합니다.
// (하지만 속성 오버라이드가 더 나은 접근 방식입니다.)
//
// if (!string.IsNullOrEmpty(MenuItemPrefabPath_SampleProjectSpecific)) // 'new'로 선언된 프로젝트별 경로 변수 사용 가정
// {
// menuItemPrefab = Resources.Load<GameObject>(MenuItemPrefabPath);
// // 부모 클래스(TopMenuView)의 protected 필드인 menuItemPrefab을 직접 덮어씁니다.
// menuItemPrefab = Resources.Load<GameObject>(MenuItemPrefabPath_SampleProjectSpecific);
// if (menuItemPrefab == null)
// {
// ULog.Error($"[SampleProject] 메뉴 아이템 프리팹을 Resources 폴더에서 로드할 수 없습니다. 경로: {MenuItemPrefabPath}");
// }
// }
//
// if (!string.IsNullOrEmpty(MenuSeparatorPrefabPath))
// {
// menuSeparatorPrefab = Resources.Load<GameObject>(MenuSeparatorPrefabPath);
// if (menuSeparatorPrefab == null)
// {
// ULog.Error($"[SampleProject] 메뉴 구분선 프리팹을 Resources 폴더에서 로드할 수 없습니다. 경로: {MenuSeparatorPrefabPath}");
// ULog.Error($"[SampleProject] 메뉴 아이템 프리팹을 Resources 폴더에서 로드할 수 없습니다. 경로: {MenuItemPrefabPath_SampleProjectSpecific}");
// }
// }
// (menuSeparatorPrefab에 대해서도 유사하게 처리 가능)
// 예: 프로젝트별 메뉴 아이템 크기 간격 변경
// menuItemWidth = 120;
// menuItemHeight = 35;
// menuItemHorizontalGap = 5;
ULog.Debug("SampleProjectTopMenuView Awake 실행됨");
// 예시 2: 프로젝트별 메뉴 아이템의 기본 크기 간격 등을 조정하고 싶을 때.
// 부모 클래스(TopMenuView)에 정의된 protected 필드들을 직접 수정할 수 있습니다.
// 이 값들은 LayoutMenuItem 등에서 사용됩니다.
//
// menuItemSize = new Vector2(120, 35); // 기본 메뉴 아이템 크기를 너비 120, 높이 35로 변경
// menuItemSpace = new Vector2(5, menuItemSpace.y); // 메뉴 아이템 간 수평 간격을 5로 변경 (수직 간격은 유지)
// subContainerPadding = new Vector4(8, 8, 8, 8); // 하위 메뉴 컨테이너의 패딩 변경
ULog.Debug("SampleProjectTopMenuView의 Awake 메서드가 실행 완료되었습니다.");
}
// 필요하다면 CreateMenuItems, LayoutMenuItem 등의 메서드를 override하여
// 프로젝트에 특화된 메뉴 생성 및 레이아웃 로직을 구현할 수 있습니다.
// 예시:
// public override Vector2 CreateMenuItems(List<MenuItemData> items, Transform parentContainer, int depth = 0)
/// <summary>
/// 메뉴 아이템 UI 요소들을 생성하고 배치합니다.
/// 이 메서드는 부모 클래스 <see cref="TopMenuView"/>의 <see cref="TopMenuView.CreateMenuItems"/>를 오버라이드하여,
/// 기본적인 메뉴 생성 로직은 부모에게 맡기되, 생성된 후 각 메뉴 아이템의 시각적 표현
/// (예: <see cref="MenuItemData.IsEnabled"/> 상태에 따른 버튼의 활성화/비활성화 및 스타일)을
/// 이 프로젝트(<c>SampleProject</c>)의 요구사항에 맞게 추가적으로 커스터마이징합니다.
/// </summary>
/// <param name="items">생성할 메뉴 아이템들의 데이터(<see cref="MenuItemData"/>) 리스트입니다.</param>
/// <param name="parentContainer">생성된 메뉴 아이템들이 자식으로 추가될 부모 Transform입니다.</param>
/// <param name="depth">현재 생성 중인 메뉴의 깊이입니다 (최상위 메뉴는 0).</param>
/// <returns>생성된 메뉴 아이템들이 차지하는 전체 영역의 계산된 크기 (너비, 높이)를 반환합니다.</returns>
public override Vector2 CreateMenuItems(List<MenuItemData> items, Transform parentContainer, int depth = 0)
{
// ULog.Debug($"[SampleProject] CreateMenuItems 실행 시작 (Depth: {depth}, 아이템 수: {items?.Count ?? 0})");
// 1. 부모 클래스(TopMenuView)의 CreateMenuItems 메서드를 호출합니다.
// - 이 호출을 통해 기본적인 메뉴 아이템 GameObject들이 프리팹으로부터 생성되고,
// 텍스트 설정, 기본 이벤트 연결, 하위 메뉴 재귀 생성 등이 수행됩니다.
// - 또한, 부모 클래스의 _menuItemObjects 딕셔너리에 생성된 GameObject들이 ItemId를 키로 하여 저장됩니다.
// - 반환값은 생성된 메뉴들이 차지하는 계산된 크기입니다.
Vector2 calculatedSize = base.CreateMenuItems(items, parentContainer, depth);
// 2. 부모 클래스에서 메뉴 아이템들이 생성된 후, 추가적인 프로젝트별 시각적 처리를 수행합니다.
// - _menuItemObjects 딕셔너리는 부모 클래스에 protected로 선언되어 있어 접근 가능합니다.
// - 이 딕셔너리를 사용하여 각 MenuItemData에 해당하는 실제 GameObject를 가져올 수 있습니다.
if (items != null)
{
foreach (MenuItemData itemData in items)
{
if (itemData.IsSeparator) continue; // 구분선 아이템은 특별한 시각적 처리가 필요 없을 수 있으므로 건너뜁니다.
// _menuItemObjects에서 현재 itemData에 해당하는 GameObject를 찾습니다.
if (_menuItemObjects.TryGetValue(itemData.ItemId, out GameObject menuItemInstance))
{
// GameObject에서 Button 컴포넌트를 가져옵니다.
Button button = menuItemInstance.GetComponent<Button>();
if (button != null)
{
// MenuItemData의 IsEnabled 상태에 따라 버튼의 interactable 속성을 설정합니다.
// - true이면 버튼이 활성화되어 클릭 가능하게 보입니다.
// - false이면 버튼이 비활성화되어 클릭 불가능하게 보입니다.
// (실제 클릭 로직 제어는 TopMenuController의 HandleMenuItemClicked에서도 이루어지지만,
// View에서의 시각적인 피드백도 중요합니다.)
button.interactable = itemData.IsEnabled;
// 추가적으로, 비활성화된 아이템의 텍스트 색상이나 다른 시각적 요소를 변경할 수 있습니다.
TextMeshProUGUI buttonText = menuItemInstance.GetComponentInChildren<TextMeshProUGUI>(true); // 비활성화된 자식도 검색
if (buttonText != null)
{
// 예시: 비활성화된 아이템의 텍스트 색상을 약간 어둡게 또는 회색으로 변경
// buttonText.color = itemData.IsEnabled ? Color.white : new Color(0.7f, 0.7f, 0.7f, 0.8f);
// (실제 사용할 색상 값은 프로젝트의 디자인 가이드에 따라 결정해야 합니다.)
// ULog.Debug($"[SampleProject] 아이템 '{itemData.ItemId}'의 버튼 활성 상태: {button.interactable}, 텍스트: {buttonText.text}");
}
}
else
{
// ULog.Warning($"[SampleProject] 메뉴 아이템 '{itemData.ItemId}'의 GameObject에 Button 컴포넌트가 없습니다.");
}
}
else
{
// ULog.Warning($"[SampleProject] _menuItemObjects에 ID '{itemData.ItemId}'를 가진 메뉴 아이템 GameObject를 찾을 수 없습니다.");
}
}
}
// ULog.Debug($"[SampleProject] CreateMenuItems 실행 완료 (Depth: {depth}). 계산된 크기: {calculatedSize}");
return calculatedSize; // 부모 클래스가 계산한 크기를 그대로 반환합니다.
}
// 필요하다면, 메뉴 아이템의 위치나 크기를 정하는 LayoutMenuItem 등의 다른 메서드도
// override하여 프로젝트에 특화된 레이아웃 로직을 구현할 수 있습니다.
//
// /// <summary>
// /// (오버라이드 예시) UI 요소(메뉴 아이템 또는 구분선)의 위치와 크기를 설정합니다.
// /// 부모의 레이아웃 로직을 실행한 후, 프로젝트별 추가적인 시각적 조정을 할 수 있습니다.
// /// </summary>
// protected override void LayoutMenuItem(GameObject uiElement, MenuItemData itemData, int depth, int itemIndex, List<MenuItemData> siblingItems)
// {
// ULog.Debug("SampleProjectTopMenuView CreateMenuItems 실행됨");
// // 프로젝트별 추가 로직...
// return base.CreateMenuItems(items, parentContainer, depth);
// // 1. 부모 클래스의 LayoutMenuItem을 호출하여 기본적인 위치 및 크기 설정을 수행합니다.
// base.LayoutMenuItem(uiElement, itemData, depth, itemIndex, siblingItems);
//
// // 2. 여기에 프로젝트별 추가적인 레이아웃 조정 로직을 넣습니다.
// if (!itemData.IsEnabled && !itemData.IsSeparator)
// {
// // 예시: 비활성화된 메뉴 아이템의 투명도를 약간 낮추어 시각적으로 덜 강조되게 합니다.
// // CanvasGroup canvasGroup = uiElement.GetComponent<CanvasGroup>();
// // if (canvasGroup == null) canvasGroup = uiElement.AddComponent<CanvasGroup>();
// // canvasGroup.alpha = 0.6f;
// // ULog.Debug($"[SampleProject] 비활성화된 아이템 '{itemData.ItemId}'의 투명도 조정됨.");
// }
// }
// 프로젝트별로 특정 UI 요소의 동작을 변경하거나 추가할 수 있습니다.
// 이 외에도, 프로젝트별로 특정 UI 요소의 동작을 변경하거나 추가적인 시각적 효과를 주고 싶을 때
// TopMenuView의 다른 virtual 메서드들을 오버라이드하여 구현할 수 있습니다.
// 예를 들어, 하위 메뉴가 열리거나 닫힐 때 특별한 애니메이션을 추가하거나(ToggleSubMenuDisplay 오버라이드),
// 메뉴 아이템의 모양을 프로젝트 테마에 맞게 동적으로 변경하는 등의 작업이 가능합니다.
}
}