주석추가

This commit is contained in:
김형인
2025-06-18 00:44:12 +09:00
parent e897c911bb
commit f0ae7b7696
5 changed files with 402 additions and 17 deletions

View File

@@ -5,8 +5,51 @@ using UVC.UI.ToolBar.View;
namespace UVC.UI.Toolbar.View
{
/// <summary>
/// 확장 가능한 툴바 버튼(ToolbarExpandableButton)의 UI 생성, 이벤트 연결, 시각적 업데이트를 처리하는 클래스입니다.
/// IButtonViewProcessor 인터페이스를 구현하여 ToolbarView가 확장 버튼을 일관된 방식으로 다룰 수 있도록 합니다.
/// 확장 버튼은 클릭 시 하위 메뉴(SubButtons)를 표시하거나 숨기는 기능을 가집니다.
/// </summary>
/// <remarks>
/// 이 클래스의 주요 역할:
/// 1. UI 생성: ToolbarView에 설정된 '확장 버튼 프리팹'을 사용하여 주 버튼의 GameObject를 만듭니다.
/// 2. 상호작용 설정: 생성된 주 버튼 UI(UnityEngine.UI.Button)가 클릭되었을 때,
/// - 연결된 ToolbarExpandableButton 모델의 ExecuteClick 메서드가 호출되도록 설정합니다.
/// - ToolbarView의 ToggleSubMenu 메서드를 호출하여 하위 메뉴의 표시/숨김을 처리하도록 합니다.
/// 3. 시각적 업데이트: 주 버튼 모델의 상태(텍스트, 아이콘, 활성화 상태 등)가 변경되면,
/// 화면에 보이는 주 버튼의 모습도 그에 맞게 업데이트합니다. 하위 메뉴 자체의 렌더링은 ToolbarView가 담당합니다.
/// </remarks>
/// <example>
/// <code>
/// // ToolbarView 내에서 이 프로세서가 사용되는 방식 (간략화된 예시):
/// // 1. ToolbarModel로부터 ToolbarExpandableButton 객체를 가져옵니다.
/// // ToolbarExpandableButton expandableModel = new ToolbarExpandableButton { Text = "Brush", ... };
/// // expandableModel.SubButtons.Add(new ToolbarStandardButton { Text = "Small Brush", ... });
///
/// // 2. ToolbarView는 해당 모델 타입에 맞는 Processor를 찾습니다.
/// // IButtonViewProcessor processor = GetButtonViewProcessor(typeof(ToolbarExpandableButton));
///
/// // 3. UI 생성 요청 (주 버튼만 생성)
/// // GameObject buttonUI = processor.CreateButtonUI(expandableModel, toolbarContainer, this);
///
/// // 4. 상호작용 및 초기 시각적 요소 설정 요청
/// // processor.SetupButtonInteractions(expandableModel, buttonUI, this);
/// // 이 과정에서 주 버튼 클릭 시 expandableModel.ExecuteClick()과 viewContext.ToggleSubMenu()가 연결됩니다.
///
/// // 5. 주 버튼 모델의 상태가 변경되면(예: 하위 버튼 선택으로 아이콘 변경 시),
/// // OnStateChanged 이벤트가 발생하고, ToolbarView는 processor.UpdateCommonButtonVisuals()를 호출합니다.
/// </code>
/// </example>
public class ToolbarExpandableButtonViewProcessor : IButtonViewProcessor
{
/// <summary>
/// ToolbarExpandableButton 모델에 해당하는 주 버튼 UI GameObject를 생성합니다.
/// ToolbarView에 있는 expandableButtonPrefab을 복제하여 사용합니다.
/// </summary>
/// <param name="buttonModel">UI를 생성할 기반이 되는 버튼 데이터 모델 (ToolbarExpandableButton으로 간주됨).</param>
/// <param name="parentContainer">생성된 버튼 UI가 자식으로 추가될 부모 Transform 객체입니다.</param>
/// <param name="viewContext">현재 ToolbarView의 인스턴스. 프리팹 참조 및 하위 메뉴 토글 기능에 접근할 때 사용됩니다.</param>
/// <returns>성공적으로 생성된 주 버튼의 GameObject. 프리팹이 없으면 null을 반환합니다.</returns>
public GameObject CreateButtonUI(ToolbarButtonBase buttonModel, Transform parentContainer, ToolbarView viewContext)
{
if (viewContext.expandableButtonPrefab == null)
@@ -17,6 +60,14 @@ namespace UVC.UI.Toolbar.View
return Object.Instantiate(viewContext.expandableButtonPrefab, parentContainer);
}
/// <summary>
/// 생성된 확장 버튼(주 버튼) UI GameObject에 필요한 상호작용을 설정하고 초기 시각적 상태를 업데이트합니다.
/// - 주 버튼 UI 클릭 시 모델의 ExecuteClick 메서드 호출 및 ToolbarView의 ToggleSubMenu 메서드 호출 설정.
/// - 모델의 초기 텍스트, 아이콘, 활성화 상태를 주 버튼 UI에 반영.
/// </summary>
/// <param name="buttonModel">설정 대상 버튼의 데이터 모델 (ToolbarExpandableButton으로 캐스팅하여 사용).</param>
/// <param name="buttonUIObject">화면에 표시된, 설정할 주 버튼의 UI GameObject.</param>
/// <param name="viewContext">현재 ToolbarView의 인스턴스.</param>
public void SetupButtonInteractions(ToolbarButtonBase buttonModel, GameObject buttonUIObject, ToolbarView viewContext)
{
ToolbarExpandableButton expandableModel = buttonModel as ToolbarExpandableButton;
@@ -25,10 +76,13 @@ namespace UVC.UI.Toolbar.View
Button uiButton = buttonUIObject.GetComponent<Button>();
if (uiButton != null)
{
// 주 버튼 클릭 시 두 가지 동작을 수행합니다:
// 1. 모델 자체의 클릭 로직 실행 (선택적)
// 2. ToolbarView를 통해 하위 메뉴를 열거나 닫음
uiButton.onClick.AddListener(() =>
{
expandableModel.ExecuteClick();
viewContext.ToggleSubMenu(expandableModel, buttonUIObject);
expandableModel.ExecuteClick(); // 모델의 기본 클릭 액션 실행
viewContext.ToggleSubMenu(expandableModel, buttonUIObject); // 하위 메뉴 표시/숨김 처리
});
}
else
@@ -38,14 +92,26 @@ namespace UVC.UI.Toolbar.View
UpdateCommonButtonVisuals(buttonModel, buttonUIObject, viewContext);
}
/// <summary>
/// 주 버튼 모델의 공통적인 시각적 속성(텍스트, 아이콘, 활성화 상태)이 변경되었을 때 UI를 업데이트합니다.
/// </summary>
public void UpdateCommonButtonVisuals(ToolbarButtonBase buttonModel, GameObject buttonUIObject, ToolbarView viewContext)
{
viewContext.InternalUpdateCommonButtonVisuals(buttonModel, buttonUIObject);
}
/// <summary>
/// 확장 버튼은 전통적인 의미의 '토글 선택 상태'를 가지지 않습니다.
/// 하위 메뉴의 표시 여부는 별도의 메커니즘(ToolbarView의 currentSubMenu)으로 관리되므로,
/// 이 메서드는 아무 작업도 수행하지 않습니다.
/// IButtonViewProcessor 인터페이스를 구현하기 위해 필요합니다.
/// </summary>
public void UpdateToggleStateVisuals(ToolbarToggleButton toggleButtonModel, GameObject buttonUIObject, bool isSelected, ToolbarView viewContext)
{
// 확장 버튼은 기본 토글 상태가 없음 (하위 메뉴 표시 여부는 다른 메커니즘으로 관리)
// 확장 버튼의 주 버튼 자체는 켜고 끄는 토글 상태를 직접적으로 가지지 않습니다.
// (예: IsSelected 같은 속성으로 UI가 바뀌지 않음)
// 하위 메뉴가 열려있는지 여부는 ToolbarView에서 관리합니다.
// 따라서 이 메서드는 비워둡니다.
}
}
}

View File

@@ -5,8 +5,55 @@ using UVC.UI.ToolBar.View;
namespace UVC.UI.Toolbar.View
{
/// <summary>
/// 라디오 툴바 버튼(ToolbarRadioButton)의 UI 생성, 이벤트 연결, 시각적 업데이트를 처리하는 클래스입니다.
/// IButtonViewProcessor 인터페이스를 구현하여 ToolbarView가 라디오 버튼을 일관된 방식으로 다룰 수 있도록 합니다.
/// 라디오 버튼은 그룹 내에서 단 하나만 선택될 수 있는 특징을 가집니다.
/// </summary>
/// <remarks>
/// 이 클래스의 주요 역할:
/// 1. UI 생성: ToolbarView에 설정된 '라디오 버튼 프리팹'을 사용하여 버튼의 GameObject를 만듭니다.
/// 이 프리팹에는 UnityEngine.UI.Toggle 컴포넌트가 포함되어 있어야 합니다.
/// 2. 상호작용 설정:
/// - 생성된 토글 UI를 적절한 UnityEngine.UI.ToggleGroup에 할당합니다. (ToolbarView의 GetOrCreateToggleGroup 사용)
/// - UI Toggle의 상태가 변경될 때(사용자가 클릭하여 선택 시), 연결된 ToolbarRadioButton 모델의 ExecuteClick 메서드가 호출되고,
/// 모델 및 같은 그룹 내 다른 라디오 버튼들의 IsSelected 상태가 업데이트되도록 합니다.
/// 3. 시각적 업데이트: 버튼 모델의 상태(텍스트, 아이콘, 활성화 상태, 선택 상태 등)가 변경되면,
/// 화면에 보이는 버튼의 모습(예: Toggle의 isOn 상태, 아이콘 변경 등)도 그에 맞게 업데이트합니다.
/// </remarks>
/// <example>
/// <code>
/// // ToolbarView 내에서 이 프로세서가 사용되는 방식 (간략화된 예시):
/// // 1. ToolbarModel로부터 ToolbarRadioButton 객체들을 가져옵니다. (같은 GroupName을 가져야 그룹으로 묶임)
/// // ToolbarRadioButton radioModel1 = new ToolbarRadioButton("ViewMode", "2D", true, ...);
/// // ToolbarRadioButton radioModel2 = new ToolbarRadioButton("ViewMode", "3D", false, ...);
///
/// // 2. ToolbarView는 각 모델 타입에 맞는 Processor를 찾습니다.
/// // IButtonViewProcessor processor = GetButtonViewProcessor(typeof(ToolbarRadioButton));
///
/// // 3. 각 라디오 버튼에 대해 UI 생성 요청
/// // GameObject radioUI1 = processor.CreateButtonUI(radioModel1, toolbarContainer, this);
/// // GameObject radioUI2 = processor.CreateButtonUI(radioModel2, toolbarContainer, this);
///
/// // 4. 각 라디오 버튼에 대해 상호작용 및 초기 시각적 요소 설정 요청
/// // processor.SetupButtonInteractions(radioModel1, radioUI1, this);
/// // processor.SetupButtonInteractions(radioModel2, radioUI2, this);
/// // 이 과정에서 각 UI Toggle이 같은 ToggleGroup에 할당되고, 초기 isOn 상태가 설정됩니다.
///
/// // 5. 모델의 상태가 변경되거나 사용자가 UI를 클릭하면, 관련 이벤트가 발생하고
/// // ToolbarView는 processor의 UpdateToggleStateVisuals() 또는 UpdateCommonButtonVisuals()를 호출하여 UI를 업데이트합니다.
/// </code>
/// </example>
public class ToolbarRadioButtonViewProcessor : IButtonViewProcessor
{
/// <summary>
/// ToolbarRadioButton 모델에 해당하는 UI GameObject를 생성합니다.
/// ToolbarView에 있는 radioButtonPrefab을 복제하여 사용합니다.
/// </summary>
/// <param name="buttonModel">UI를 생성할 기반이 되는 버튼 데이터 모델 (ToolbarRadioButton으로 간주됨).</param>
/// <param name="parentContainer">생성된 버튼 UI가 자식으로 추가될 부모 Transform 객체입니다.</param>
/// <param name="viewContext">현재 ToolbarView의 인스턴스. 프리팹 참조 및 ToggleGroup 관리에 사용됩니다.</param>
/// <returns>성공적으로 생성된 버튼의 GameObject. 프리팹이 없으면 null을 반환합니다.</returns>
public GameObject CreateButtonUI(ToolbarButtonBase buttonModel, Transform parentContainer, ToolbarView viewContext)
{
if (viewContext.radioButtonPrefab == null)
@@ -17,6 +64,16 @@ namespace UVC.UI.Toolbar.View
return Object.Instantiate(viewContext.radioButtonPrefab, parentContainer);
}
/// <summary>
/// 생성된 라디오 버튼 UI GameObject에 필요한 상호작용을 설정하고 초기 시각적 상태를 업데이트합니다.
/// - UI Toggle 컴포넌트를 가져와 모델의 GroupName에 해당하는 ToggleGroup에 할당합니다.
/// - UI Toggle의 상태 변경(onValueChanged) 시, 선택된 경우에만 모델의 ExecuteClick 메서드 호출 설정.
/// - 모델의 초기 IsSelected 상태를 UI Toggle의 isOn 상태에 반영.
/// - 모델의 초기 텍스트, 아이콘, 활성화 상태를 UI에 반영.
/// </summary>
/// <param name="buttonModel">설정 대상 버튼의 데이터 모델 (ToolbarRadioButton으로 캐스팅하여 사용).</param>
/// <param name="buttonUIObject">화면에 표시된, 설정할 버튼의 UI GameObject.</param>
/// <param name="viewContext">현재 ToolbarView의 인스턴스.</param>
public void SetupButtonInteractions(ToolbarButtonBase buttonModel, GameObject buttonUIObject, ToolbarView viewContext)
{
ToolbarRadioButton radioModel = buttonModel as ToolbarRadioButton;
@@ -25,14 +82,26 @@ namespace UVC.UI.Toolbar.View
Toggle toggleComponent = buttonUIObject.GetComponent<Toggle>();
if (toggleComponent != null)
{
// 모델의 GroupName을 사용하여 ToolbarView로부터 ToggleGroup을 가져오거나 생성합니다.
// 같은 GroupName을 가진 라디오 버튼들은 이 ToggleGroup에 의해 관리됩니다.
ToggleGroup uiToggleGroup = viewContext.GetOrCreateToggleGroup(radioModel.GroupName);
toggleComponent.group = uiToggleGroup;
toggleComponent.group = uiToggleGroup; // UI Toggle을 해당 그룹에 할당
// 모델의 현재 IsSelected 상태로 UI Toggle의 초기 상태를 설정합니다.
toggleComponent.SetIsOnWithoutNotify(radioModel.IsSelected);
// UI Toggle의 상태가 사용자에 의해 변경될 때 호출될 리스너를 추가합니다.
toggleComponent.onValueChanged.AddListener((isSelected) =>
{
// 라디오 버튼은 선택될 때(isSelected가 true일 때)만 동작을 수행합니다.
// 해제될 때는 다른 라디오 버튼이 선택되면서 자동으로 해제되므로 별도 처리가 필요 없습니다.
if (isSelected)
{
// 사용자가 UI를 클릭하여 이 라디오 버튼을 선택하면, 모델의 ExecuteClick()을 호출합니다.
// ToolbarRadioButton의 ExecuteClick() 내부에서 IsSelected 상태가 true로 설정되고,
// 같은 그룹의 다른 라디오 버튼들은 IsSelected가 false로 설정됩니다.
// 이후 관련 이벤트가 발생하여 UI가 동기화됩니다.
// 파라미터 true는 '선택됨'을 명시적으로 나타낼 수 있으나, 모델 내부 로직에 따라 달라질 수 있습니다.
radioModel.ExecuteClick(true);
}
});
@@ -45,14 +114,25 @@ namespace UVC.UI.Toolbar.View
UpdateToggleStateVisuals(radioModel, buttonUIObject, radioModel.IsSelected, viewContext);
}
/// <summary>
/// 버튼 모델의 공통적인 시각적 속성(텍스트, 아이콘, 활성화 상태)이 변경되었을 때 UI를 업데이트합니다.
/// </summary>
public void UpdateCommonButtonVisuals(ToolbarButtonBase buttonModel, GameObject buttonUIObject, ToolbarView viewContext)
{
viewContext.InternalUpdateCommonButtonVisuals(buttonModel, buttonUIObject);
}
/// <summary>
/// 라디오 버튼 모델의 IsSelected 상태가 변경되었을 때 UI의 시각적 표현을 업데이트합니다.
/// UI Toggle 컴포넌트의 isOn 상태를 모델과 동기화하고, 선택 상태에 따라 아이콘을 변경합니다.
/// </summary>
/// <param name="toggleButtonModel">IsSelected 상태가 변경된 라디오 버튼의 모델 (ToolbarRadioButton으로 캐스팅하여 사용).</param>
/// <param name="buttonUIObject">업데이트할 UI GameObject.</param>
/// <param name="isSelected">모델의 새로운 IsSelected 값.</param>
/// <param name="viewContext">현재 ToolbarView의 인스턴스.</param>
public void UpdateToggleStateVisuals(ToolbarToggleButton toggleButtonModel, GameObject buttonUIObject, bool isSelected, ToolbarView viewContext)
{
// ToolbarToggleButton 타입으로 캐스팅 시도
// ToolbarRadioButton은 ToolbarToggleButton을 상속하므로, toggleButtonModel을 ToolbarRadioButton으로 캐스팅합니다.
ToolbarRadioButton radioModel = toggleButtonModel as ToolbarRadioButton;
if (radioModel == null) return;
@@ -64,6 +144,7 @@ namespace UVC.UI.Toolbar.View
toggleComponent.SetIsOnWithoutNotify(isSelected);
}
}
// 공통 시각적 업데이트를 호출하여 선택 상태에 따른 아이콘 변경 등을 처리합니다.
viewContext.InternalUpdateCommonButtonVisuals(radioModel, buttonUIObject); // 아이콘 업데이트를 위해 호출
}
}

View File

@@ -5,8 +5,49 @@ using UVC.UI.ToolBar.View;
namespace UVC.UI.Toolbar.View
{
/// <summary>
/// 표준 툴바 버튼(ToolbarStandardButton)의 UI 생성, 이벤트 연결, 시각적 업데이트를 처리하는 클래스입니다.
/// IButtonViewProcessor 인터페이스를 구현하여 ToolbarView가 표준 버튼을 일관된 방식으로 다룰 수 있도록 합니다.
/// </summary>
/// <remarks>
/// 이 클래스의 주요 역할:
/// 1. UI 생성: ToolbarView에 설정된 '표준 버튼 프리팹'을 사용하여 버튼의 GameObject를 만듭니다.
/// 2. 상호작용 설정: 생성된 버튼 UI(UnityEngine.UI.Button)가 클릭되었을 때,
/// 연결된 ToolbarStandardButton 모델의 ExecuteClick 메서드가 호출되도록 설정합니다.
/// 3. 시각적 업데이트: 버튼 모델의 상태(텍스트, 아이콘, 활성화 상태 등)가 변경되면,
/// 화면에 보이는 버튼의 모습도 그에 맞게 업데이트합니다.
/// </remarks>
/// <example>
/// <code>
/// // ToolbarView 내에서 이 프로세서가 사용되는 방식 (간략화된 예시):
/// // 1. ToolbarModel로부터 ToolbarStandardButton 객체를 가져옵니다.
/// // ToolbarStandardButton standardButtonModel = new ToolbarStandardButton { Text = "Save", ... };
///
/// // 2. ToolbarView는 해당 모델 타입에 맞는 Processor를 찾습니다.
/// // IButtonViewProcessor processor = GetButtonViewProcessor(typeof(ToolbarStandardButton));
/// // (이때, ToolbarStandardButtonViewProcessor 인스턴스가 반환됩니다)
///
/// // 3. UI 생성 요청
/// // GameObject buttonUI = processor.CreateButtonUI(standardButtonModel, toolbarContainer, this);
///
/// // 4. 상호작용 및 초기 시각적 요소 설정 요청
/// // processor.SetupButtonInteractions(standardButtonModel, buttonUI, this);
///
/// // 5. 모델의 상태가 변경되면(예: standardButtonModel.Text = "Save As";) OnStateChanged 이벤트가 발생하고,
/// // ToolbarView는 다시 processor.UpdateCommonButtonVisuals()를 호출하여 UI를 업데이트합니다.
/// // standardButtonModel.OnStateChanged += () => processor.UpdateCommonButtonVisuals(standardButtonModel, buttonUI, this);
/// </code>
/// </example>
public class ToolbarStandardButtonViewProcessor : IButtonViewProcessor
{
/// <summary>
/// ToolbarStandardButton 모델에 해당하는 UI GameObject를 생성합니다.
/// ToolbarView에 있는 standardButtonPrefab을 복제하여 사용합니다.
/// </summary>
/// <param name="buttonModel">UI를 생성할 기반이 되는 버튼 데이터 모델 (ToolbarButtonBase 타입이지만, 실제로는 ToolbarStandardButton으로 간주됨).</param>
/// <param name="parentContainer">생성된 버튼 UI가 자식으로 추가될 부모 Transform 객체입니다.</param>
/// <param name="viewContext">현재 ToolbarView의 인스턴스. 프리팹 참조나 다른 뷰 관련 기능에 접근할 때 사용됩니다.</param>
/// <returns>성공적으로 생성된 버튼의 GameObject. 프리팹이 없으면 null을 반환합니다.</returns>
public GameObject CreateButtonUI(ToolbarButtonBase buttonModel, Transform parentContainer, ToolbarView viewContext)
{
if (viewContext.standardButtonPrefab == null)
@@ -14,20 +55,35 @@ namespace UVC.UI.Toolbar.View
Debug.LogError("StandardButtonViewProcessor: standardButtonPrefab이 ToolbarView에 할당되지 않았습니다.", viewContext);
return null;
}
// standardButtonPrefab을 복제하여 새로운 버튼 UI GameObject를 만듭니다.
// parentContainer의 자식으로 생성되어 툴바 레이아웃에 포함됩니다.
return Object.Instantiate(viewContext.standardButtonPrefab, parentContainer);
}
/// <summary>
/// 생성된 표준 버튼 UI GameObject에 필요한 상호작용을 설정하고 초기 시각적 상태를 업데이트합니다.
/// - UI 버튼 클릭 시 모델의 ExecuteClick 메서드 호출 설정
/// - 모델의 초기 텍스트, 아이콘, 활성화 상태를 UI에 반영
/// </summary>
/// <param name="buttonModel">설정 대상 버튼의 데이터 모델 (ToolbarStandardButton으로 캐스팅하여 사용).</param>
/// <param name="buttonUIObject">화면에 표시된, 설정할 버튼의 UI GameObject.</param>
/// <param name="viewContext">현재 ToolbarView의 인스턴스.</param>
public void SetupButtonInteractions(ToolbarButtonBase buttonModel, GameObject buttonUIObject, ToolbarView viewContext)
{
// buttonModel을 실제 타입인 ToolbarStandardButton으로 변환합니다.
ToolbarStandardButton standardModel = buttonModel as ToolbarStandardButton;
if (standardModel == null) return;
// 버튼 UI GameObject에서 UnityEngine.UI.Button 컴포넌트를 가져옵니다.
Button uiButton = buttonUIObject.GetComponent<Button>();
if (uiButton != null)
{
// 버튼 클릭 이벤트에 리스너(실행할 함수)를 추가합니다.
// 버튼이 클릭되면 standardModel의 ExecuteClick 메서드가 호출됩니다.
// standardModel.Text를 파라미터로 전달하는 것은 예시이며, 필요에 따라 null이나 다른 값을 전달할 수 있습니다.
uiButton.onClick.AddListener(() =>
{
standardModel.ExecuteClick(standardModel.Text);
standardModel.ExecuteClick(standardModel.Text); // 모델의 클릭 로직 실행
});
}
else
@@ -35,20 +91,37 @@ namespace UVC.UI.Toolbar.View
Debug.LogError($"StandardButtonViewProcessor: StandardButton '{standardModel.Text}'의 GameObject에 Button 컴포넌트가 없습니다.", buttonUIObject);
}
// 공통적인 모델 상태 변경 구독은 ToolbarView의 RenderToolbar에서 처리하거나,
// 각 Processor가 viewContext를 통해 UpdateCommonButtonVisuals 등을 직접 호출하도록 할 수 있습니다.
// 여기서는 ToolbarView에서 공통 구독을 처리한다고 가정합니다.
// 버튼의 초기 시각적 상태(텍스트, 아이콘, 활성화 상태 등)를 설정합니다.
UpdateCommonButtonVisuals(buttonModel, buttonUIObject, viewContext);
}
/// <summary>
/// 버튼 모델의 공통적인 시각적 속성(텍스트, 아이콘, 활성화 상태)이 변경되었을 때 UI를 업데이트합니다.
/// 실제 업데이트 로직은 ToolbarView의 InternalUpdateCommonButtonVisuals 메서드에 위임합니다.
/// </summary>
/// <param name="buttonModel">상태가 변경된 버튼의 데이터 모델.</param>
/// <param name="buttonUIObject">업데이트할 UI GameObject.</param>
/// <param name="viewContext">현재 ToolbarView의 인스턴스.</param>
public void UpdateCommonButtonVisuals(ToolbarButtonBase buttonModel, GameObject buttonUIObject, ToolbarView viewContext)
{
// ToolbarView에 있는 공통 UI 업데이트 로직을 호출합니다.
// 이 메서드는 버튼의 텍스트, 아이콘, 활성화(interactable) 상태 등을 모델에 맞춰 업데이트합니다.
viewContext.InternalUpdateCommonButtonVisuals(buttonModel, buttonUIObject);
}
/// <summary>
/// 표준 버튼은 토글 상태(선택됨/해제됨)를 가지지 않으므로, 이 메서드는 아무 작업도 수행하지 않습니다.
/// IButtonViewProcessor 인터페이스를 구현하기 위해 필요합니다.
/// </summary>
/// <param name="toggleButtonModel">토글 버튼 모델 (여기서는 사용되지 않음).</param>
/// <param name="buttonUIObject">UI GameObject (여기서는 사용되지 않음).</param>
/// <param name="isSelected">선택 상태 (여기서는 사용되지 않음).</param>
/// <param name="viewContext">ToolbarView 컨텍스트 (여기서는 사용되지 않음).</param>
public void UpdateToggleStateVisuals(ToolbarToggleButton toggleButtonModel, GameObject buttonUIObject, bool isSelected, ToolbarView viewContext)
{
// 표준 버튼은 토글 상태가 없음
// 표준 버튼은 별도의 '토글 선택 상태'에 따른 시각적 변화가 없습니다.
// 예를 들어, 배경색이 바뀌거나 체크마크가 표시되는 등의 변화가 없습니다.
// 따라서 이 메서드는 비워둡니다.
}
}
}

View File

@@ -5,8 +5,48 @@ using UVC.UI.ToolBar.View;
namespace UVC.UI.Toolbar.View
{
/// <summary>
/// <see cref="ToolbarToggleButton"/> 모델의 UI 표현을 생성하고 관리하는 클래스입니다.
/// 이 클래스는 <see cref="IButtonViewProcessor"/> 인터페이스를 구현하여,
/// 토글 버튼의 UI GameObject 생성, 이벤트 바인딩, 시각적 업데이트 로직을 담당합니다.
/// </summary>
/// <remarks>
/// 주요 역할:
/// - <see cref="ToolbarView.toggleButtonPrefab"/>을 사용하여 토글 버튼 UI GameObject를 인스턴스화합니다.
/// - 생성된 UI의 <see cref="Toggle"/> 컴포넌트와 <see cref="ToolbarToggleButton"/> 모델 간의 상호작용을 설정합니다.
/// (예: UI 토글 시 모델의 <see cref="ToolbarToggleButton.ExecuteClick"/> 호출)
/// - 모델의 상태 변경(<see cref="ToolbarButtonBase.OnStateChanged"/>, <see cref="ToolbarToggleButton.OnToggleStateChanged"/>)에 따라
/// UI의 시각적 요소(텍스트, 아이콘, 토글 상태 등)를 업데이트합니다.
/// </remarks>
/// <example>
/// <code>
/// // ToolbarView 내에서 이 프로세서가 사용되는 방식 (간략화된 예시):
/// // ToolbarView view = GetComponent<ToolbarView>();
/// // ToolbarToggleButton toggleModel = new ToolbarToggleButton { Text = "ToggleMe", IsSelected = true };
/// //
/// // IButtonViewProcessor processor = new ToolbarToggleButtonViewProcessor();
/// //
/// // // 1. UI 생성
/// // GameObject buttonUI = processor.CreateButtonUI(toggleModel, view.toolbarContainer, view);
/// //
/// // // 2. 상호작용 및 초기 상태 설정
/// // processor.SetupButtonInteractions(toggleModel, buttonUI, view);
/// //
/// // // 3. 모델 상태 변경 시 UI 업데이트 (ToolbarView의 RenderToolbar 내에서 이벤트 구독을 통해 자동 처리됨)
/// // // toggleModel.IsSelected = false; // 이 변경이 UI에 반영됨
/// // // toggleModel.Text = "New Text"; // 이 변경도 UI에 반영됨
/// </code>
/// </example>
public class ToolbarToggleButtonViewProcessor : IButtonViewProcessor
{
/// <summary>
/// <see cref="ToolbarToggleButton"/> 모델에 대한 UI GameObject를 생성합니다.
/// <see cref="ToolbarView.toggleButtonPrefab"/>을 사용하여 인스턴스화합니다.
/// </summary>
/// <param name="buttonModel">UI를 생성할 대상인 <see cref="ToolbarButtonBase"/> 타입의 모델 객체입니다. 내부적으로 <see cref="ToolbarToggleButton"/>으로 캐스팅하여 사용합니다.</param>
/// <param name="parentContainer">생성된 UI GameObject가 자식으로 추가될 부모 <see cref="Transform"/>입니다.</param>
/// <param name="viewContext">현재 <see cref="ToolbarView"/>의 컨텍스트입니다. 프리팹 참조 등에 사용됩니다.</param>
/// <returns>생성된 UI GameObject입니다. <see cref="ToolbarView.toggleButtonPrefab"/>이 할당되지 않은 경우 null을 반환할 수 있습니다.</returns>
public GameObject CreateButtonUI(ToolbarButtonBase buttonModel, Transform parentContainer, ToolbarView viewContext)
{
if (viewContext.toggleButtonPrefab == null)
@@ -14,9 +54,26 @@ namespace UVC.UI.Toolbar.View
Debug.LogError("ToggleButtonViewProcessor: toggleButtonPrefab이 ToolbarView에 할당되지 않았습니다.", viewContext);
return null;
}
// toggleButtonPrefab을 사용하여 새 GameObject를 만들고 parentContainer의 자식으로 배치합니다.
return Object.Instantiate(viewContext.toggleButtonPrefab, parentContainer);
}
/// <summary>
/// 생성된 토글 버튼 UI GameObject에 초기 시각적 요소를 설정하고, 사용자 상호작용 및 모델 상태 변경에 따른 UI 업데이트 로직을 바인딩합니다.
/// </summary>
/// <param name="buttonModel">설정할 버튼의 데이터 모델 (<see cref="ToolbarToggleButton"/> 타입이어야 함).</param>
/// <param name="buttonUIObject">모델에 해당하는, 화면에 표시될 UI GameObject.</param>
/// <param name="viewContext">현재 <see cref="ToolbarView"/>의 컨텍스트.</param>
/// <remarks>
/// 이 메서드는 다음을 수행합니다:
/// 1. <paramref name="buttonModel"/>을 <see cref="ToolbarToggleButton"/>으로 캐스팅합니다.
/// 2. <paramref name="buttonUIObject"/>에서 <see cref="Toggle"/> 컴포넌트를 찾습니다.
/// 3. <see cref="Toggle"/> 컴포넌트의 초기 `isOn` 상태를 모델의 <see cref="ToolbarToggleButton.IsSelected"/> 값으로 설정합니다. (UI 이벤트 발생 없이)
/// 4. <see cref="Toggle"/> 컴포넌트의 `onValueChanged` 이벤트에 리스너를 추가하여, UI에서 토글 상태가 변경되면 모델의 <see cref="ToolbarToggleButton.ExecuteClick"/> 메서드를 호출합니다.
/// 이를 통해 모델의 상태가 업데이트되고, 연결된 커맨드가 실행될 수 있습니다.
/// 5. <see cref="UpdateCommonButtonVisuals"/>를 호출하여 텍스트, 아이콘 등 공통 시각 요소를 초기화합니다.
/// 6. <see cref="UpdateToggleStateVisuals"/>를 호출하여 토글 상태에 따른 시각적 표현(예: 아이콘 변경)을 초기화합니다.
/// </remarks>
public void SetupButtonInteractions(ToolbarButtonBase buttonModel, GameObject buttonUIObject, ToolbarView viewContext)
{
ToolbarToggleButton toggleModel = buttonModel as ToolbarToggleButton;
@@ -25,9 +82,18 @@ namespace UVC.UI.Toolbar.View
Toggle toggleComponent = buttonUIObject.GetComponent<Toggle>();
if (toggleComponent != null)
{
// 1. 모델의 현재 선택 상태로 UI의 Toggle 컴포넌트 상태를 초기화합니다.
// SetIsOnWithoutNotify를 사용하여 이 변경으로 인해 onValueChanged 이벤트가 발생하지 않도록 합니다.
toggleComponent.SetIsOnWithoutNotify(toggleModel.IsSelected);
// 2. UI의 Toggle 컴포넌트의 값이 변경될 때(사용자가 클릭 등) 모델의 ExecuteClick 메서드를 호출합니다.
// ExecuteClick 내부에서 IsSelected 상태가 변경되고, 관련 커맨드가 실행되며, OnToggleStateChanged 이벤트가 발생합니다.
toggleComponent.onValueChanged.AddListener((isSelected) =>
{
// isSelected 파라미터는 UI에서 변경된 새 상태이지만,
// ToolbarToggleButton의 ExecuteClick() 내부에서 IsSelected가 토글되므로
// isSelected 파라미터를 직접 사용하지 않고 ExecuteClick()만 호출합니다.
// ExecuteClick()이 모델의 상태를 올바르게 변경하고 이벤트를 발생시킬 책임이 있습니다.
toggleModel.ExecuteClick();
});
}
@@ -35,26 +101,62 @@ namespace UVC.UI.Toolbar.View
{
Debug.LogError($"ToggleButtonViewProcessor: ToggleButton '{toggleModel.Text}'의 GameObject에 Toggle 컴포넌트가 없습니다.", buttonUIObject);
}
// 공통 시각적 요소(텍스트, 아이콘, 활성화 상태) 업데이트
UpdateCommonButtonVisuals(buttonModel, buttonUIObject, viewContext);
// 토글 상태에 따른 시각적 요소(예: 선택/해제 시 아이콘 변경) 업데이트
// 이 호출은 초기 상태를 반영하고, 모델의 IsSelected 값에 따라 올바른 아이콘(On/Off)을 표시하도록 합니다.
UpdateToggleStateVisuals(toggleModel, buttonUIObject, toggleModel.IsSelected, viewContext);
}
/// <summary>
/// 버튼 모델의 공통적인 상태(텍스트, 아이콘, 활성화 상태 등)가 변경되었을 때,
/// 해당 버튼 UI의 시각적 요소를 업데이트합니다.
/// 이 메서드는 <see cref="ToolbarView.InternalUpdateCommonButtonVisuals"/>를 호출하여 실제 업데이트 로직을 수행합니다.
/// </summary>
/// <param name="buttonModel">상태가 변경된 버튼의 모델입니다.</param>
/// <param name="buttonUIObject">업데이트할 UI GameObject입니다.</param>
/// <param name="viewContext">현재 <see cref="ToolbarView"/>의 컨텍스트입니다.</param>
public void UpdateCommonButtonVisuals(ToolbarButtonBase buttonModel, GameObject buttonUIObject, ToolbarView viewContext)
{
// ToolbarView에 있는 공통 업데이트 로직을 사용합니다.
// 이 메서드는 텍스트, 기본 아이콘(토글 상태에 따라 다를 수 있음), 활성화 상태(interactable) 등을 업데이트합니다.
viewContext.InternalUpdateCommonButtonVisuals(buttonModel, buttonUIObject);
}
/// <summary>
/// <see cref="ToolbarToggleButton"/>의 선택 상태(<see cref="ToolbarToggleButton.IsSelected"/>)가 모델에서 변경되었을 때 호출됩니다.
/// 해당 UI <see cref="Toggle"/> 컴포넌트의 `isOn` 상태를 모델의 <see cref="ToolbarToggleButton.IsSelected"/> 값과 동기화하고,
/// 선택 상태에 따른 아이콘 변경 등을 위해 <see cref="ToolbarView.InternalUpdateCommonButtonVisuals"/>를 호출합니다.
/// </summary>
/// <param name="toggleButtonModel">IsSelected 상태가 변경된 토글 버튼의 모델입니다.</param>
/// <param name="buttonUIObject">업데이트할 UI GameObject입니다.</param>
/// <param name="isSelected">모델의 새로운 <see cref="ToolbarToggleButton.IsSelected"/> 값입니다.</param>
/// <param name="viewContext">현재 <see cref="ToolbarView"/>의 컨텍스트입니다.</param>
/// <remarks>
/// 이 메서드는 주로 모델의 <see cref="ToolbarToggleButton.OnToggleStateChanged"/> 이벤트에 의해 호출됩니다.
/// UI의 <see cref="Toggle"/> 컴포넌트 상태를 모델과 동기화하고,
/// 선택/해제 상태에 따라 다른 아이콘을 사용한다면 <see cref="ToolbarView.InternalUpdateCommonButtonVisuals"/>가 이를 처리합니다.
/// </remarks>
public void UpdateToggleStateVisuals(ToolbarToggleButton toggleButtonModel, GameObject buttonUIObject, bool isSelected, ToolbarView viewContext)
{
Toggle toggleComponent = buttonUIObject.GetComponent<Toggle>();
if (toggleComponent != null)
{
// 모델의 IsSelected 상태와 UI의 Toggle.isOn 상태가 다를 경우에만 UI를 업데이트합니다.
// 이는 불필요한 UI 업데이트 및 이벤트 발생을 방지합니다.
if (toggleComponent.isOn != isSelected)
{
toggleComponent.SetIsOnWithoutNotify(isSelected);
}
}
viewContext.InternalUpdateCommonButtonVisuals(toggleButtonModel, buttonUIObject); // 아이콘 업데이트를 위해 호출
// 아이콘 업데이트를 위해 InternalUpdateCommonButtonVisuals를 다시 호출합니다.
// ToolbarToggleButton은 IsSelected 상태에 따라 다른 아이콘(IconSpritePath 또는 OffIconSpritePath)을
// 가질 수 있으므로, 이 상태 변경 시 아이콘도 업데이트되어야 합니다.
// InternalUpdateCommonButtonVisuals 내부에서 ToolbarToggleButton 타입인지 확인하고 적절한 아이콘 경로를 사용합니다.
viewContext.InternalUpdateCommonButtonVisuals(toggleButtonModel, buttonUIObject);
}
}
}

View File

@@ -217,6 +217,9 @@ namespace UVC.UI.Toolbar.View
RenderToolbar();
}
// 현재 열린 하위 메뉴의 원인 제공자(확장 버튼 모델) 추적용
protected ToolbarExpandableButton _ownerOfCurrentSubMenu;
/// <summary>
/// 현재 툴바에 표시된 모든 UI 요소들을 제거하고, 관련된 이벤트 구독을 해제합니다.
/// _modelToGameObjectMap, _toggleGroups를 비우고, 열려있는 하위 메뉴(currentSubMenu)도 파괴합니다.
@@ -236,6 +239,22 @@ namespace UVC.UI.Toolbar.View
}
if (pair.Value != null) // UI GameObject 파괴
{
// UI 컴포넌트의 이벤트 리스너 명시적 해제
// ToggleButton 또는 RadioButton의 경우 Toggle 컴포넌트 리스너 해제
Toggle toggleComponent = pair.Value.GetComponent<Toggle>();
if (toggleComponent != null)
{
toggleComponent.onValueChanged.RemoveAllListeners();
}
// StandardButton 또는 ExpandableButton의 경우 Button 컴포넌트 리스너 해제
// (필요에 따라 다른 버튼 타입의 리스너도 유사하게 처리)
Button buttonComponent = pair.Value.GetComponent<Button>();
if (buttonComponent != null)
{
buttonComponent.onClick.RemoveAllListeners();
}
TooltipHandler handler = pair.Value.GetComponent<TooltipHandler>();
if (handler != null) // 툴팁 핸들러 이벤트도 정리 (필요시)
{
@@ -259,8 +278,7 @@ namespace UVC.UI.Toolbar.View
if (currentSubMenu != null) // 열려있는 하위 메뉴 파괴
{
Destroy(currentSubMenu);
currentSubMenu = null;
DestroyCurrentSubMenuAndClearListeners(); // 헬퍼 메서드 사용
}
if (TooltipManager.Instance != null && TooltipManager.Instance.IsInitialized) // 툴팁 숨기기
@@ -479,6 +497,41 @@ namespace UVC.UI.Toolbar.View
return group;
}
/// <summary>
/// 현재 열려있는 하위 메뉴(currentSubMenu)의 자식 UI 요소들의 이벤트 리스너를 제거하고,
/// 하위 메뉴 GameObject를 파괴한 후 currentSubMenu 참조를 null로 설정합니다.
/// </summary>
protected void DestroyCurrentSubMenuAndClearListeners()
{
if (currentSubMenu == null) return;
// currentSubMenu의 자식들을 순회하며 Button 리스너 제거
Button[] subButtons = currentSubMenu.GetComponentsInChildren<Button>(true);
foreach (Button btn in subButtons)
{
btn.onClick.RemoveAllListeners();
}
// currentSubMenu의 자식들을 순회하며 Toggle 리스너 제거
Toggle[] subToggles = currentSubMenu.GetComponentsInChildren<Toggle>(true);
foreach (Toggle tgl in subToggles)
{
tgl.onValueChanged.RemoveAllListeners();
}
// currentSubMenu의 자식들에 있는 TooltipHandler 정리 (필요시)
TooltipHandler[] subTooltipHandlers = currentSubMenu.GetComponentsInChildren<TooltipHandler>(true);
foreach (TooltipHandler handler in subTooltipHandlers)
{
handler.OnPointerEnterAction = null;
handler.OnPointerExitAction = null;
}
Destroy(currentSubMenu);
currentSubMenu = null;
_ownerOfCurrentSubMenu = null; // 하위 메뉴 소유자 참조도 초기화
}
/// <summary>
/// 현재 열려있는 하위 메뉴 UI GameObject입니다. 없으면 null입니다.
/// </summary>
@@ -493,11 +546,16 @@ namespace UVC.UI.Toolbar.View
/// <param name="expandableButtonObj">주 확장 버튼의 UI GameObject로, 하위 메뉴 위치 결정에 사용됩니다.</param>
internal void ToggleSubMenu(ToolbarExpandableButton expandableButtonModel, GameObject expandableButtonObj)
{
bool closeOnly = false;
// 이미 다른 하위 메뉴가 열려있거나, 현재 클릭한 버튼의 하위 메뉴가 열려있다면 닫습니다.
if (currentSubMenu != null)
{
Destroy(currentSubMenu);
currentSubMenu = null;
// 현재 열린 하위 메뉴가 지금 클릭한 확장 버튼에 의해 열린 것인지 확인
if (_ownerOfCurrentSubMenu == expandableButtonModel)
{
closeOnly = true; // 같은 버튼을 다시 클릭했으므로 닫기만 함
}
DestroyCurrentSubMenuAndClearListeners(); // 기존 하위 메뉴 정리 (리스너 포함)
// 만약 현재 클릭한 버튼의 하위 메뉴가 이미 열려있어서 닫는 경우라면, 여기서 함수 종료
// (다시 열리지 않도록). 이 로직은 currentSubMenu가 어떤 버튼에 의해 열렸는지 알아야 함.
// 간단하게는, 어떤 확장 버튼이든 클릭하면 기존 서브메뉴는 닫고, 필요하면 새로 연다.
@@ -514,6 +572,11 @@ namespace UVC.UI.Toolbar.View
// 지금은 닫고, 아래에서 다시 열 수 있게 함.
}
if (closeOnly)
{
return; // 닫기만 하고 새로운 메뉴를 열지 않음
}
// 새 하위 메뉴를 열 조건 확인 (닫혀 있었고, 프리팹과 하위 버튼이 있는 경우)
if (subMenuPanelPrefab == null)
{
@@ -530,6 +593,7 @@ namespace UVC.UI.Toolbar.View
// 생성 위치는 일단 ToolbarView의 자식으로 하고, 이후 위치 조정
currentSubMenu = Instantiate(subMenuPanelPrefab, transform);
currentSubMenu.name = $"SubMenu_{expandableButtonModel.Text}";
_ownerOfCurrentSubMenu = expandableButtonModel; // 새 하위 메뉴의 소유자 설정
RectTransform panelRect = currentSubMenu.GetComponent<RectTransform>();
if (panelRect == null)
@@ -710,8 +774,7 @@ namespace UVC.UI.Toolbar.View
if (!RectTransformUtility.RectangleContainsScreenPoint(subMenuRect, Input.mousePosition, eventCamera))
{
// 하위 메뉴 영역 바깥을 클릭한 경우, 하위 메뉴를 닫습니다.
Destroy(currentSubMenu);
currentSubMenu = null;
DestroyCurrentSubMenuAndClearListeners();
if (TooltipManager.Instance != null && TooltipManager.Instance.IsInitialized) TooltipManager.Instance.HideTooltip();
}
}