diff --git a/Assets/Scripts/UVC/UI/ToolBar/View/ToolbarExpandableButtonViewProcessor.cs b/Assets/Scripts/UVC/UI/ToolBar/View/ToolbarExpandableButtonViewProcessor.cs
index 47af2cd9..71197b94 100644
--- a/Assets/Scripts/UVC/UI/ToolBar/View/ToolbarExpandableButtonViewProcessor.cs
+++ b/Assets/Scripts/UVC/UI/ToolBar/View/ToolbarExpandableButtonViewProcessor.cs
@@ -5,8 +5,51 @@ using UVC.UI.ToolBar.View;
namespace UVC.UI.Toolbar.View
{
+ ///
+ /// 확장 가능한 툴바 버튼(ToolbarExpandableButton)의 UI 생성, 이벤트 연결, 시각적 업데이트를 처리하는 클래스입니다.
+ /// IButtonViewProcessor 인터페이스를 구현하여 ToolbarView가 확장 버튼을 일관된 방식으로 다룰 수 있도록 합니다.
+ /// 확장 버튼은 클릭 시 하위 메뉴(SubButtons)를 표시하거나 숨기는 기능을 가집니다.
+ ///
+ ///
+ /// 이 클래스의 주요 역할:
+ /// 1. UI 생성: ToolbarView에 설정된 '확장 버튼 프리팹'을 사용하여 주 버튼의 GameObject를 만듭니다.
+ /// 2. 상호작용 설정: 생성된 주 버튼 UI(UnityEngine.UI.Button)가 클릭되었을 때,
+ /// - 연결된 ToolbarExpandableButton 모델의 ExecuteClick 메서드가 호출되도록 설정합니다.
+ /// - ToolbarView의 ToggleSubMenu 메서드를 호출하여 하위 메뉴의 표시/숨김을 처리하도록 합니다.
+ /// 3. 시각적 업데이트: 주 버튼 모델의 상태(텍스트, 아이콘, 활성화 상태 등)가 변경되면,
+ /// 화면에 보이는 주 버튼의 모습도 그에 맞게 업데이트합니다. 하위 메뉴 자체의 렌더링은 ToolbarView가 담당합니다.
+ ///
+ ///
+ ///
+ /// // 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()를 호출합니다.
+ ///
+ ///
public class ToolbarExpandableButtonViewProcessor : IButtonViewProcessor
{
+ ///
+ /// ToolbarExpandableButton 모델에 해당하는 주 버튼 UI GameObject를 생성합니다.
+ /// ToolbarView에 있는 expandableButtonPrefab을 복제하여 사용합니다.
+ ///
+ /// UI를 생성할 기반이 되는 버튼 데이터 모델 (ToolbarExpandableButton으로 간주됨).
+ /// 생성된 버튼 UI가 자식으로 추가될 부모 Transform 객체입니다.
+ /// 현재 ToolbarView의 인스턴스. 프리팹 참조 및 하위 메뉴 토글 기능에 접근할 때 사용됩니다.
+ /// 성공적으로 생성된 주 버튼의 GameObject. 프리팹이 없으면 null을 반환합니다.
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);
}
+ ///
+ /// 생성된 확장 버튼(주 버튼) UI GameObject에 필요한 상호작용을 설정하고 초기 시각적 상태를 업데이트합니다.
+ /// - 주 버튼 UI 클릭 시 모델의 ExecuteClick 메서드 호출 및 ToolbarView의 ToggleSubMenu 메서드 호출 설정.
+ /// - 모델의 초기 텍스트, 아이콘, 활성화 상태를 주 버튼 UI에 반영.
+ ///
+ /// 설정 대상 버튼의 데이터 모델 (ToolbarExpandableButton으로 캐스팅하여 사용).
+ /// 화면에 표시된, 설정할 주 버튼의 UI GameObject.
+ /// 현재 ToolbarView의 인스턴스.
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();
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);
}
+ ///
+ /// 주 버튼 모델의 공통적인 시각적 속성(텍스트, 아이콘, 활성화 상태)이 변경되었을 때 UI를 업데이트합니다.
+ ///
public void UpdateCommonButtonVisuals(ToolbarButtonBase buttonModel, GameObject buttonUIObject, ToolbarView viewContext)
{
viewContext.InternalUpdateCommonButtonVisuals(buttonModel, buttonUIObject);
}
+ ///
+ /// 확장 버튼은 전통적인 의미의 '토글 선택 상태'를 가지지 않습니다.
+ /// 하위 메뉴의 표시 여부는 별도의 메커니즘(ToolbarView의 currentSubMenu)으로 관리되므로,
+ /// 이 메서드는 아무 작업도 수행하지 않습니다.
+ /// IButtonViewProcessor 인터페이스를 구현하기 위해 필요합니다.
+ ///
public void UpdateToggleStateVisuals(ToolbarToggleButton toggleButtonModel, GameObject buttonUIObject, bool isSelected, ToolbarView viewContext)
{
- // 확장 버튼은 기본 토글 상태가 없음 (하위 메뉴 표시 여부는 다른 메커니즘으로 관리)
+ // 확장 버튼의 주 버튼 자체는 켜고 끄는 토글 상태를 직접적으로 가지지 않습니다.
+ // (예: IsSelected 같은 속성으로 UI가 바뀌지 않음)
+ // 하위 메뉴가 열려있는지 여부는 ToolbarView에서 관리합니다.
+ // 따라서 이 메서드는 비워둡니다.
}
}
}
\ No newline at end of file
diff --git a/Assets/Scripts/UVC/UI/ToolBar/View/ToolbarRadioButtonViewProcessor.cs b/Assets/Scripts/UVC/UI/ToolBar/View/ToolbarRadioButtonViewProcessor.cs
index 1a1dd47d..54d6eaac 100644
--- a/Assets/Scripts/UVC/UI/ToolBar/View/ToolbarRadioButtonViewProcessor.cs
+++ b/Assets/Scripts/UVC/UI/ToolBar/View/ToolbarRadioButtonViewProcessor.cs
@@ -5,8 +5,55 @@ using UVC.UI.ToolBar.View;
namespace UVC.UI.Toolbar.View
{
+ ///
+ /// 라디오 툴바 버튼(ToolbarRadioButton)의 UI 생성, 이벤트 연결, 시각적 업데이트를 처리하는 클래스입니다.
+ /// IButtonViewProcessor 인터페이스를 구현하여 ToolbarView가 라디오 버튼을 일관된 방식으로 다룰 수 있도록 합니다.
+ /// 라디오 버튼은 그룹 내에서 단 하나만 선택될 수 있는 특징을 가집니다.
+ ///
+ ///
+ /// 이 클래스의 주요 역할:
+ /// 1. UI 생성: ToolbarView에 설정된 '라디오 버튼 프리팹'을 사용하여 버튼의 GameObject를 만듭니다.
+ /// 이 프리팹에는 UnityEngine.UI.Toggle 컴포넌트가 포함되어 있어야 합니다.
+ /// 2. 상호작용 설정:
+ /// - 생성된 토글 UI를 적절한 UnityEngine.UI.ToggleGroup에 할당합니다. (ToolbarView의 GetOrCreateToggleGroup 사용)
+ /// - UI Toggle의 상태가 변경될 때(사용자가 클릭하여 선택 시), 연결된 ToolbarRadioButton 모델의 ExecuteClick 메서드가 호출되고,
+ /// 모델 및 같은 그룹 내 다른 라디오 버튼들의 IsSelected 상태가 업데이트되도록 합니다.
+ /// 3. 시각적 업데이트: 버튼 모델의 상태(텍스트, 아이콘, 활성화 상태, 선택 상태 등)가 변경되면,
+ /// 화면에 보이는 버튼의 모습(예: Toggle의 isOn 상태, 아이콘 변경 등)도 그에 맞게 업데이트합니다.
+ ///
+ ///
+ ///
+ /// // 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를 업데이트합니다.
+ ///
+ ///
public class ToolbarRadioButtonViewProcessor : IButtonViewProcessor
{
+ ///
+ /// ToolbarRadioButton 모델에 해당하는 UI GameObject를 생성합니다.
+ /// ToolbarView에 있는 radioButtonPrefab을 복제하여 사용합니다.
+ ///
+ /// UI를 생성할 기반이 되는 버튼 데이터 모델 (ToolbarRadioButton으로 간주됨).
+ /// 생성된 버튼 UI가 자식으로 추가될 부모 Transform 객체입니다.
+ /// 현재 ToolbarView의 인스턴스. 프리팹 참조 및 ToggleGroup 관리에 사용됩니다.
+ /// 성공적으로 생성된 버튼의 GameObject. 프리팹이 없으면 null을 반환합니다.
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);
}
+ ///
+ /// 생성된 라디오 버튼 UI GameObject에 필요한 상호작용을 설정하고 초기 시각적 상태를 업데이트합니다.
+ /// - UI Toggle 컴포넌트를 가져와 모델의 GroupName에 해당하는 ToggleGroup에 할당합니다.
+ /// - UI Toggle의 상태 변경(onValueChanged) 시, 선택된 경우에만 모델의 ExecuteClick 메서드 호출 설정.
+ /// - 모델의 초기 IsSelected 상태를 UI Toggle의 isOn 상태에 반영.
+ /// - 모델의 초기 텍스트, 아이콘, 활성화 상태를 UI에 반영.
+ ///
+ /// 설정 대상 버튼의 데이터 모델 (ToolbarRadioButton으로 캐스팅하여 사용).
+ /// 화면에 표시된, 설정할 버튼의 UI GameObject.
+ /// 현재 ToolbarView의 인스턴스.
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();
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);
}
+ ///
+ /// 버튼 모델의 공통적인 시각적 속성(텍스트, 아이콘, 활성화 상태)이 변경되었을 때 UI를 업데이트합니다.
+ ///
public void UpdateCommonButtonVisuals(ToolbarButtonBase buttonModel, GameObject buttonUIObject, ToolbarView viewContext)
{
viewContext.InternalUpdateCommonButtonVisuals(buttonModel, buttonUIObject);
}
+ ///
+ /// 라디오 버튼 모델의 IsSelected 상태가 변경되었을 때 UI의 시각적 표현을 업데이트합니다.
+ /// UI Toggle 컴포넌트의 isOn 상태를 모델과 동기화하고, 선택 상태에 따라 아이콘을 변경합니다.
+ ///
+ /// IsSelected 상태가 변경된 라디오 버튼의 모델 (ToolbarRadioButton으로 캐스팅하여 사용).
+ /// 업데이트할 UI GameObject.
+ /// 모델의 새로운 IsSelected 값.
+ /// 현재 ToolbarView의 인스턴스.
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); // 아이콘 업데이트를 위해 호출
}
}
diff --git a/Assets/Scripts/UVC/UI/ToolBar/View/ToolbarStandardButtonViewProcessor.cs b/Assets/Scripts/UVC/UI/ToolBar/View/ToolbarStandardButtonViewProcessor.cs
index 5130e1fe..07c3cdea 100644
--- a/Assets/Scripts/UVC/UI/ToolBar/View/ToolbarStandardButtonViewProcessor.cs
+++ b/Assets/Scripts/UVC/UI/ToolBar/View/ToolbarStandardButtonViewProcessor.cs
@@ -5,8 +5,49 @@ using UVC.UI.ToolBar.View;
namespace UVC.UI.Toolbar.View
{
+ ///
+ /// ǥ ư(ToolbarStandardButton) UI , ̺Ʈ , ð Ʈ óϴ ŬԴϴ.
+ /// IButtonViewProcessor ̽ Ͽ ToolbarView ǥ ư ϰ ٷ ֵ մϴ.
+ ///
+ ///
+ /// Ŭ ֿ :
+ /// 1. UI : ToolbarView 'ǥ ư ' Ͽ ư GameObject ϴ.
+ /// 2. ȣۿ : ư UI(UnityEngine.UI.Button) ŬǾ ,
+ /// ToolbarStandardButton ExecuteClick 尡 ȣǵ մϴ.
+ /// 3. ð Ʈ: ư (ؽƮ, , Ȱȭ ) Ǹ,
+ /// ȭ鿡 ̴ ư ° Ʈմϴ.
+ ///
+ ///
+ ///
+ /// // 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);
+ ///
+ ///
public class ToolbarStandardButtonViewProcessor : IButtonViewProcessor
{
+ ///
+ /// ToolbarStandardButton شϴ UI GameObject մϴ.
+ /// ToolbarView ִ standardButtonPrefab Ͽ մϴ.
+ ///
+ /// UI Ǵ ư (ToolbarButtonBase Ÿ, δ ToolbarStandardButton ֵ).
+ /// ư UI ڽ ߰ θ Transform üԴϴ.
+ /// ToolbarView νϽ. ٸ ɿ ˴ϴ.
+ /// ư GameObject. null ȯմϴ.
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);
}
+ ///
+ /// ǥ ư UI GameObject ʿ ȣۿ ϰ ʱ ð ¸ Ʈմϴ.
+ /// - UI ư Ŭ ExecuteClick ȣ
+ /// - ʱ ؽƮ, , Ȱȭ ¸ UI ݿ
+ ///
+ /// ư (ToolbarStandardButton ijϿ ).
+ /// ȭ鿡 ǥõ, ư UI GameObject.
+ /// ToolbarView νϽ.
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();
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);
}
+ ///
+ /// ư ð Ӽ(ؽƮ, , Ȱȭ ) Ǿ UI Ʈմϴ.
+ /// Ʈ ToolbarView InternalUpdateCommonButtonVisuals 忡 մϴ.
+ ///
+ /// ° ư .
+ /// Ʈ UI GameObject.
+ /// ToolbarView νϽ.
public void UpdateCommonButtonVisuals(ToolbarButtonBase buttonModel, GameObject buttonUIObject, ToolbarView viewContext)
{
+ // ToolbarView ִ UI Ʈ ȣմϴ.
+ // ư ؽƮ, , Ȱȭ(interactable) Ʈմϴ.
viewContext.InternalUpdateCommonButtonVisuals(buttonModel, buttonUIObject);
}
+ ///
+ /// ǥ ư (õ/) Ƿ, ƹ ۾ ʽϴ.
+ /// IButtonViewProcessor ̽ ϱ ʿմϴ.
+ ///
+ /// ư (⼭ ).
+ /// UI GameObject (⼭ ).
+ /// (⼭ ).
+ /// ToolbarView ؽƮ (⼭ ).
public void UpdateToggleStateVisuals(ToolbarToggleButton toggleButtonModel, GameObject buttonUIObject, bool isSelected, ToolbarView viewContext)
{
- // ǥ ư °
+ // ǥ ư ' ' ð ȭ ϴ.
+ // , ٲų üũũ ǥõǴ ȭ ϴ.
+ // Ӵϴ.
}
}
}
\ No newline at end of file
diff --git a/Assets/Scripts/UVC/UI/ToolBar/View/ToolbarToggleButtonViewProcessor.cs b/Assets/Scripts/UVC/UI/ToolBar/View/ToolbarToggleButtonViewProcessor.cs
index 486baec6..e6ad8164 100644
--- a/Assets/Scripts/UVC/UI/ToolBar/View/ToolbarToggleButtonViewProcessor.cs
+++ b/Assets/Scripts/UVC/UI/ToolBar/View/ToolbarToggleButtonViewProcessor.cs
@@ -5,8 +5,48 @@ using UVC.UI.ToolBar.View;
namespace UVC.UI.Toolbar.View
{
+ ///
+ /// 모델의 UI 표현을 생성하고 관리하는 클래스입니다.
+ /// 이 클래스는 인터페이스를 구현하여,
+ /// 토글 버튼의 UI GameObject 생성, 이벤트 바인딩, 시각적 업데이트 로직을 담당합니다.
+ ///
+ ///
+ /// 주요 역할:
+ /// - 을 사용하여 토글 버튼 UI GameObject를 인스턴스화합니다.
+ /// - 생성된 UI의 컴포넌트와 모델 간의 상호작용을 설정합니다.
+ /// (예: UI 토글 시 모델의 호출)
+ /// - 모델의 상태 변경( , )에 따라
+ /// UI의 시각적 요소(텍스트, 아이콘, 토글 상태 등)를 업데이트합니다.
+ ///
+ ///
+ ///
+ /// // ToolbarView 내에서 이 프로세서가 사용되는 방식 (간략화된 예시):
+ /// // ToolbarView view = GetComponent();
+ /// // 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에 반영됨
+ ///
+ ///
public class ToolbarToggleButtonViewProcessor : IButtonViewProcessor
{
+ ///
+ /// 모델에 대한 UI GameObject를 생성합니다.
+ /// 을 사용하여 인스턴스화합니다.
+ ///
+ /// UI를 생성할 대상인 타입의 모델 객체입니다. 내부적으로 으로 캐스팅하여 사용합니다.
+ /// 생성된 UI GameObject가 자식으로 추가될 부모 입니다.
+ /// 현재 의 컨텍스트입니다. 프리팹 참조 등에 사용됩니다.
+ /// 생성된 UI GameObject입니다. 이 할당되지 않은 경우 null을 반환할 수 있습니다.
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);
}
+ ///
+ /// 생성된 토글 버튼 UI GameObject에 초기 시각적 요소를 설정하고, 사용자 상호작용 및 모델 상태 변경에 따른 UI 업데이트 로직을 바인딩합니다.
+ ///
+ /// 설정할 버튼의 데이터 모델 ( 타입이어야 함).
+ /// 모델에 해당하는, 화면에 표시될 UI GameObject.
+ /// 현재 의 컨텍스트.
+ ///
+ /// 이 메서드는 다음을 수행합니다:
+ /// 1. 을 으로 캐스팅합니다.
+ /// 2. 에서 컴포넌트를 찾습니다.
+ /// 3. 컴포넌트의 초기 `isOn` 상태를 모델의 값으로 설정합니다. (UI 이벤트 발생 없이)
+ /// 4. 컴포넌트의 `onValueChanged` 이벤트에 리스너를 추가하여, UI에서 토글 상태가 변경되면 모델의 메서드를 호출합니다.
+ /// 이를 통해 모델의 상태가 업데이트되고, 연결된 커맨드가 실행될 수 있습니다.
+ /// 5. 를 호출하여 텍스트, 아이콘 등 공통 시각 요소를 초기화합니다.
+ /// 6. 를 호출하여 토글 상태에 따른 시각적 표현(예: 아이콘 변경)을 초기화합니다.
+ ///
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();
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);
}
+ ///
+ /// 버튼 모델의 공통적인 상태(텍스트, 아이콘, 활성화 상태 등)가 변경되었을 때,
+ /// 해당 버튼 UI의 시각적 요소를 업데이트합니다.
+ /// 이 메서드는 를 호출하여 실제 업데이트 로직을 수행합니다.
+ ///
+ /// 상태가 변경된 버튼의 모델입니다.
+ /// 업데이트할 UI GameObject입니다.
+ /// 현재 의 컨텍스트입니다.
public void UpdateCommonButtonVisuals(ToolbarButtonBase buttonModel, GameObject buttonUIObject, ToolbarView viewContext)
{
+ // ToolbarView에 있는 공통 업데이트 로직을 사용합니다.
+ // 이 메서드는 텍스트, 기본 아이콘(토글 상태에 따라 다를 수 있음), 활성화 상태(interactable) 등을 업데이트합니다.
viewContext.InternalUpdateCommonButtonVisuals(buttonModel, buttonUIObject);
}
+ ///
+ /// 의 선택 상태( )가 모델에서 변경되었을 때 호출됩니다.
+ /// 해당 UI 컴포넌트의 `isOn` 상태를 모델의 값과 동기화하고,
+ /// 선택 상태에 따른 아이콘 변경 등을 위해 를 호출합니다.
+ ///
+ /// IsSelected 상태가 변경된 토글 버튼의 모델입니다.
+ /// 업데이트할 UI GameObject입니다.
+ /// 모델의 새로운 값입니다.
+ /// 현재 의 컨텍스트입니다.
+ ///
+ /// 이 메서드는 주로 모델의 이벤트에 의해 호출됩니다.
+ /// UI의 컴포넌트 상태를 모델과 동기화하고,
+ /// 선택/해제 상태에 따라 다른 아이콘을 사용한다면 가 이를 처리합니다.
+ ///
public void UpdateToggleStateVisuals(ToolbarToggleButton toggleButtonModel, GameObject buttonUIObject, bool isSelected, ToolbarView viewContext)
{
Toggle toggleComponent = buttonUIObject.GetComponent();
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);
}
}
}
\ No newline at end of file
diff --git a/Assets/Scripts/UVC/UI/ToolBar/View/ToolbarView.cs b/Assets/Scripts/UVC/UI/ToolBar/View/ToolbarView.cs
index 7d8dab9a..48b7aac7 100644
--- a/Assets/Scripts/UVC/UI/ToolBar/View/ToolbarView.cs
+++ b/Assets/Scripts/UVC/UI/ToolBar/View/ToolbarView.cs
@@ -217,6 +217,9 @@ namespace UVC.UI.Toolbar.View
RenderToolbar();
}
+ // 현재 열린 하위 메뉴의 원인 제공자(확장 버튼 모델) 추적용
+ protected ToolbarExpandableButton _ownerOfCurrentSubMenu;
+
///
/// 현재 툴바에 표시된 모든 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();
+ if (toggleComponent != null)
+ {
+ toggleComponent.onValueChanged.RemoveAllListeners();
+ }
+
+ // StandardButton 또는 ExpandableButton의 경우 Button 컴포넌트 리스너 해제
+ // (필요에 따라 다른 버튼 타입의 리스너도 유사하게 처리)
+ Button buttonComponent = pair.Value.GetComponent();
+ if (buttonComponent != null)
+ {
+ buttonComponent.onClick.RemoveAllListeners();
+ }
+
TooltipHandler handler = pair.Value.GetComponent();
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;
}
+ ///
+ /// 현재 열려있는 하위 메뉴(currentSubMenu)의 자식 UI 요소들의 이벤트 리스너를 제거하고,
+ /// 하위 메뉴 GameObject를 파괴한 후 currentSubMenu 참조를 null로 설정합니다.
+ ///
+ protected void DestroyCurrentSubMenuAndClearListeners()
+ {
+ if (currentSubMenu == null) return;
+
+ // currentSubMenu의 자식들을 순회하며 Button 리스너 제거
+ Button[] subButtons = currentSubMenu.GetComponentsInChildren(true);
+ foreach (Button btn in subButtons)
+ {
+ btn.onClick.RemoveAllListeners();
+ }
+
+ // currentSubMenu의 자식들을 순회하며 Toggle 리스너 제거
+ Toggle[] subToggles = currentSubMenu.GetComponentsInChildren(true);
+ foreach (Toggle tgl in subToggles)
+ {
+ tgl.onValueChanged.RemoveAllListeners();
+ }
+
+ // currentSubMenu의 자식들에 있는 TooltipHandler 정리 (필요시)
+ TooltipHandler[] subTooltipHandlers = currentSubMenu.GetComponentsInChildren(true);
+ foreach (TooltipHandler handler in subTooltipHandlers)
+ {
+ handler.OnPointerEnterAction = null;
+ handler.OnPointerExitAction = null;
+ }
+
+ Destroy(currentSubMenu);
+ currentSubMenu = null;
+ _ownerOfCurrentSubMenu = null; // 하위 메뉴 소유자 참조도 초기화
+ }
+
///
/// 현재 열려있는 하위 메뉴 UI GameObject입니다. 없으면 null입니다.
///
@@ -493,11 +546,16 @@ namespace UVC.UI.Toolbar.View
/// 주 확장 버튼의 UI GameObject로, 하위 메뉴 위치 결정에 사용됩니다.
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();
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();
}
}