modal 개발 중
This commit is contained in:
@@ -5,6 +5,7 @@ using UVC.Log;
|
||||
using UVC.network;
|
||||
using UVC.Network;
|
||||
using UVC.Tests;
|
||||
using UVC.UI.Modal;
|
||||
|
||||
namespace SampleProject
|
||||
{
|
||||
@@ -46,5 +47,28 @@ namespace SampleProject
|
||||
//mqttService.Connect();
|
||||
}
|
||||
|
||||
public async void ShowSimpleAlert()
|
||||
{
|
||||
Debug.Log("알림창을 엽니다...");
|
||||
await Alert.Show("알림", "이것은 간단한 알림 메시지입니다.");
|
||||
Debug.Log("알림창이 닫혔습니다.");
|
||||
}
|
||||
|
||||
public async void ShowAlertWithCustomConfirmText()
|
||||
{
|
||||
await Alert.Show("경고", "데이터를 저장할 수 없습니다.", "알겠습니다");
|
||||
}
|
||||
|
||||
public async void ShowLocalizedAlert()
|
||||
{
|
||||
// locale.json에 다음 키들이 정의되어 있다고 가정:
|
||||
// "alert_title_error": "오류", "Error"
|
||||
// "alert_message_network": "네트워크 연결을 확인해주세요.", "Please check your network connection."
|
||||
// "alert_button_retry": "재시도", "Retry"
|
||||
|
||||
await Alert.ShowLocalized("error", "error_network_not", "button_retry");
|
||||
// 또는 확인 버튼에 기본 키(modal_confirm_button)를 사용하려면:
|
||||
// await Alert.ShowLocalized("alert_title_error", "alert_message_network");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
/// 프로젝트에 특화된 메뉴 아이템들을 초기화합니다.
|
||||
/// TopMenuController의 InitializeMenuItems를 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를 반환하도록 되어 있습니다. 실제 구현 필요.
|
||||
// }
|
||||
// --- 예시 코드 끝 ---
|
||||
}
|
||||
}
|
||||
@@ -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<MenuItemData> 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<TextMeshProUGUI>(true);
|
||||
/// if (buttonText != null)
|
||||
/// {
|
||||
/// // 예시: 고대비 테마일 경우 텍스트 색상을 밝은 노란색으로 변경
|
||||
/// // buttonText.color = Color.yellow;
|
||||
/// }
|
||||
/// // Image background = menuItemInstance.GetComponent<Image>();
|
||||
/// // 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 오버라이드),
|
||||
// 메뉴 아이템의 모양을 프로젝트 테마에 맞게 동적으로 변경하는 등의 작업이 가능합니다.
|
||||
}
|
||||
}
|
||||
33
Assets/Scripts/UVC/UI/Commands/ChangeLanguageCommand.cs
Normal file
33
Assets/Scripts/UVC/UI/Commands/ChangeLanguageCommand.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using UVC.Locale;
|
||||
using UVC.Log;
|
||||
|
||||
namespace UVC.UI.Commands
|
||||
{
|
||||
|
||||
// 언어 변경 커맨드
|
||||
public class ChangeLanguageCommand : ICommand
|
||||
{
|
||||
private readonly string _languageCode;
|
||||
|
||||
public ChangeLanguageCommand(string languageCode)
|
||||
{
|
||||
_languageCode = languageCode;
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
LocalizationManager _localizationManager = LocalizationManager.Instance;
|
||||
if (_localizationManager != null)
|
||||
{
|
||||
_localizationManager.SetCurrentLanguage(_languageCode);
|
||||
ULog.Debug($"언어가 {_languageCode}(으)로 변경되었습니다. (Command)");
|
||||
}
|
||||
else
|
||||
{
|
||||
ULog.Error("LocalizationManager가 ChangeLanguageCommand에 전달되지 않았습니다.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
20
Assets/Scripts/UVC/UI/Commands/DebugLogCommand.cs
Normal file
20
Assets/Scripts/UVC/UI/Commands/DebugLogCommand.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using UVC.Log;
|
||||
|
||||
namespace UVC.UI.Commands
|
||||
{
|
||||
// 간단한 디버그 로그 출력 커맨드
|
||||
public class DebugLogCommand : ICommand
|
||||
{
|
||||
private readonly string _message;
|
||||
|
||||
public DebugLogCommand(string message)
|
||||
{
|
||||
_message = message;
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
ULog.Debug(_message);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/UI/Commands/DebugLogCommand.cs.meta
Normal file
2
Assets/Scripts/UVC/UI/Commands/DebugLogCommand.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 30eecf77a607ebb4cb25eb2c27d24389
|
||||
@@ -1,62 +0,0 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UVC.Locale;
|
||||
using UVC.Log;
|
||||
|
||||
namespace UVC.UI.Commands
|
||||
{
|
||||
// 간단한 디버그 로그 출력 커맨드
|
||||
public class DebugLogCommand : ICommand
|
||||
{
|
||||
private readonly string _message;
|
||||
|
||||
public DebugLogCommand(string message)
|
||||
{
|
||||
_message = message;
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
ULog.Debug(_message);
|
||||
}
|
||||
}
|
||||
|
||||
// 애플리케이션 종료 커맨드
|
||||
public class QuitApplicationCommand : ICommand
|
||||
{
|
||||
public void Execute()
|
||||
{
|
||||
ULog.Debug("애플리케이션을 종료합니다.");
|
||||
Application.Quit();
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.EditorApplication.isPlaying = false; // 에디터에서 실행 중일 경우 플레이 모드 종료
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// 언어 변경 커맨드
|
||||
public class ChangeLanguageCommand : ICommand
|
||||
{
|
||||
private readonly string _languageCode;
|
||||
private readonly LocalizationManager _localizationManager;
|
||||
|
||||
public ChangeLanguageCommand(string languageCode, LocalizationManager localizationManager)
|
||||
{
|
||||
_languageCode = languageCode;
|
||||
_localizationManager = localizationManager;
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
if (_localizationManager != null)
|
||||
{
|
||||
_localizationManager.SetCurrentLanguage(_languageCode);
|
||||
ULog.Debug($"언어가 {_languageCode}(으)로 변경되었습니다. (Command)");
|
||||
}
|
||||
else
|
||||
{
|
||||
ULog.Error("LocalizationManager가 ChangeLanguageCommand에 전달되지 않았습니다.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
Assets/Scripts/UVC/UI/Commands/Mono.meta
Normal file
8
Assets/Scripts/UVC/UI/Commands/Mono.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f6098959b5d11f8409d2e13ac60b4ee0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Collections.Generic;
|
||||
using UVC.Locale;
|
||||
using UVC.Log;
|
||||
|
||||
namespace UVC.UI.Commands.Mono
|
||||
{
|
||||
public class ChangeLanguageCommandMono : MonoBehaviourCommand
|
||||
{
|
||||
public override void Execute()
|
||||
{
|
||||
//언어가 2개 인경우 switch 시킴
|
||||
LocalizationManager _localizationManager = LocalizationManager.Instance;
|
||||
if (_localizationManager != null)
|
||||
{
|
||||
string _languageCode = _localizationManager.CurrentLanguage;
|
||||
List<string> allLan = _localizationManager.AvailableLanguages;
|
||||
if (allLan.Count == 0)
|
||||
{
|
||||
ULog.Error("사용 가능한 언어가 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (string lang in allLan)
|
||||
{
|
||||
if(_languageCode != lang)
|
||||
{
|
||||
_languageCode = lang;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_localizationManager.SetCurrentLanguage(_languageCode);
|
||||
ULog.Debug($"언어가 {_languageCode}(으)로 변경되었습니다. (Command)");
|
||||
}
|
||||
else
|
||||
{
|
||||
ULog.Error("LocalizationManager가 ChangeLanguageCommand에 전달되지 않았습니다.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ebbb0027d30b4fc499f841193050cb79
|
||||
14
Assets/Scripts/UVC/UI/Commands/Mono/MonoBehaviourCommand.cs
Normal file
14
Assets/Scripts/UVC/UI/Commands/Mono/MonoBehaviourCommand.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.UI.Commands.Mono
|
||||
{
|
||||
public class MonoBehaviourCommand : MonoBehaviour, ICommand
|
||||
{
|
||||
// MonoCommand는 MonoBehaviour를 상속받아 Unity의 생명주기를 활용할 수 있습니다.
|
||||
// ICommand 인터페이스를 구현하여 명령 패턴을 따릅니다.
|
||||
public virtual void Execute()
|
||||
{
|
||||
// 기본 실행 로직 (필요시 override 가능)
|
||||
Debug.Log("MonoCommand executed.");
|
||||
} }
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d397ba1578c376943a1e6b9c25deafe3
|
||||
@@ -0,0 +1,14 @@
|
||||
using UnityEngine;
|
||||
using UVC.Log;
|
||||
|
||||
namespace UVC.UI.Commands.Mono
|
||||
{
|
||||
public class QuitApplicationCommandMono : MonoBehaviourCommand
|
||||
{
|
||||
public override void Execute()
|
||||
{
|
||||
new QuitApplicationCommand().Execute();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2480dc463c63bf945a9488183ffe66d0
|
||||
19
Assets/Scripts/UVC/UI/Commands/QuitApplicationCommand.cs
Normal file
19
Assets/Scripts/UVC/UI/Commands/QuitApplicationCommand.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using UnityEngine;
|
||||
using UVC.Log;
|
||||
|
||||
namespace UVC.UI.Commands
|
||||
{
|
||||
// 애플리케이션 종료 커맨드
|
||||
public class QuitApplicationCommand : ICommand
|
||||
{
|
||||
public void Execute()
|
||||
{
|
||||
ULog.Debug("애플리케이션을 종료합니다.");
|
||||
Application.Quit();
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.EditorApplication.isPlaying = false; // 에디터에서 실행 중일 경우 플레이 모드 종료
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ab6dafe99d95c304f8b4a697cd6f3320
|
||||
@@ -1,131 +1,385 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UVC.Locale;
|
||||
using System.Collections.Generic;
|
||||
using UVC.UI.Commands;
|
||||
using UVC.Log;
|
||||
using System;
|
||||
using UVC.UI.Commands;
|
||||
|
||||
namespace UVC.UI.Menu
|
||||
{
|
||||
/// <summary>
|
||||
/// 상단 메뉴의 로직을 관리하는 컨트롤러 클래스입니다.
|
||||
/// Model(데이터: <see cref="TopMenuModel"/>)과 View(표시: <see cref="TopMenuView"/>) 사이의 중재자 역할을 하며,
|
||||
/// 메뉴 아이템 초기화, 사용자 입력 처리, 언어 변경 감지 등의 기능을 수행합니다.
|
||||
/// 이 클래스는 MonoBehaviour를 상속받아 Unity 게임 오브젝트에 컴포넌트로 추가될 수 있습니다.
|
||||
/// Inspector에서 <see cref="TopMenuController"/>와 <see cref="TopMenuView"/> 추가해서 사용합니다.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// 다음은 TopMenuController를 상속받아 특정 기능을 확장하는 예제입니다.
|
||||
/// <code>
|
||||
/// public class CustomTopMenuController : TopMenuController
|
||||
/// {
|
||||
/// protected override void InitializeMenuItems()
|
||||
/// {
|
||||
/// // 기본 메뉴 아이템 초기화 로직 호출
|
||||
/// base.InitializeMenuItems();
|
||||
///
|
||||
/// // 기존 모델에 새로운 메뉴 아이템 추가 또는 수정
|
||||
/// // 예: '도움말' 메뉴 추가
|
||||
/// model.MenuItems.Add(new MenuItemData("help", "menu_help", subMenuItems: new List<MenuItemData>
|
||||
/// {
|
||||
/// new MenuItemData("help_about", "menu_help_about", new DebugLogCommand("도움말 > 정보 선택됨"))
|
||||
/// }));
|
||||
///
|
||||
/// // 변경된 모델을 기반으로 뷰를 다시 생성하거나 업데이트해야 할 수 있습니다.
|
||||
/// // (이미 Start 메서드에서 CreateMenuItems가 호출되므로, InitializeMenuItems 시점에서는 모델만 수정)
|
||||
/// ULog.Debug("CustomTopMenuController: '도움말' 메뉴가 추가되었습니다.");
|
||||
/// }
|
||||
///
|
||||
/// protected override void HandleMenuItemClicked(MenuItemData clickedItemData)
|
||||
/// {
|
||||
/// // 기본 클릭 처리 로직 호출
|
||||
/// base.HandleMenuItemClicked(clickedItemData);
|
||||
///
|
||||
/// // 특정 메뉴 아이템에 대한 추가적인 커스텀 로직 수행
|
||||
/// if (clickedItemData.ItemId == "file_exit")
|
||||
/// {
|
||||
/// ULog.Debug("CustomTopMenuController: 애플리케이션 종료 메뉴가 선택되었습니다. 추가 확인 로직을 여기에 넣을 수 있습니다.");
|
||||
/// // 예: 사용자에게 정말 종료할 것인지 확인하는 팝업 표시 등
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // 새로운 기능을 추가할 수도 있습니다.
|
||||
/// public void AddCustomMenuOption(string parentItemId, MenuItemData newItem)
|
||||
/// {
|
||||
/// if (model == null || model.MenuItems == null) return;
|
||||
///
|
||||
/// MenuItemData parentItem = FindMenuItemRecursive(model.MenuItems, parentItemId);
|
||||
/// if (parentItem != null)
|
||||
/// {
|
||||
/// if (parentItem.IsSeparator)
|
||||
/// {
|
||||
/// ULog.Warning($"구분선('{parentItemId}')에는 하위 메뉴를 추가할 수 없습니다.");
|
||||
/// return;
|
||||
/// }
|
||||
/// parentItem.AddSubMenuItem(newItem);
|
||||
/// ULog.Debug($"'{parentItemId}'에 새로운 하위 메뉴 '{newItem.ItemId}'가 추가되었습니다.");
|
||||
///
|
||||
/// // 중요: 모델 변경 후에는 뷰를 업데이트해야 합니다.
|
||||
/// // 예를 들어, 메뉴를 전부 다시 그리거나 특정 부분만 업데이트하는 메서드를 호출합니다.
|
||||
/// // view.ClearMenuItems();
|
||||
/// // view.CreateMenuItems(model.MenuItems, view.MenuContainer);
|
||||
/// // 또는 더 정교한 뷰 업데이트 메서드가 필요할 수 있습니다.
|
||||
/// }
|
||||
/// else
|
||||
/// {
|
||||
/// ULog.Warning($"ID가 '{parentItemId}'인 부모 메뉴 아이템을 찾을 수 없습니다.");
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class TopMenuController : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// 메뉴의 시각적 표현을 담당하는 <see cref="TopMenuView"/> 컴포넌트에 대한 참조입니다.
|
||||
/// Inspector에서 할당하거나 <see cref="Awake"/> 메서드에서 자동으로 찾습니다.
|
||||
/// </summary>
|
||||
protected TopMenuView view;
|
||||
|
||||
/// <summary>
|
||||
/// 메뉴 아이템들의 데이터 구조를 관리하는 <see cref="TopMenuModel"/> 인스턴스입니다.
|
||||
/// 메뉴의 내용(예: 아이템 이름, 명령, 하위 메뉴)을 저장합니다.
|
||||
/// </summary>
|
||||
protected TopMenuModel model;
|
||||
|
||||
/// <summary>
|
||||
/// 다국어 처리를 위한 <see cref="LocalizationManager"/>의 인스턴스입니다.
|
||||
/// 메뉴 아이템의 텍스트를 현재 설정된 언어에 맞게 표시하는 데 사용됩니다.
|
||||
/// </summary>
|
||||
protected LocalizationManager _locManager;
|
||||
|
||||
/// <summary>
|
||||
/// MonoBehaviour의 Awake 메시지입니다. 스크립트 인스턴스가 로드될 때 호출됩니다.
|
||||
/// 주로 <see cref="view"/> 컴포넌트를 찾는 데 사용됩니다.
|
||||
/// 상속 클래스에서 오버라이드하여 추가적인 초기화 로직을 구현할 수 있습니다.
|
||||
/// </summary>
|
||||
protected virtual void Awake()
|
||||
{
|
||||
// 1. 동일한 GameObject에서 TopMenuView 컴포넌트 검색
|
||||
// 1. 이 GameObject에 연결된 TopMenuView 컴포넌트를 찾습니다.
|
||||
view = GetComponent<TopMenuView>();
|
||||
|
||||
// 2. 동일한 GameObject에 없다면, 자식 GameObject에서 검색
|
||||
// 2. 만약 현재 GameObject에 없다면, 자식 GameObject들 중에서 TopMenuView 컴포넌트를 찾습니다.
|
||||
if (view == null)
|
||||
{
|
||||
view = GetComponentInChildren<TopMenuView>();
|
||||
}
|
||||
|
||||
// view가 여전히 null이라면, Start 메서드에서 오류를 기록할 것입니다.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MonoBehaviour의 Start 메시지입니다. 첫 번째 프레임 업데이트 전에 호출됩니다.
|
||||
/// 모델 초기화, <see cref="LocalizationManager"/> 인스턴스 가져오기, 메뉴 아이템 데이터 설정,
|
||||
/// View에 메뉴 생성 요청, 이벤트 핸들러 등록 등의 주요 초기화 작업을 수행합니다.
|
||||
/// </summary>
|
||||
protected virtual void Start()
|
||||
{
|
||||
// 메뉴 데이터 모델 인스턴스 생성
|
||||
model = new TopMenuModel();
|
||||
// 다국어 관리자 인스턴스 가져오기
|
||||
_locManager = LocalizationManager.Instance;
|
||||
|
||||
// View 컴포넌트가 할당되었는지 확인
|
||||
if (view == null)
|
||||
{
|
||||
ULog.Error("TopMenuView가 Inspector에서 할당되지 않았습니다.");
|
||||
return;
|
||||
ULog.Error("TopMenuView가 Inspector에서 할당되지 않았거나 찾을 수 없습니다. TopMenuController가 정상적으로 작동하지 않습니다.");
|
||||
return; // View가 없으면 더 이상 진행할 수 없음
|
||||
}
|
||||
|
||||
// LocalizationManager 인스턴스 확인
|
||||
if (_locManager == null)
|
||||
{
|
||||
ULog.Error("LocalizationManager 인스턴스를 찾을 수 없습니다. 메뉴 텍스트가 올바르게 표시되지 않을 수 있습니다.");
|
||||
// _locManager가 null이어도 메뉴 구조는 생성될 수 있도록 진행합니다.
|
||||
// TopMenuView에서 _locManager null 체크를 통해 텍스트 처리를 합니다.
|
||||
ULog.Error("LocalizationManager 인스턴스를 찾을 수 없습니다. 메뉴 텍스트가 올바르게 표시되지 않거나, 언어 변경 기능이 작동하지 않을 수 있습니다.");
|
||||
// _locManager가 null이어도 메뉴 구조 자체는 생성될 수 있도록 계속 진행합니다.
|
||||
// TopMenuView와 이 클래스의 다른 부분에서 _locManager null 체크를 통해 안전하게 처리합니다.
|
||||
}
|
||||
|
||||
// 메뉴 아이템 데이터 초기화 (모델 채우기)
|
||||
InitializeMenuItems();
|
||||
|
||||
// View에 기존 메뉴 아이템들을 지우도록 요청
|
||||
view.ClearMenuItems();
|
||||
// menuContainer가 TopMenuView 내부에 public으로 노출되어 있다고 가정합니다.
|
||||
// 만약 private이라면, TopMenuView에 menuContainer를 전달받는 public 메서드가 필요할 수 있습니다.
|
||||
// View에 현재 모델 데이터를 기반으로 메뉴 UI를 생성하도록 요청
|
||||
// view.MenuContainer는 TopMenuView에서 메뉴 UI 요소들이 배치될 부모 Transform을 가리킵니다.
|
||||
view.CreateMenuItems(model.MenuItems, view.MenuContainer);
|
||||
|
||||
// View에서 발생하는 메뉴 아이템 클릭 이벤트에 대한 핸들러 등록
|
||||
view.OnMenuItemClicked += HandleMenuItemClicked;
|
||||
|
||||
// LocalizationManager가 존재하고, 언어 변경 이벤트를 지원한다면 핸들러 등록
|
||||
if (_locManager != null)
|
||||
{
|
||||
_locManager.OnLanguageChanged += HandleLanguageChanged;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MonoBehaviour의 OnDestroy 메시지입니다. GameObject가 파괴될 때 호출됩니다.
|
||||
/// 등록된 이벤트 핸들러들을 해제하여 메모리 누수 및 잠재적 오류를 방지합니다.
|
||||
/// </summary>
|
||||
protected virtual void OnDestroy()
|
||||
{
|
||||
// View의 메뉴 아이템 클릭 이벤트 핸들러 해제
|
||||
if (view != null)
|
||||
{
|
||||
view.OnMenuItemClicked -= HandleMenuItemClicked;
|
||||
}
|
||||
// LocalizationManager의 언어 변경 이벤트 핸들러 해제
|
||||
if (_locManager != null)
|
||||
{
|
||||
_locManager.OnLanguageChanged -= HandleLanguageChanged;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 메뉴 아이템 데이터를 초기화하고 <see cref="model"/>에 추가합니다.
|
||||
/// 이 메서드에서 메뉴의 전체 구조(항목, 하위 항목, 구분선, 연결된 명령 등)를 정의합니다.
|
||||
/// 상속 클래스에서 이 메서드를 오버라이드하여 메뉴 구성을 변경하거나 확장할 수 있습니다.
|
||||
/// </summary>
|
||||
protected virtual void InitializeMenuItems()
|
||||
{
|
||||
// 기존 메뉴 아이템 목록을 비웁니다.
|
||||
model.MenuItems.Clear();
|
||||
|
||||
// "파일" 메뉴 및 하위 메뉴들 정의
|
||||
model.MenuItems.Add(new MenuItemData("file", "menu_file", subMenuItems: new List<MenuItemData>
|
||||
{
|
||||
new MenuItemData("file_new", "menu_file_new", subMenuItems: new List<MenuItemData>
|
||||
{
|
||||
new MenuItemData("file_new_project", "menu_file_new_project", new DebugLogCommand("새 프로젝트 선택됨 (Command)")),
|
||||
new MenuItemData("file_new_file", "menu_file_new_file", new DebugLogCommand("새 파일 선택됨 (Command)"))
|
||||
new MenuItemData("file_new_project", "menu_file_new_project", new DebugLogCommand("새 프로젝트 선택됨 (Command 실행)")),
|
||||
new MenuItemData("file_new_file", "menu_file_new_file", new DebugLogCommand("새 파일 선택됨 (Command 실행)"))
|
||||
}),
|
||||
new MenuItemData("file_open", "menu_file_open", new DebugLogCommand("파일 열기 선택됨 (Command)")),
|
||||
new MenuItemData("file_open", "menu_file_open", new DebugLogCommand("파일 열기 선택됨 (Command 실행)"), isEnabled: false), // "파일 열기"는 비활성화 상태로 시작
|
||||
MenuItemData.CreateSeparator("file_sep1"), // 구분선 추가
|
||||
new MenuItemData("file_save", "menu_file_save", command: new DebugLogCommand("저장 선택됨 (Command)") , subMenuItems: new List<MenuItemData> // 저장 메뉴에 Command 추가
|
||||
new MenuItemData("file_save", "menu_file_save", command: new DebugLogCommand("저장 선택됨 (Command 실행)") , subMenuItems: new List<MenuItemData>
|
||||
{
|
||||
new MenuItemData("file_save_as", "menu_file_save_as", new DebugLogCommand("다른 이름으로 저장 선택됨 (Command)"))
|
||||
new MenuItemData("file_save_as", "menu_file_save_as", new DebugLogCommand("다른 이름으로 저장 선택됨 (Command 실행)"))
|
||||
}),
|
||||
MenuItemData.CreateSeparator("file_sep2"), // 또 다른 구분선 추가
|
||||
new MenuItemData("file_exit", "menu_file_exit", new QuitApplicationCommand())
|
||||
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("실행 취소 선택됨 (Command)")),
|
||||
new MenuItemData("edit_redo", "menu_edit_redo", new DebugLogCommand("다시 실행 선택됨 (Command)")),
|
||||
MenuItemData.CreateSeparator("edit_sep1"),
|
||||
new MenuItemData("preferences", "menu_preferences", new DebugLogCommand("환경설정 선택됨 (Command)"))
|
||||
new MenuItemData("edit_undo", "menu_edit_undo", new DebugLogCommand("실행 취소 선택됨 (Command 실행)")),
|
||||
new MenuItemData("edit_redo", "menu_edit_redo", new DebugLogCommand("다시 실행 선택됨 (Command 실행)")),
|
||||
MenuItemData.CreateSeparator("edit_sep1"), // 구분선
|
||||
new MenuItemData("preferences", "menu_preferences", new DebugLogCommand("환경설정 선택됨 (Command 실행)"))
|
||||
}));
|
||||
|
||||
if (_locManager != null)
|
||||
model.MenuItems.Add(new MenuItemData("language", "menu_language", subMenuItems: new List<MenuItemData>
|
||||
{
|
||||
model.MenuItems.Add(new MenuItemData("language", "menu_language", 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))
|
||||
}));
|
||||
}
|
||||
else
|
||||
{
|
||||
ULog.Warning("LocalizationManager가 null이므로 언어 변경 메뉴를 초기화할 수 없습니다.");
|
||||
}
|
||||
// 각 언어 메뉴 아이템에 ChangeLanguageCommand를 연결하여 언어 변경 기능 수행
|
||||
new MenuItemData("lang_ko", "menu_lang_korean", new ChangeLanguageCommand("ko-KR")),
|
||||
new MenuItemData("lang_en", "menu_lang_english", new ChangeLanguageCommand("en-US"))
|
||||
// 필요에 따라 다른 언어들도 추가 가능
|
||||
}));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="TopMenuView.OnMenuItemClicked"/> 이벤트가 발생했을 때 호출되는 핸들러입니다.
|
||||
/// 클릭된 메뉴 아이템(<paramref name="clickedItemData"/>)의 유효성을 검사하고,
|
||||
/// 연결된 <see cref="ICommand"/>를 실행합니다.
|
||||
/// </summary>
|
||||
/// <param name="clickedItemData">사용자가 클릭한 메뉴 아이템의 데이터입니다.</param>
|
||||
protected virtual void HandleMenuItemClicked(MenuItemData clickedItemData)
|
||||
{
|
||||
if (clickedItemData.IsSeparator) return; // 구분선은 클릭 액션이 없음
|
||||
// 클릭된 아이템이 구분선이거나 비활성화 상태인지 확인
|
||||
if (clickedItemData.IsSeparator)
|
||||
{
|
||||
// 구분선은 아무 동작도 하지 않음
|
||||
return;
|
||||
}
|
||||
if (!clickedItemData.IsEnabled)
|
||||
{
|
||||
// 비활성화된 아이템 클릭 시 로그 (디버깅 목적)
|
||||
ULog.Debug($"비활성화된 메뉴 아이템 클릭 시도: {clickedItemData.ItemId} (표시 키: {clickedItemData.DisplayNameKey})");
|
||||
return; // 비활성화된 아이템은 명령을 실행하지 않음
|
||||
}
|
||||
|
||||
ULog.Debug($"메뉴 아이템 클릭됨: {clickedItemData.ItemId} (Key: {clickedItemData.DisplayNameKey})");
|
||||
// 클릭된 메뉴 아이템 정보 로그 (디버깅 목적)
|
||||
ULog.Debug($"메뉴 아이템 클릭됨: {clickedItemData.ItemId} (표시 키: {clickedItemData.DisplayNameKey})");
|
||||
|
||||
// 메뉴 아이템에 연결된 Command가 있다면 실행
|
||||
// Command가 null일 수 있으므로 null 조건부 연산자(?.) 사용
|
||||
clickedItemData.Command?.Execute();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="LocalizationManager.OnLanguageChanged"/> 이벤트가 발생했을 때 호출되는 핸들러입니다.
|
||||
/// 새로운 언어 코드(<paramref name="newLanguageCode"/>)가 적용되었음을 감지하고,
|
||||
/// <see cref="view"/>에 모든 메뉴 텍스트를 업데이트하도록 요청합니다.
|
||||
/// </summary>
|
||||
/// <param name="newLanguageCode">새롭게 설정된 언어 코드입니다 (예: "ko-KR", "en-US").</param>
|
||||
protected virtual void HandleLanguageChanged(string newLanguageCode)
|
||||
{
|
||||
ULog.Debug($"언어 변경 감지: {newLanguageCode}. 메뉴 텍스트 업데이트 중...");
|
||||
ULog.Debug($"언어 변경 감지됨: {newLanguageCode}. 메뉴 텍스트 업데이트를 시도합니다...");
|
||||
// View와 Model이 모두 유효한 경우에만 텍스트 업데이트 진행
|
||||
if (view != null && model != null)
|
||||
{
|
||||
view.UpdateAllMenuTexts(model.MenuItems);
|
||||
ULog.Debug("메뉴 텍스트 업데이트 완료.");
|
||||
}
|
||||
else
|
||||
{
|
||||
ULog.Warning("View 또는 Model이 null이므로 메뉴 텍스트를 업데이트할 수 없습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 ID를 가진 메뉴 아이템의 활성화 상태를 동적으로 변경합니다.
|
||||
/// 이 메서드는 모델(<see cref="TopMenuModel"/>)의 데이터를 변경하고,
|
||||
/// 변경 사항을 뷰(<see cref="TopMenuView"/>)에 반영하여 UI의 버튼 상호작용 상태를 업데이트합니다.
|
||||
/// </summary>
|
||||
/// <param name="itemId">상태를 변경할 메뉴 아이템의 고유 ID입니다.</param>
|
||||
/// <param name="isEnabled">새로운 활성화 상태입니다 (true: 활성, false: 비활성).</param>
|
||||
public virtual void SetMenuItemEnabled(string itemId, bool isEnabled)
|
||||
{
|
||||
if (model == null || model.MenuItems == null)
|
||||
{
|
||||
ULog.Warning("모델이 초기화되지 않아 메뉴 아이템 활성화 상태를 변경할 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 모델에서 해당 ID를 가진 메뉴 아이템을 재귀적으로 검색
|
||||
MenuItemData targetItem = FindMenuItemRecursive(model.MenuItems, itemId);
|
||||
|
||||
if (targetItem != null)
|
||||
{
|
||||
// 구분선의 활성화 상태는 변경할 수 없음
|
||||
if (targetItem.IsSeparator)
|
||||
{
|
||||
ULog.Warning($"구분선('{itemId}')의 활성화 상태는 변경할 수 없습니다. 작업이 무시됩니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 이미 요청된 상태와 동일하다면 변경하지 않음 (불필요한 업데이트 방지)
|
||||
if (targetItem.IsEnabled == isEnabled)
|
||||
{
|
||||
ULog.Debug($"메뉴 아이템 '{itemId}'은(는) 이미 요청된 활성화 상태({isEnabled})입니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 모델 데이터의 활성화 상태 변경
|
||||
targetItem.IsEnabled = isEnabled;
|
||||
ULog.Debug($"모델에서 메뉴 아이템 '{itemId}'의 활성화 상태가 '{isEnabled}'(으)로 변경되었습니다.");
|
||||
|
||||
// 2. View 업데이트: 해당 메뉴 아이템 GameObject를 찾아 Button 컴포넌트의 interactable 속성 갱신
|
||||
if (view != null && view.isActiveAndEnabled) // View가 유효하고 활성화된 상태일 때만 시도
|
||||
{
|
||||
if (view.TryGetMenuItemGameObject(itemId, out GameObject menuItemGO))
|
||||
{
|
||||
Button button = menuItemGO.GetComponent<Button>();
|
||||
if (button != null)
|
||||
{
|
||||
button.interactable = isEnabled;
|
||||
ULog.Debug($"View에서 메뉴 아이템 '{itemId}'의 버튼 상호작용 상태가 '{isEnabled}'(으)로 업데이트되었습니다.");
|
||||
// 필요에 따라, 텍스트 색상 등 다른 시각적 요소도 여기서 업데이트 할 수 있습니다.
|
||||
// 예: TextMeshProUGUI textComponent = menuItemGO.GetComponentInChildren<TextMeshProUGUI>();
|
||||
// if (textComponent != null) textComponent.color = isEnabled ? Color.black : Color.gray;
|
||||
}
|
||||
else
|
||||
{
|
||||
ULog.Warning($"메뉴 아이템 GameObject ('{itemId}')에서 Button 컴포넌트를 찾을 수 없어 View를 업데이트할 수 없습니다.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ULog.Warning($"View에서 ID가 '{itemId}'인 메뉴 아이템 GameObject를 찾을 수 없어 View를 업데이트할 수 없습니다.");
|
||||
}
|
||||
}
|
||||
else if (view == null || !view.isActiveAndEnabled)
|
||||
{
|
||||
ULog.Warning("View가 유효하지 않거나 비활성화 상태이므로, 메뉴 아이템 버튼의 상호작용 상태를 업데이트할 수 없습니다. 모델 데이터만 변경되었습니다.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ULog.Warning($"ID가 '{itemId}'인 메뉴 아이템을 모델에서 찾을 수 없어 활성화 상태를 변경할 수 없습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 제공된 메뉴 아이템 리스트(<paramref name="items"/>)와 그 하위 메뉴들을 재귀적으로 탐색하여
|
||||
/// 지정된 ID(<paramref name="itemId"/>)를 가진 <see cref="MenuItemData"/>를 찾습니다.
|
||||
/// </summary>
|
||||
/// <param name="items">검색을 시작할 메뉴 아이템 데이터 리스트입니다.</param>
|
||||
/// <param name="itemId">찾고자 하는 메뉴 아이템의 고유 ID입니다.</param>
|
||||
/// <returns>ID와 일치하는 <see cref="MenuItemData"/>를 찾으면 해당 객체를 반환하고, 찾지 못하면 null을 반환합니다.</returns>
|
||||
protected MenuItemData FindMenuItemRecursive(List<MenuItemData> items, string itemId)
|
||||
{
|
||||
if (items == null || string.IsNullOrEmpty(itemId)) return null;
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item.ItemId == itemId)
|
||||
{
|
||||
return item; // 현재 아이템이 찾던 아이템이면 반환
|
||||
}
|
||||
|
||||
// 현재 아이템에 하위 메뉴가 있다면, 하위 메뉴에 대해 재귀적으로 검색 수행
|
||||
if (item.SubMenuItems != null && item.SubMenuItems.Count > 0)
|
||||
{
|
||||
MenuItemData foundInSubMenu = FindMenuItemRecursive(item.SubMenuItems, itemId);
|
||||
if (foundInSubMenu != null)
|
||||
{
|
||||
return foundInSubMenu; // 하위 메뉴에서 찾았으면 반환
|
||||
}
|
||||
}
|
||||
}
|
||||
return null; // 현재 리스트 및 그 하위 리스트에서 아이템을 찾지 못함
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,24 +4,73 @@ using UVC.UI.Commands;
|
||||
|
||||
namespace UVC.UI.Menu
|
||||
{
|
||||
// 메뉴 아이템 하나를 나타내는 데이터 구조체 또는 클래스
|
||||
/// <summary>
|
||||
/// 메뉴 시스템에서 개별 메뉴 아이템을 나타내는 데이터 클래스입니다.
|
||||
/// 각 메뉴 아이템은 고유 ID, 표시 이름, 실행될 명령, 하위 메뉴 등을 가질 수 있습니다.
|
||||
/// </summary>
|
||||
public class MenuItemData
|
||||
{
|
||||
/// <summary>
|
||||
/// 메뉴 아이템의 고유 식별자입니다.
|
||||
/// </summary>
|
||||
public string ItemId { get; private set; }
|
||||
public string DisplayNameKey { get; private set; } // 다국어 처리를 위한 키
|
||||
public ICommand Command { get; private set; } // Action 대신 ICommand 사용
|
||||
public List<MenuItemData> SubMenuItems { get; private set; } // 하위 메뉴 아이템 목록
|
||||
public bool IsSeparator { get; private set; } // 구분선 여부 플래그
|
||||
|
||||
public MenuItemData(string itemId, string displayNameKey, ICommand command = null, List<MenuItemData> subMenuItems = null, bool isSeparator = false)
|
||||
/// <summary>
|
||||
/// UI에 표시될 메뉴 아이템의 이름에 대한 다국어 처리 키입니다.
|
||||
/// 이 키를 사용하여 실제 표시될 텍스트를 가져옵니다.
|
||||
/// </summary>
|
||||
public string DisplayNameKey { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 메뉴 아이템이 선택되었을 때 실행될 명령입니다.
|
||||
/// `ICommand` 인터페이스를 구현하는 객체여야 합니다.
|
||||
/// 실행할 동작이 없는 경우 null일 수 있습니다.
|
||||
/// </summary>
|
||||
public ICommand Command { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 이 메뉴 아이템에 속한 하위 메뉴 아이템들의 목록입니다.
|
||||
/// 하위 메뉴가 없는 경우 빈 리스트입니다.
|
||||
/// </summary>
|
||||
public List<MenuItemData> SubMenuItems { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 이 메뉴 아이템이 시각적인 구분선인지 여부를 나타냅니다.
|
||||
/// `true`이면 메뉴 UI에서 다른 아이템들과 구분되는 선으로 표시됩니다.
|
||||
/// </summary>
|
||||
public bool IsSeparator { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 메뉴 아이템의 활성화 상태를 나타냅니다.
|
||||
/// `false`일 경우 메뉴 아이템은 비활성화되어 사용자가 선택할 수 없습니다.
|
||||
/// 기본값은 `true` (활성화) 입니다.
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// `MenuItemData` 클래스의 새 인스턴스를 초기화합니다.
|
||||
/// </summary>
|
||||
/// <param name="itemId">메뉴 아이템의 고유 ID.</param>
|
||||
/// <param name="displayNameKey">표시 이름의 다국어 키.</param>
|
||||
/// <param name="command">실행할 명령 (선택 사항).</param>
|
||||
/// <param name="subMenuItems">하위 메뉴 아이템 목록 (선택 사항).</param>
|
||||
/// <param name="isSeparator">구분선 여부 (선택 사항, 기본값: false).</param>
|
||||
/// <param name="isEnabled">활성화 상태 (선택 사항, 기본값: true).</param>
|
||||
public MenuItemData(string itemId, string displayNameKey, ICommand command = null, List<MenuItemData> subMenuItems = null, bool isSeparator = false, bool isEnabled = true)
|
||||
{
|
||||
ItemId = itemId;
|
||||
DisplayNameKey = displayNameKey;
|
||||
Command = command;
|
||||
SubMenuItems = subMenuItems ?? new List<MenuItemData>();
|
||||
SubMenuItems = subMenuItems ?? new List<MenuItemData>(); // null인 경우 빈 리스트로 초기화
|
||||
IsSeparator = isSeparator;
|
||||
IsEnabled = isEnabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 메뉴 아이템에 하위 메뉴 아이템을 추가합니다.
|
||||
/// 만약 현재 아이템이 구분선(`IsSeparator`가 `true`)이라면, 하위 메뉴를 추가할 수 없습니다.
|
||||
/// </summary>
|
||||
/// <param name="subItem">추가할 하위 메뉴 아이템.</param>
|
||||
public void AddSubMenuItem(MenuItemData subItem)
|
||||
{
|
||||
if (IsSeparator) return; // 구분선에는 하위 메뉴를 추가할 수 없음
|
||||
@@ -29,12 +78,13 @@ namespace UVC.UI.Menu
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 구분선 역할을 하는 MenuItemData 객체를 생성합니다.
|
||||
/// 메뉴 UI에서 시각적인 구분선으로 사용될 `MenuItemData` 객체를 생성합니다.
|
||||
/// </summary>
|
||||
/// <param name="itemId">구분선의 고유 ID. null일 경우 GUID로 자동 생성됩니다.</param>
|
||||
/// <returns>구분선 MenuItemData 객체입니다.</returns>
|
||||
/// <param name="itemId">구분선의 고유 ID. null일 경우 GUID를 사용하여 자동으로 생성됩니다.</param>
|
||||
/// <returns>구분선 역할을 하는 새로운 `MenuItemData` 객체입니다.</returns>
|
||||
public static MenuItemData CreateSeparator(string itemId = null)
|
||||
{
|
||||
// 구분선은 특정 동작이나 표시 이름이 필요 없으므로, displayNameKey는 비워두고 command는 null로 설정합니다.
|
||||
return new MenuItemData(itemId ?? $"separator_{Guid.NewGuid()}", string.Empty, null, null, true);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
8
Assets/Scripts/UVC/UI/Modal.meta
Normal file
8
Assets/Scripts/UVC/UI/Modal.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a8e4ce5c773f99a4c9a68de7ffdf5b67
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
85
Assets/Scripts/UVC/UI/Modal/Alert.cs
Normal file
85
Assets/Scripts/UVC/UI/Modal/Alert.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UVC.Locale; // ConfirmButtonText의 기본값을 위해 추가
|
||||
|
||||
namespace UVC.UI.Modal
|
||||
{
|
||||
/// <summary>
|
||||
/// 간단한 알림 메시지를 표시하는 정적 클래스입니다.
|
||||
/// 확인 버튼만 있으며, 사용자의 확인을 기다립니다.
|
||||
/// </summary>
|
||||
public static class Alert
|
||||
{
|
||||
/// <summary>
|
||||
/// Alert 모달 프리팹의 기본 경로입니다.
|
||||
/// </summary>
|
||||
private const string DefaultAlertPrefabPath = "Prefabs/UI/Modal/Alert";
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 제목과 메시지로 알림창을 표시합니다.
|
||||
/// 사용자가 확인 버튼을 누를 때까지 기다립니다.
|
||||
/// </summary>
|
||||
/// <param name="title">알림창의 제목입니다.</param>
|
||||
/// <param name="message">알림창에 표시될 메시지입니다.</param>
|
||||
/// <param name="confirmButtonText">확인 버튼에 표시될 텍스트입니다. null일 경우 LocalizationManager에서 "modal_confirm_button" 키로 조회합니다.</param>
|
||||
/// <param name="customPrefabPath">사용자 정의 알림 프리팹 경로입니다. null일 경우 기본 경로를 사용합니다.</param>
|
||||
/// <returns>모달이 닫힐 때 완료되는 UniTask입니다.</returns>
|
||||
public static async UniTask Show(
|
||||
string title,
|
||||
string message,
|
||||
string confirmButtonText = null,
|
||||
string customPrefabPath = null)
|
||||
{
|
||||
string prefabPath = string.IsNullOrEmpty(customPrefabPath) ? DefaultAlertPrefabPath : customPrefabPath;
|
||||
|
||||
// ModalContent 설정
|
||||
ModalContent content = new ModalContent(prefabPath)
|
||||
{
|
||||
Title = title,
|
||||
Message = message,
|
||||
ShowCancelButton = false, // Alert에서는 취소 버튼 숨김
|
||||
// ConfirmButtonText는 null이면 ModalContent의 getter가 LocalizationManager를 사용함
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(confirmButtonText))
|
||||
{
|
||||
content.ConfirmButtonText = confirmButtonText;
|
||||
}
|
||||
// else인 경우, ModalContent의 ConfirmButtonText getter가
|
||||
// LocalizationManager.Instance.GetString("modal_confirm_button")을 사용합니다.
|
||||
// 만약 이 키가 아닌 다른 키를 기본값으로 사용하고 싶다면 여기서 설정할 수 있습니다.
|
||||
// 예: content.ConfirmButtonText = LocalizationManager.Instance.GetString("alert_ok_button");
|
||||
|
||||
// Modal.Open<T> 호출. Alert은 별도의 결과를 반환하지 않으므로 T는 bool 또는 object 같은 기본 타입을 사용할 수 있습니다.
|
||||
// 여기서는 bool을 사용하고, 확인 버튼은 true를 반환하도록 Modal 시스템이 되어있다고 가정합니다.
|
||||
// 실제 반환값은 사용하지 않으므로, UniTask<bool>을 받고 무시합니다.
|
||||
await Modal.Open<bool>(content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 다국어 키를 사용하여 제목과 메시지를 표시하는 알림창을 엽니다.
|
||||
/// </summary>
|
||||
/// <param name="titleLocalizationKey">제목으로 사용할 다국어 키입니다.</param>
|
||||
/// <param name="messageLocalizationKey">메시지로 사용할 다국어 키입니다.</param>
|
||||
/// <param name="confirmButtonLocalizationKey">확인 버튼 텍스트로 사용할 다국어 키입니다. null일 경우 "modal_confirm_button"을 사용합니다.</param>
|
||||
/// <param name="customPrefabPath">사용자 정의 알림 프리팹 경로입니다. null일 경우 기본 경로를 사용합니다.</param>
|
||||
/// <returns>모달이 닫힐 때 완료되는 UniTask입니다.</returns>
|
||||
public static async UniTask ShowLocalized(
|
||||
string titleLocalizationKey,
|
||||
string messageLocalizationKey,
|
||||
string confirmButtonLocalizationKey = null,
|
||||
string customPrefabPath = null)
|
||||
{
|
||||
string title = LocalizationManager.Instance.GetString(titleLocalizationKey);
|
||||
string message = LocalizationManager.Instance.GetString(messageLocalizationKey);
|
||||
string confirmText = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(confirmButtonLocalizationKey))
|
||||
{
|
||||
confirmText = LocalizationManager.Instance.GetString(confirmButtonLocalizationKey);
|
||||
}
|
||||
// confirmText가 null이면 Show 메서드 내부에서 ModalContent의 기본 로직(modal_confirm_button 키 사용)이 적용됩니다.
|
||||
|
||||
await Show(title, message, confirmText, customPrefabPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/UI/Modal/Alert.cs.meta
Normal file
2
Assets/Scripts/UVC/UI/Modal/Alert.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb7546746a9d25c40b1e9cf0bdda05c1
|
||||
359
Assets/Scripts/UVC/UI/Modal/Modal.cs
Normal file
359
Assets/Scripts/UVC/UI/Modal/Modal.cs
Normal file
@@ -0,0 +1,359 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using System; // System.Type 사용을 위해 추가
|
||||
using System.Threading;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UVC.Log;
|
||||
|
||||
namespace UVC.UI.Modal
|
||||
{
|
||||
public static class Modal
|
||||
{
|
||||
private static GameObject currentModalInstance;
|
||||
private static GameObject currentBlockerInstance;
|
||||
private static IUniTaskSource activeTcs;
|
||||
private static Type activeResultType;
|
||||
|
||||
private const string DefaultBlockerPrefabPath = "Prefabs/UI/Modal/ModalBlocker";
|
||||
|
||||
public static async UniTask<T> Open<T>(ModalContent content, string blockerPrefabPath = DefaultBlockerPrefabPath)
|
||||
{
|
||||
if (currentModalInstance != null)
|
||||
{
|
||||
ULog.Warning("[Modal] 다른 모달이 이미 열려있습니다. 새 모달을 열기 전에 기존 모달을 닫아주세요.");
|
||||
return default(T);
|
||||
}
|
||||
|
||||
var tcs = new UniTaskCompletionSource<T>();
|
||||
activeTcs = tcs;
|
||||
activeResultType = typeof(T);
|
||||
|
||||
CanvasGroup blockerCanvasGroup = null;
|
||||
GameObject blockerPrefabObj = await Resources.LoadAsync<GameObject>(blockerPrefabPath) as GameObject;
|
||||
if (blockerPrefabObj != null)
|
||||
{
|
||||
Canvas mainCanvasForBlocker = UnityEngine.Object.FindFirstObjectByType<Canvas>();
|
||||
if (mainCanvasForBlocker != null)
|
||||
{
|
||||
currentBlockerInstance = UnityEngine.Object.Instantiate(blockerPrefabObj, mainCanvasForBlocker.transform);
|
||||
currentBlockerInstance.transform.SetAsLastSibling();
|
||||
blockerCanvasGroup = currentBlockerInstance.GetComponent<CanvasGroup>();
|
||||
if (blockerCanvasGroup == null) blockerCanvasGroup = currentBlockerInstance.AddComponent<CanvasGroup>();
|
||||
blockerCanvasGroup.alpha = 0f;
|
||||
_ = FadeUI(blockerCanvasGroup, 0.7f, 0.3f, true);
|
||||
}
|
||||
else ULog.Error("[Modal] UIBlocker를 표시할 Canvas를 찾을 수 없습니다.");
|
||||
}
|
||||
else ULog.Warning($"[Modal] UIBlocker 프리팹을 다음 경로에서 찾을 수 없습니다: {blockerPrefabPath}");
|
||||
|
||||
GameObject modalPrefabObj = await Resources.LoadAsync<GameObject>(content.PrefabPath) as GameObject;
|
||||
if (modalPrefabObj == null)
|
||||
{
|
||||
ULog.Error($"[Modal] 모달 프리팹을 다음 경로에서 찾을 수 없습니다: {content.PrefabPath}");
|
||||
await CleanupCurrentModalResources(null, false); // ModalView가 없으므로 null 전달
|
||||
tcs.TrySetResult(default(T));
|
||||
return await tcs.Task;
|
||||
}
|
||||
|
||||
Canvas mainCanvasForModal = UnityEngine.Object.FindObjectOfType<Canvas>();
|
||||
if (mainCanvasForModal == null)
|
||||
{
|
||||
ULog.Error("[Modal] 모달을 표시할 Canvas를 찾을 수 없습니다.");
|
||||
await CleanupCurrentModalResources(null, false);
|
||||
tcs.TrySetResult(default(T));
|
||||
return await tcs.Task;
|
||||
}
|
||||
|
||||
currentModalInstance = UnityEngine.Object.Instantiate(modalPrefabObj, mainCanvasForModal.transform);
|
||||
currentModalInstance.transform.SetAsLastSibling();
|
||||
|
||||
ModalView modalView = currentModalInstance.GetComponent<ModalView>();
|
||||
if (modalView == null)
|
||||
{
|
||||
ULog.Error($"[Modal] 모달 프리팹에 ModalView 컴포넌트가 없습니다: {content.PrefabPath}");
|
||||
await CleanupCurrentModalResources(null, false);
|
||||
tcs.TrySetResult(default(T));
|
||||
return await tcs.Task;
|
||||
}
|
||||
|
||||
|
||||
|
||||
var modalDestroyToken = currentModalInstance.GetCancellationTokenOnDestroy();
|
||||
modalDestroyToken.RegisterWithoutCaptureExecutionContext(async () => {
|
||||
if (Modal.activeTcs == tcs && tcs.Task.Status == UniTaskStatus.Pending)
|
||||
{
|
||||
ULog.Debug("[Modal] 활성 모달 인스턴스가 외부에서 파괴되어 취소로 처리합니다.");
|
||||
// modalView 참조가 유효하다면 전달, 아니면 null
|
||||
ModalView viewOnDestroy = currentModalInstance != null ? currentModalInstance.GetComponent<ModalView>() : null;
|
||||
await CleanupCurrentModalResources(viewOnDestroy, false, true, tcs, typeof(T));
|
||||
}
|
||||
});
|
||||
|
||||
if (modalView.titleText != null) modalView.titleText.text = content.Title;
|
||||
if (modalView.messageText != null) modalView.messageText.text = content.Message;
|
||||
|
||||
SetupButton(modalView.confirmButton, modalView.confirmButtonText, content.ConfirmButtonText, content.ShowConfirmButton,
|
||||
() => _ = HandleModalActionAsync(tcs, true, modalView));
|
||||
|
||||
SetupButton(modalView.cancelButton, modalView.cancelButtonText, content.CancelButtonText, content.ShowCancelButton,
|
||||
() => _ = HandleModalActionAsync(tcs, false, modalView));
|
||||
|
||||
SetupButton(modalView.closeButton, null, null, true,
|
||||
() => _ = HandleModalActionAsync(tcs, false, modalView));
|
||||
|
||||
// OnOpen 호출
|
||||
modalView.OnOpen();
|
||||
|
||||
return await tcs.Task;
|
||||
}
|
||||
|
||||
private static void SetupButton(Button button, TMPro.TextMeshProUGUI buttonTextComponent, string text, bool showButton, Action onClickAction)
|
||||
{
|
||||
if (button == null) return;
|
||||
button.gameObject.SetActive(showButton);
|
||||
if (showButton)
|
||||
{
|
||||
if (buttonTextComponent != null && !string.IsNullOrEmpty(text)) buttonTextComponent.text = text;
|
||||
button.onClick.RemoveAllListeners();
|
||||
button.onClick.AddListener(() => onClickAction?.Invoke());
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetButtonsInteractable(ModalView view, bool interactable)
|
||||
{
|
||||
if (view == null) return;
|
||||
if (view.confirmButton != null) view.confirmButton.interactable = interactable;
|
||||
if (view.cancelButton != null) view.cancelButton.interactable = interactable;
|
||||
if (view.closeButton != null) view.closeButton.interactable = interactable;
|
||||
}
|
||||
|
||||
private static async UniTaskVoid HandleModalActionAsync<T>(
|
||||
UniTaskCompletionSource<T> tcs,
|
||||
bool isConfirmAction,
|
||||
ModalView modalViewContext)
|
||||
{
|
||||
if (tcs != activeTcs || currentModalInstance == null || modalViewContext == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetButtonsInteractable(modalViewContext, false);
|
||||
|
||||
// GetResult 호출 시점 변경: CleanupCurrentModalResources 내부에서 호출하도록 변경하거나,
|
||||
// 여기서 결과를 받고 Cleanup에는 결과값 자체를 넘겨주는 방식도 고려 가능.
|
||||
// 현재는 Cleanup 내부에서 ModalView를 통해 GetResult를 호출하도록 유지.
|
||||
await CleanupCurrentModalResources(modalViewContext, isConfirmAction, false, tcs, typeof(T));
|
||||
}
|
||||
|
||||
private static async UniTask CleanupCurrentModalResources(
|
||||
ModalView modalViewToClose, // ModalView 인스턴스 전달
|
||||
bool confirmOrCancel,
|
||||
bool wasExternallyDestroyed = false,
|
||||
IUniTaskSource tcsToResolve = null,
|
||||
Type resultTypeForTcs = null)
|
||||
{
|
||||
var blockerInstanceToDestroy = currentBlockerInstance;
|
||||
var modalInstanceToDestroy = currentModalInstance; // 이 시점의 currentModalInstance 사용
|
||||
|
||||
if (tcsToResolve == null) tcsToResolve = activeTcs;
|
||||
if (resultTypeForTcs == null) resultTypeForTcs = activeResultType;
|
||||
|
||||
currentModalInstance = null;
|
||||
currentBlockerInstance = null;
|
||||
activeTcs = null;
|
||||
activeResultType = null;
|
||||
|
||||
if (blockerInstanceToDestroy != null)
|
||||
{
|
||||
var blockerCG = blockerInstanceToDestroy.GetComponent<CanvasGroup>();
|
||||
if (blockerCG != null) await FadeUI(blockerCG, 0f, 0.3f, false);
|
||||
UnityEngine.Object.Destroy(blockerInstanceToDestroy);
|
||||
}
|
||||
|
||||
if (tcsToResolve != null && tcsToResolve.GetStatus(0) == UniTaskStatus.Pending)
|
||||
{
|
||||
if (resultTypeForTcs == typeof(bool))
|
||||
{
|
||||
if (tcsToResolve is UniTaskCompletionSource<bool> boolCompletionSource)
|
||||
{
|
||||
boolCompletionSource.TrySetResult(confirmOrCancel);
|
||||
}
|
||||
}
|
||||
else // bool이 아닌 다른 타입의 경우 ModalView.GetResult() 사용
|
||||
{
|
||||
object resultFromView = null;
|
||||
if (modalViewToClose != null) // modalViewToClose가 유효할 때만 GetResult 호출
|
||||
{
|
||||
resultFromView = modalViewToClose.GetResult();
|
||||
}
|
||||
|
||||
if (tcsToResolve.GetType().IsGenericType &&
|
||||
tcsToResolve.GetType().GetGenericTypeDefinition() == typeof(UniTaskCompletionSource<>))
|
||||
{
|
||||
var genericArg = tcsToResolve.GetType().GetGenericArguments()[0];
|
||||
if (genericArg == resultTypeForTcs)
|
||||
{
|
||||
object resultToSet;
|
||||
if (resultFromView != null && resultTypeForTcs.IsAssignableFrom(resultFromView.GetType()))
|
||||
{
|
||||
resultToSet = resultFromView;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (resultFromView != null) // 타입은 안맞지만 null은 아닐 때 경고
|
||||
{
|
||||
ULog.Warning($"[Modal] GetResult() 반환 타입({resultFromView.GetType()})과 모달 결과 타입({resultTypeForTcs})이 일치하지 않습니다. 기본값을 사용합니다.");
|
||||
}
|
||||
resultToSet = GetDefault(resultTypeForTcs);
|
||||
}
|
||||
|
||||
var trySetResultHelperMethod = typeof(Modal).GetMethod(nameof(TrySetGenericResultHelper), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)
|
||||
.MakeGenericMethod(resultTypeForTcs);
|
||||
trySetResultHelperMethod.Invoke(null, new object[] { tcsToResolve, resultToSet });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (modalInstanceToDestroy != null) // modalInstanceToDestroy는 Cleanup 시작 시점의 currentModalInstance
|
||||
{
|
||||
// OnClose 호출
|
||||
// modalViewToClose가 null일 수 있으므로, modalInstanceToDestroy에서 다시 가져오거나 확인
|
||||
ModalView viewForOnClose = modalInstanceToDestroy.GetComponent<ModalView>();
|
||||
if (viewForOnClose != null)
|
||||
{
|
||||
viewForOnClose.OnClose();
|
||||
}
|
||||
if (!wasExternallyDestroyed)
|
||||
{
|
||||
UnityEngine.Object.Destroy(modalInstanceToDestroy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper for setting generic result
|
||||
private static void TrySetGenericResultHelper<T>(IUniTaskSource tcs, object result)
|
||||
{
|
||||
if (tcs is UniTaskCompletionSource<T> genericTcs)
|
||||
{
|
||||
try
|
||||
{
|
||||
genericTcs.TrySetResult((T)result);
|
||||
}
|
||||
catch (InvalidCastException ex)
|
||||
{
|
||||
ULog.Error($"[Modal] 결과를 {typeof(T)}로 캐스팅하는데 실패했습니다: {ex.Message}. 기본값을 사용합니다.", ex);
|
||||
genericTcs.TrySetResult(default(T));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static async UniTask Close()
|
||||
{
|
||||
if (currentModalInstance == null && activeTcs == null) return;
|
||||
|
||||
IUniTaskSource tcsToClose = activeTcs;
|
||||
Type resultTypeToClose = activeResultType;
|
||||
ModalView view = currentModalInstance?.GetComponent<ModalView>();
|
||||
|
||||
if (view != null) SetButtonsInteractable(view, false);
|
||||
|
||||
var localModalInstance = currentModalInstance;
|
||||
var localBlockerInstance = currentBlockerInstance;
|
||||
|
||||
currentModalInstance = null;
|
||||
currentBlockerInstance = null;
|
||||
activeTcs = null;
|
||||
activeResultType = null;
|
||||
|
||||
if (tcsToClose != null && tcsToClose.GetStatus(0) == UniTaskStatus.Pending)
|
||||
{
|
||||
if (resultTypeToClose == typeof(bool) && tcsToClose is UniTaskCompletionSource<bool> boolTcs)
|
||||
{
|
||||
boolTcs.TrySetResult(false); // Close는 항상 false(취소)로 간주
|
||||
}
|
||||
else if (resultTypeToClose != null && tcsToClose.GetType().IsGenericType && tcsToClose.GetType().GetGenericTypeDefinition() == typeof(UniTaskCompletionSource<>))
|
||||
{
|
||||
// Close 시에는 GetResult()를 사용하지 않고 기본값(취소)으로 처리
|
||||
var genericArg = tcsToClose.GetType().GetGenericArguments()[0];
|
||||
if (genericArg == resultTypeToClose)
|
||||
{
|
||||
var trySetDefaultResultMethod = typeof(Modal).GetMethod(nameof(TrySetDefaultResultHelper), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)
|
||||
.MakeGenericMethod(genericArg);
|
||||
trySetDefaultResultMethod.Invoke(null, new object[] { tcsToClose });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (localBlockerInstance != null)
|
||||
{
|
||||
var blockerCG = localBlockerInstance.GetComponent<CanvasGroup>();
|
||||
if (blockerCG != null) await FadeUI(blockerCG, 0f, 0.3f, false);
|
||||
UnityEngine.Object.Destroy(localBlockerInstance);
|
||||
}
|
||||
|
||||
if (localModalInstance != null)
|
||||
{
|
||||
// OnClose 호출
|
||||
ModalView viewForOnClose = localModalInstance.GetComponent<ModalView>();
|
||||
if (viewForOnClose != null)
|
||||
{
|
||||
viewForOnClose.SendMessage("OnClose", SendMessageOptions.DontRequireReceiver);
|
||||
}
|
||||
UnityEngine.Object.Destroy(localModalInstance);
|
||||
}
|
||||
}
|
||||
|
||||
private static void TrySetDefaultResultHelper<T>(IUniTaskSource tcs)
|
||||
{
|
||||
if (tcs is UniTaskCompletionSource<T> genericTcs)
|
||||
{
|
||||
genericTcs.TrySetResult(default(T));
|
||||
}
|
||||
}
|
||||
|
||||
private static async UniTask FadeUI(CanvasGroup canvasGroup, float targetAlpha, float duration, bool fadeIn)
|
||||
{
|
||||
if (canvasGroup == null) return;
|
||||
CancellationToken cancellationToken;
|
||||
try
|
||||
{
|
||||
cancellationToken = canvasGroup.GetCancellationTokenOnDestroy();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ULog.Error($"[Modal] CanvasGroup에 대한 취소 토큰을 가져오는 중 오류 발생: {ex.Message}", ex);
|
||||
return;
|
||||
}
|
||||
float startAlpha = canvasGroup.alpha;
|
||||
float time = 0;
|
||||
if (fadeIn)
|
||||
{
|
||||
canvasGroup.interactable = true;
|
||||
canvasGroup.blocksRaycasts = true;
|
||||
}
|
||||
while (time < duration)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested || canvasGroup == null) return;
|
||||
canvasGroup.alpha = Mathf.Lerp(startAlpha, targetAlpha, time / duration);
|
||||
time += Time.deltaTime;
|
||||
await UniTask.Yield(PlayerLoopTiming.Update, cancellationToken).SuppressCancellationThrow();
|
||||
}
|
||||
if (cancellationToken.IsCancellationRequested || canvasGroup == null) return;
|
||||
canvasGroup.alpha = targetAlpha;
|
||||
if (!fadeIn)
|
||||
{
|
||||
canvasGroup.interactable = false;
|
||||
canvasGroup.blocksRaycasts = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static object GetDefault(Type type)
|
||||
{
|
||||
if (type == null) return null;
|
||||
if (type.IsValueType) return Activator.CreateInstance(type);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/UI/Modal/Modal.cs.meta
Normal file
2
Assets/Scripts/UVC/UI/Modal/Modal.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5d673bf3a867f5048bd67b6759160ae2
|
||||
123
Assets/Scripts/UVC/UI/Modal/ModalContent.cs
Normal file
123
Assets/Scripts/UVC/UI/Modal/ModalContent.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using UVC.Locale; // LocalizationManager 사용을 위해 추가
|
||||
|
||||
namespace UVC.UI.Modal
|
||||
{
|
||||
public class ModalContent
|
||||
{
|
||||
/// <summary>
|
||||
/// 모달 창의 제목입니다. 다국어 지원을 위해 직접 설정하거나, 생성 후 별도로 설정할 수 있습니다.
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 모달 창에 표시될 메시지 또는 본문 내용입니다. 다국어 지원을 위해 직접 설정하거나, 생성 후 별도로 설정할 수 있습니다.
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
|
||||
private string _confirmButtonText;
|
||||
/// <summary>
|
||||
/// 확인 버튼에 표시될 텍스트입니다.
|
||||
/// 기본적으로 "modal_confirm_button" 키를 사용하여 <see cref="LocalizationManager"/>에서 번역된 문자열을 가져옵니다.
|
||||
/// 직접 설정하여 이 기본값을 재정의할 수 있습니다.
|
||||
/// </summary>
|
||||
public string ConfirmButtonText
|
||||
{
|
||||
get
|
||||
{
|
||||
// _confirmButtonText가 명시적으로 설정되지 않았고, LocalizationManager 사용이 가능하다면 번역된 문자열을 반환
|
||||
if (string.IsNullOrEmpty(_confirmButtonText) && LocalizationManager.Instance != null)
|
||||
{
|
||||
return LocalizationManager.Instance.GetString("button_confirm");
|
||||
}
|
||||
// 명시적으로 설정된 값이 있거나 LocalizationManager를 사용할 수 없다면 해당 값 또는 기본 키 반환
|
||||
return !string.IsNullOrEmpty(_confirmButtonText) ? _confirmButtonText : "[button_confirm]";
|
||||
}
|
||||
set => _confirmButtonText = value;
|
||||
}
|
||||
|
||||
private string _cancelButtonText;
|
||||
/// <summary>
|
||||
/// 취소 버튼에 표시될 텍스트입니다.
|
||||
/// 기본적으로 "modal_cancel_button" 키를 사용하여 <see cref="LocalizationManager"/>에서 번역된 문자열을 가져옵니다.
|
||||
/// 직접 설정하여 이 기본값을 재정의할 수 있습니다.
|
||||
/// </summary>
|
||||
public string CancelButtonText
|
||||
{
|
||||
get
|
||||
{
|
||||
// _cancelButtonText가 명시적으로 설정되지 않았고, LocalizationManager 사용이 가능하다면 번역된 문자열을 반환
|
||||
if (string.IsNullOrEmpty(_cancelButtonText) && LocalizationManager.Instance != null)
|
||||
{
|
||||
return LocalizationManager.Instance.GetString("button_cancel");
|
||||
}
|
||||
// 명시적으로 설정된 값이 있거나 LocalizationManager를 사용할 수 없다면 해당 값 또는 기본 키 반환
|
||||
return !string.IsNullOrEmpty(_cancelButtonText) ? _cancelButtonText : "[button_cancel]";
|
||||
}
|
||||
set => _cancelButtonText = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 확인 버튼 표시 여부를 결정합니다. (기본값: true)
|
||||
/// </summary>
|
||||
public bool ShowConfirmButton { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 취소 버튼 표시 여부를 결정합니다. (기본값: true)
|
||||
/// </summary>
|
||||
public bool ShowCancelButton { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 모달 콘텐츠로 사용될 프리팹의 경로입니다.
|
||||
/// </summary>
|
||||
public string PrefabPath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ModalContent 생성자입니다.
|
||||
/// </summary>
|
||||
/// <param name="prefabPath">로드할 프리팹의 경로입니다. 이 프리팹 내부에 UI 요소들이 구성되어 있어야 합니다.</param>
|
||||
public ModalContent(string prefabPath)
|
||||
{
|
||||
PrefabPath = prefabPath;
|
||||
// Title, Message 등 다른 텍스트 속성도 필요한 경우 여기서 기본 다국어 키를 사용하여 초기화할 수 있습니다.
|
||||
// 예: Title = LocalizationManager.Instance.GetString("default_modal_title");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 다국어 키를 사용하여 확인 버튼 텍스트를 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="localizationKey">사용할 다국어 키입니다.</param>
|
||||
public void SetConfirmButtonTextFromKey(string localizationKey)
|
||||
{
|
||||
// 직접 값을 설정하는 대신, 키를 저장하고 getter에서 처리하도록 할 수도 있으나,
|
||||
// 현재 구조에서는 setter를 통해 LocalizationManager.GetString을 호출하는 것이 더 간단합니다.
|
||||
// 다만, 이 경우 인스턴스 생성 시점의 언어에 고정될 수 있으므로,
|
||||
// 아래와 같이 getter에서 매번 가져오도록 하는 것이 언어 변경에 동적으로 반응합니다.
|
||||
// 여기서는 명시적 설정을 위해 _confirmButtonText를 사용합니다.
|
||||
// 만약 항상 최신 언어를 반영해야 한다면, _confirmButtonTextKey 와 같은 필드를 두고 getter에서 처리해야 합니다.
|
||||
if (LocalizationManager.Instance != null)
|
||||
{
|
||||
_confirmButtonText = LocalizationManager.Instance.GetString(localizationKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
_confirmButtonText = $"[{localizationKey}]";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 다국어 키를 사용하여 취소 버튼 텍스트를 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="localizationKey">사용할 다국어 키입니다.</param>
|
||||
public void SetCancelButtonTextFromKey(string localizationKey)
|
||||
{
|
||||
if (LocalizationManager.Instance != null)
|
||||
{
|
||||
_cancelButtonText = LocalizationManager.Instance.GetString(localizationKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
_cancelButtonText = $"[{localizationKey}]";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/UI/Modal/ModalContent.cs.meta
Normal file
2
Assets/Scripts/UVC/UI/Modal/ModalContent.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 37487f58ea28e0c429269605c7d4e016
|
||||
72
Assets/Scripts/UVC/UI/Modal/ModalView.cs
Normal file
72
Assets/Scripts/UVC/UI/Modal/ModalView.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UVC.Log;
|
||||
|
||||
namespace UVC.UI.Modal
|
||||
{
|
||||
/// <summary>
|
||||
/// 모달 프리팹에 부착되어 UI 요소들을 관리하는 컴포넌트입니다.
|
||||
/// </summary>
|
||||
public class ModalView : MonoBehaviour
|
||||
{
|
||||
[Header("UI Elements")]
|
||||
public TextMeshProUGUI titleText;
|
||||
public TextMeshProUGUI messageText;
|
||||
public Button confirmButton;
|
||||
public TextMeshProUGUI confirmButtonText; // confirmButton 내의 Text 컴포넌트
|
||||
public Button cancelButton;
|
||||
public TextMeshProUGUI cancelButtonText; // cancelButton 내의 Text 컴포넌트
|
||||
public Button closeButton; // 닫기 버튼
|
||||
|
||||
// 필요에 따라 다른 UI 요소들을 추가할 수 있습니다.
|
||||
// 예: public Image backgroundImage;
|
||||
// 예: public InputField inputField;
|
||||
|
||||
// Modal.cs에서 호출 가능하도록 public으로 변경 (또는 protected internal)
|
||||
public virtual void OnOpen() // virtual로 변경하여 하위 클래스에서 재정의 가능하도록 함
|
||||
{
|
||||
// 모달이 열릴 때 실행할 로직
|
||||
ULog.Debug($"[ModalView] {gameObject.name} OnOpen called.");
|
||||
//confirmButton, cancelButton 버튼 위치 가운 대로 설정.
|
||||
if (confirmButton.IsActive() && !cancelButton.IsActive())
|
||||
{
|
||||
RectTransform confirmButtonRect = confirmButton.GetComponent<RectTransform>();
|
||||
if (confirmButtonRect != null)
|
||||
{
|
||||
confirmButtonRect.anchoredPosition = new Vector2(0, confirmButtonRect.anchoredPosition.y);
|
||||
}
|
||||
}
|
||||
else if (!confirmButton.IsActive() && cancelButton.IsActive())
|
||||
{
|
||||
RectTransform cancelButtonRect = cancelButton.GetComponent<RectTransform>();
|
||||
if (cancelButtonRect != null)
|
||||
{
|
||||
cancelButtonRect.anchoredPosition = new Vector2(0, cancelButtonRect.anchoredPosition.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Modal.cs에서 호출 가능하도록 public으로 변경 (또는 protected internal)
|
||||
public virtual void OnClose() // virtual로 변경하여 하위 클래스에서 재정의 가능하도록 함
|
||||
{
|
||||
// 모달이 닫힐 때 실행할 로직
|
||||
ULog.Debug($"[ModalView] {gameObject.name} OnClose called.");
|
||||
}
|
||||
|
||||
public virtual object GetResult() // virtual로 변경하여 하위 클래스에서 재정의 가능하도록 함
|
||||
{
|
||||
// 기본적으로는 null을 반환. 특정 모달 뷰에서 이 메서드를 재정의하여
|
||||
// 입력 필드의 값, 선택된 항목 등 구체적인 결과 데이터를 반환하도록 구현합니다.
|
||||
// 예를 들어, 확인 버튼 시 true, 취소 버튼 시 false를 반환하도록 할 수도 있습니다.
|
||||
// 이 값은 Modal.Open<T>의 T 타입으로 변환 시도됩니다.
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/UI/Modal/ModalView.cs.meta
Normal file
2
Assets/Scripts/UVC/UI/Modal/ModalView.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d70c9296cdb5be34d9c934fbdd52bdae
|
||||
Reference in New Issue
Block a user