Files
XRLib/Assets/Sample/UIToolkit/UTKAccordionListWindowSample.cs
2026-02-02 19:33:27 +09:00

764 lines
27 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#nullable enable
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
using UVC.UIToolkit;
/// <summary>
/// UTKAccordionListWindow의 기능을 테스트하기 위한 샘플 MonoBehaviour입니다.
/// 아코디언 리스트 데이터를 생성하고 다양한 이벤트 핸들러를 등록하여 동작을 확인합니다.
///
/// <para><b>테스트 기능:</b></para>
/// <list type="bullet">
/// <item>섹션 펼침/접힘</item>
/// <item>수평 레이아웃 (Head-Content-Tail)</item>
/// <item>그리드 레이아웃 (Image + Caption)</item>
/// <item>검색 필터링</item>
/// <item>드래그 앤 드롭 이벤트</item>
/// </list>
/// </summary>
public class UTKAccordionListWindowSample : MonoBehaviour
{
#region (Fields)
[SerializeField]
[Tooltip("UI를 표시할 UIDocument 컴포넌트")]
public UIDocument? uiDocument;
[SerializeField]
[Tooltip("드래그 시 고스트 이미지 표시 여부")]
private bool showDragGhost = true;
[SerializeField]
[Tooltip("시작 시 적용할 테마")]
private UTKTheme initialTheme = UTKTheme.Dark;
private UTKToggle _themeToggle;
/// <summary>UTKAccordionListWindow 인스턴스</summary>
private UTKAccordionListWindow _accordionWindow;
/// <summary>드롭 위치 (스크린 좌표)</summary>
private Vector2 _lastDropScreenPosition;
/// <summary>메인 카메라 참조</summary>
private Camera? _mainCamera;
/// <summary>드래그 중인 3D 프리뷰 오브젝트</summary>
private GameObject? _dragPreview3D;
/// <summary>드래그 중 리스트 영역 밖에 있는지 여부</summary>
private bool _isOutsideListArea;
#endregion
#region Unity
private void Start()
{
// UIDocument 참조 확인
var doc = GetComponent<UIDocument>();
if (doc == null)
{
Debug.LogError("UIDocument가 할당되지 않았습니다.");
return;
}
uiDocument = doc;
var toggle = uiDocument.rootVisualElement.Q<UTKToggle>("toggle");
if (toggle == null)
{
Debug.LogError("UXML에서 UTKToggle을 찾을 수 없습니다.");
return;
}
_themeToggle = toggle;
var window = uiDocument.rootVisualElement.Q<UTKAccordionListWindow>("window");
if (window == null)
{
Debug.LogError("UXML에서 UTKAccordionListWindow를 찾을 수 없습니다.");
return;
}
_accordionWindow = window;
UTKThemeManager.Instance.RegisterRoot(uiDocument.rootVisualElement);
UTKThemeManager.Instance.SetTheme(initialTheme);
_themeToggle.OnValueChanged += (isOn) =>
{
UTKThemeManager.Instance.SetTheme(!isOn ? UTKTheme.Dark : UTKTheme.Light);
};
// UTKAccordionListWindow 인스턴스 생성 및 추가
_accordionWindow.ShowDragGhost = showDragGhost;
_accordionWindow.Title = "ACCORDION";
_accordionWindow.ShowCloseButton = true;
// 테스트 데이터 생성
CreateTestData();
// 이벤트 핸들러 등록
RegisterEventHandlers();
Debug.Log("[UTKAccordionListWindowSample] 초기화 완료");
}
private void OnDestroy()
{
// 3D 프리뷰 정리
Destroy3DPreview();
// 리소스 정리
_accordionWindow?.Dispose();
_accordionWindow = null;
}
#endregion
#region
/// <summary>
/// 테스트용 아코디언 데이터를 생성합니다.
/// AccordionSample과 동일한 구조로 데이터를 생성합니다.
/// </summary>
private void CreateTestData()
{
if (_accordionWindow == null) return;
var data = new UTKAccordionData();
// ========================================
// 수평 레이아웃 섹션 1: Settings
// ========================================
var settingsSection = new UTKAccordionSectionData
{
Title = "Settings",
IsExpanded = true,
LayoutType = UTKAccordionLayoutType.Horizontal,
HorizontalItems = new List<UTKAccordionHorizontalItemData>
{
new UTKAccordionHorizontalItemData
{
Head = UTKAccordionContentSpec.FromImage("Prefabs/UI/images/icon_side_tab_library_24"),
Content = UTKAccordionContentSpec.FromText("Graphics", "open_graphics"),
Tail = new List<UTKAccordionContentSpec>
{
UTKAccordionContentSpec.FromIconButton(UTKMaterialIcons.Refresh, 12, "refresh_graphics", "새로고침"),
UTKAccordionContentSpec.FromIconButton(UTKMaterialIcons.Settings, 12, "setting_graphics", "설정"),
}
},
new UTKAccordionHorizontalItemData
{
Head = UTKAccordionContentSpec.FromImage("Prefabs/UI/images/icon_side_tab_fleet_128"),
Content = UTKAccordionContentSpec.FromText("Audio", "open_audio"),
Tail = new List<UTKAccordionContentSpec>
{
UTKAccordionContentSpec.FromIconButton(UTKMaterialIcons.Refresh, 12, "refresh_audio", "새로고침"),
UTKAccordionContentSpec.FromIconButton(UTKMaterialIcons.Settings, 12, "setting_audio", "설정"),
}
},
new UTKAccordionHorizontalItemData
{
Head = UTKAccordionContentSpec.FromImage("Prefabs/UI/images/icon_side_tab_explorer_24"),
Content = UTKAccordionContentSpec.FromText("Network", "open_network"),
Tail = new List<UTKAccordionContentSpec>
{
UTKAccordionContentSpec.FromIconButton(UTKMaterialIcons.Refresh, 12, "refresh_network", "새로고침"),
}
}
}
};
data.Sections.Add(settingsSection);
// ========================================
// 수평 레이아웃 섹션 2: Components
// ========================================
var componentsSection = new UTKAccordionSectionData
{
Title = "Components",
IsExpanded = false,
LayoutType = UTKAccordionLayoutType.Horizontal,
HorizontalItems = new List<UTKAccordionHorizontalItemData>
{
new UTKAccordionHorizontalItemData
{
Content = UTKAccordionContentSpec.FromText("Transform", "open_transform"),
},
new UTKAccordionHorizontalItemData
{
Content = UTKAccordionContentSpec.FromText("Rigidbody", "open_rigidbody"),
},
new UTKAccordionHorizontalItemData
{
Content = UTKAccordionContentSpec.FromText("Collider", "open_collider"),
}
}
};
data.Sections.Add(componentsSection);
// ========================================
// 그리드 레이아웃 섹션 1: Vehicles
// ========================================
var vehiclesSection = new UTKAccordionSectionData
{
Title = "Vehicles",
IsExpanded = true,
LayoutType = UTKAccordionLayoutType.Grid,
GridItems = new List<UTKAccordionGridItemData>
{
new UTKAccordionGridItemData
{
Caption = "Forklift",
ImagePath = "Simulator/Images/lib_forklift_400x300",
PrefabPath = "Simulator/FreeForkLift/Prefabs/Forklift",
Tag = "vehicle"
},
new UTKAccordionGridItemData
{
Caption = "Truck",
ImagePath = "Simulator/Images/lib_forklift_400x300",
PrefabPath = "Simulator/FreeForkLift/Prefabs/Forklift",
Tag = "vehicle"
}
}
};
data.Sections.Add(vehiclesSection);
// ========================================
// 그리드 레이아웃 섹션 2: Objects
// ========================================
var objectsSection = new UTKAccordionSectionData
{
Title = "Objects",
IsExpanded = true,
LayoutType = UTKAccordionLayoutType.Grid,
GridItems = new List<UTKAccordionGridItemData>
{
new UTKAccordionGridItemData
{
Caption = "Pallet",
ImagePath = "Simulator/Images/lib_pallet_400x300",
PrefabPath = "Simulator/FreeForkLift/Prefabs/PalletEmpty",
Tag = "object"
},
new UTKAccordionGridItemData
{
Caption = "Pallet (Full)",
ImagePath = "Simulator/Images/lib_pallet_400x300",
PrefabPath = "Simulator/FreeForkLift/Prefabs/PalletEmpty",
Tag = "object"
},
new UTKAccordionGridItemData
{
Caption = "Box",
ImagePath = "Simulator/Images/lib_pallet_400x300",
PrefabPath = "Simulator/FreeForkLift/Prefabs/PalletEmpty",
Tag = "object"
}
}
};
data.Sections.Add(objectsSection);
// ========================================
// 그리드 레이아웃 섹션 3: Characters
// ========================================
var charactersSection = new UTKAccordionSectionData
{
Title = "Characters",
IsExpanded = true,
LayoutType = UTKAccordionLayoutType.Grid,
GridItems = new List<UTKAccordionGridItemData>
{
new UTKAccordionGridItemData
{
Caption = "Worker",
ImagePath = "Simulator/Images/lib_worker_400x300",
PrefabPath = "Simulator/CharCrafter Free Preset Characters Pack (Vol. 1)/Prefabs/Male Young Guy",
Tag = "character"
},
new UTKAccordionGridItemData
{
Caption = "Manager",
ImagePath = "Simulator/Images/lib_worker_400x300",
PrefabPath = "Simulator/CharCrafter Free Preset Characters Pack (Vol. 1)/Prefabs/Male Young Guy",
Tag = "character"
}
}
};
data.Sections.Add(charactersSection);
// 데이터 설정
_accordionWindow.SetData(data);
Debug.Log($"[UTKAccordionListWindowSample] 테스트 데이터 생성 완료: {data.Sections.Count}개 섹션");
}
#endregion
#region
/// <summary>
/// UTKAccordionListWindow의 이벤트 핸들러들을 등록합니다.
/// </summary>
private void RegisterEventHandlers()
{
if (_accordionWindow == null) return;
// ========================================
// 수평 아이템 이벤트
// ========================================
// 수평 아이템 클릭 이벤트
_accordionWindow.OnHorizontalItemClick += (item, contentSpec) =>
{
var text = contentSpec?.Text ?? item.Content?.Text ?? "Unknown";
var actionId = contentSpec?.ActionId ?? "none";
Debug.Log($"[수평 아이템 클릭] {text} (ActionId: {actionId})");
// ActionId에 따라 분기 처리
HandleHorizontalItemAction(actionId);
};
// 수평 아이템 아이콘 버튼 클릭 이벤트
_accordionWindow.OnHorizontalItemIconClick += (item, iconSpec) =>
{
var actionId = iconSpec.ActionId ?? "none";
var itemText = item.Content?.Text ?? "Unknown";
Debug.Log($"[아이콘 버튼 클릭] {itemText} - {actionId}");
HandleHorizontalItemAction(actionId);
};
// ========================================
// 그리드 아이템 이벤트
// ========================================
// 그리드 아이템 클릭 이벤트
_accordionWindow.OnGridItemClick += (item) =>
{
Debug.Log($"[그리드 아이템 클릭] {item.Caption} (Tag: {item.Tag})");
};
// 드래그 시작 이벤트
_accordionWindow.OnGridItemBeginDrag += (item, position) =>
{
Debug.Log($"[드래그 시작] {item.Caption} at {position}");
};
// 드래그 중 이벤트
_accordionWindow.OnGridItemDrag += (item, position) =>
{
// 3D 프리뷰가 있으면 위치 업데이트
if (_dragPreview3D != null)
{
Update3DPreviewPosition(position);
}
};
// 드래그 종료 이벤트
_accordionWindow.OnGridItemEndDrag += (item, position) =>
{
Debug.Log($"[드래그 종료] {item.Caption} at {position}");
_lastDropScreenPosition = position;
// 드롭 이벤트에서 처리하므로 여기서는 프리뷰를 제거하지 않음
};
// 리스트 영역 이탈 이벤트 (3D 프리팹 미리보기 표시)
_accordionWindow.OnDragExitList += (item, position) =>
{
Debug.Log($"[리스트 영역 이탈] {item.Caption} - 3D 프리뷰 생성");
_isOutsideListArea = true;
Create3DPreview(item, position);
};
// 리스트 영역 진입 이벤트 (3D 프리팹 미리보기 숨김)
_accordionWindow.OnDragEnterList += (item, position) =>
{
Debug.Log($"[리스트 영역 진입] {item.Caption} - 3D 프리뷰 제거");
_isOutsideListArea = false;
Destroy3DPreview();
};
// 그리드 아이템 드롭 이벤트
_accordionWindow.OnGridItemDrop += (item) =>
{
Debug.Log($"[드롭] {item.Caption} - PrefabPath: {item.PrefabPath}, Outside: {_isOutsideListArea}");
// 리스트 영역 밖에서 드롭한 경우에만 프리팹 생성
if (!_isOutsideListArea)
{
Debug.Log("[UTKAccordionListWindowSample] 리스트 영역 내부에서 드롭 - 프리팹 생성 안함");
_isOutsideListArea = false;
return;
}
if (string.IsNullOrEmpty(item.PrefabPath))
{
Debug.LogWarning("[UTKAccordionListWindowSample] PrefabPath가 설정되지 않았습니다.");
Destroy3DPreview();
_isOutsideListArea = false;
return;
}
// 프리팹 로드
var prefab = Resources.Load<GameObject>(item.PrefabPath);
if (prefab == null)
{
Debug.LogWarning($"[UTKAccordionListWindowSample] 프리팹을 찾을 수 없습니다: {item.PrefabPath}");
Destroy3DPreview();
_isOutsideListArea = false;
return;
}
// 월드 좌표 계산
Vector3 worldPosition = ScreenToWorldPosition(_lastDropScreenPosition);
// 프리팹 인스턴스화
var instance = Instantiate(prefab, worldPosition, Quaternion.identity);
Debug.Log($"[UTKAccordionListWindowSample] 프리팹 생성됨: {instance.name} at {worldPosition}");
// 3D 프리뷰 정리 및 상태 초기화
Destroy3DPreview();
_isOutsideListArea = false;
};
// ========================================
// 섹션 이벤트
// ========================================
// 섹션 펼침/접힘 이벤트
_accordionWindow.OnSectionToggled += (section, isExpanded) =>
{
Debug.Log($"[섹션 토글] {section.Title} - {(isExpanded ? "" : "")}");
};
// ========================================
// 윈도우 이벤트
// ========================================
// 윈도우 닫기 이벤트
_accordionWindow.OnClosed += () =>
{
Debug.Log("[윈도우 닫힘]");
};
Debug.Log("[UTKAccordionListWindowSample] 이벤트 핸들러 등록 완료");
}
/// <summary>
/// 수평 아이템의 ActionId에 따라 동작을 처리합니다.
/// </summary>
private void HandleHorizontalItemAction(string? actionId)
{
switch (actionId)
{
case "open_graphics":
Debug.Log(" → Graphics 설정 열기");
break;
case "refresh_graphics":
Debug.Log(" → Graphics 새로고침");
break;
case "setting_graphics":
Debug.Log(" → Graphics 상세 설정");
break;
case "open_audio":
Debug.Log(" → Audio 설정 열기");
break;
case "refresh_audio":
Debug.Log(" → Audio 새로고침");
break;
case "setting_audio":
Debug.Log(" → Audio 상세 설정");
break;
case "open_network":
Debug.Log(" → Network 설정 열기");
break;
case "refresh_network":
Debug.Log(" → Network 새로고침");
break;
case "open_transform":
Debug.Log(" → Transform 컴포넌트 열기");
break;
case "open_rigidbody":
Debug.Log(" → Rigidbody 컴포넌트 열기");
break;
case "open_collider":
Debug.Log(" → Collider 컴포넌트 열기");
break;
default:
Debug.Log($" → 알 수 없는 액션: {actionId}");
break;
}
}
#endregion
#region (Coordinate Conversion)
/// <summary>
/// UI Toolkit 좌표를 Unity Screen 좌표로 변환합니다.
/// </summary>
private Vector2 UIToolkitToScreenPosition(Vector2 uiToolkitPosition)
{
return new Vector2(uiToolkitPosition.x, Screen.height - uiToolkitPosition.y);
}
/// <summary>
/// 스크린 좌표를 월드 좌표로 변환합니다.
/// </summary>
private Vector3 ScreenToWorldPosition(Vector2 uiToolkitPosition)
{
_mainCamera ??= Camera.main;
if (_mainCamera == null)
{
Debug.LogWarning("[UTKAccordionListWindowSample] 메인 카메라를 찾을 수 없습니다.");
return Vector3.zero;
}
Vector2 screenPosition = UIToolkitToScreenPosition(uiToolkitPosition);
Ray ray = _mainCamera.ScreenPointToRay(screenPosition);
// 바닥면(Y=0)과의 교차점 계산
Plane groundPlane = new Plane(Vector3.up, Vector3.zero);
if (groundPlane.Raycast(ray, out float distance))
{
return ray.GetPoint(distance);
}
// 물리 레이캐스트
if (Physics.Raycast(ray, out RaycastHit hit, 100f))
{
return hit.point;
}
return ray.GetPoint(10f);
}
#endregion
#region 3D (3D Preview)
/// <summary>
/// 드래그 중 3D 프리뷰 오브젝트를 생성합니다.
/// </summary>
private void Create3DPreview(UTKAccordionGridItemData item, Vector2 screenPosition)
{
Destroy3DPreview();
if (string.IsNullOrEmpty(item.PrefabPath)) return;
var prefab = Resources.Load<GameObject>(item.PrefabPath);
if (prefab == null)
{
Debug.LogWarning($"[UTKAccordionListWindowSample] 프리팹을 찾을 수 없습니다: {item.PrefabPath}");
return;
}
Vector3 worldPosition = ScreenToWorldPosition(screenPosition);
_dragPreview3D = Instantiate(prefab, worldPosition, Quaternion.identity);
_dragPreview3D.name = $"DragPreview_{item.Caption}";
SetPreviewTransparency(_dragPreview3D, 0.6f);
DisableColliders(_dragPreview3D);
Debug.Log($"[UTKAccordionListWindowSample] 3D 프리뷰 생성됨: {_dragPreview3D.name} at {worldPosition}");
}
/// <summary>
/// 3D 프리뷰 오브젝트를 제거합니다.
/// </summary>
private void Destroy3DPreview()
{
if (_dragPreview3D != null)
{
Destroy(_dragPreview3D);
_dragPreview3D = null;
}
}
/// <summary>
/// 3D 프리뷰 오브젝트의 위치를 업데이트합니다.
/// </summary>
private void Update3DPreviewPosition(Vector2 screenPosition)
{
if (_dragPreview3D == null) return;
_dragPreview3D.transform.position = ScreenToWorldPosition(screenPosition);
}
/// <summary>
/// 오브젝트의 모든 렌더러를 반투명하게 설정합니다.
/// </summary>
private void SetPreviewTransparency(GameObject obj, float alpha)
{
var renderers = obj.GetComponentsInChildren<Renderer>();
foreach (var renderer in renderers)
{
foreach (var material in renderer.materials)
{
if (material.HasProperty("_Color"))
{
var color = material.color;
color.a = alpha;
material.color = color;
material.SetFloat("_Mode", 3);
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
material.SetInt("_ZWrite", 0);
material.DisableKeyword("_ALPHATEST_ON");
material.EnableKeyword("_ALPHABLEND_ON");
material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
material.renderQueue = 3000;
}
}
}
}
/// <summary>
/// 오브젝트의 모든 콜라이더를 비활성화합니다.
/// </summary>
private void DisableColliders(GameObject obj)
{
var colliders = obj.GetComponentsInChildren<Collider>();
foreach (var collider in colliders)
{
collider.enabled = false;
}
}
#endregion
#region
/// <summary>
/// 모든 섹션을 펼칩니다.
/// </summary>
[ContextMenu("Expand All Sections")]
public void ExpandAllSections()
{
_accordionWindow?.ExpandAll();
Debug.Log("[UTKAccordionListWindowSample] 모든 섹션 펼침");
}
/// <summary>
/// 모든 섹션을 접습니다.
/// </summary>
[ContextMenu("Collapse All Sections")]
public void CollapseAllSections()
{
_accordionWindow?.CollapseAll();
Debug.Log("[UTKAccordionListWindowSample] 모든 섹션 접힘");
}
/// <summary>
/// 검색을 테스트합니다.
/// </summary>
[ContextMenu("Test Search (Pallet)")]
public void TestSearch()
{
_accordionWindow?.Search("Pallet");
Debug.Log("[UTKAccordionListWindowSample] 검색 테스트: 'Pallet'");
}
/// <summary>
/// 검색을 초기화합니다.
/// </summary>
[ContextMenu("Clear Search")]
public void ClearSearchTest()
{
_accordionWindow?.ClearSearch();
Debug.Log("[UTKAccordionListWindowSample] 검색 초기화");
}
/// <summary>
/// 모든 데이터를 제거합니다.
/// </summary>
[ContextMenu("Clear All Data")]
public void ClearAllData()
{
_accordionWindow?.Clear();
Debug.Log("[UTKAccordionListWindowSample] 모든 데이터 제거됨");
}
/// <summary>
/// 윈도우를 다시 표시합니다.
/// </summary>
[ContextMenu("Show Window")]
public void ShowWindow()
{
_accordionWindow?.Show();
}
/// <summary>
/// 데이터를 다시 로드합니다.
/// </summary>
[ContextMenu("Reload Data")]
public void ReloadData()
{
CreateTestData();
Debug.Log("[UTKAccordionListWindowSample] 데이터 다시 로드됨");
}
/// <summary>
/// 새로운 통합 API(UTKAccordionItemData)를 사용하여 데이터를 생성합니다.
/// TreeView 기반 구현에 최적화된 데이터 구조입니다.
/// </summary>
[ContextMenu("Load Data (New API)")]
public void LoadDataWithNewAPI()
{
if (_accordionWindow == null) return;
var roots = new List<UTKAccordionItemData>();
// ========================================
// 섹션 1: Settings (수평 레이아웃)
// ========================================
var settingsSection = UTKAccordionItemData.CreateSection("Settings", isExpanded: true);
settingsSection
.AddHorizontalItem(
head: UTKAccordionContentSpec.FromImage("Prefabs/UI/images/icon_side_tab_library_128"),
content: UTKAccordionContentSpec.FromText("Graphics", "open_graphics"),
tail: new List<UTKAccordionContentSpec>
{
UTKAccordionContentSpec.FromIconButton(UTKMaterialIcons.Refresh, 12, "refresh_graphics", "새로고침"),
UTKAccordionContentSpec.FromIconButton(UTKMaterialIcons.Settings, 12, "setting_graphics", "설정"),
})
.AddHorizontalItem(
head: UTKAccordionContentSpec.FromImage("Prefabs/UI/images/icon_side_tab_fleet_128"),
content: UTKAccordionContentSpec.FromText("Audio", "open_audio"),
tail: new List<UTKAccordionContentSpec>
{
UTKAccordionContentSpec.FromIconButton(UTKMaterialIcons.Refresh, 12, "refresh_audio", "새로고침"),
});
roots.Add(settingsSection);
// ========================================
// 섹션 2: Vehicles (그리드 레이아웃)
// ========================================
var vehiclesSection = UTKAccordionItemData.CreateSection("Vehicles", isExpanded: true);
vehiclesSection
.AddGridItem("Forklift", "Simulator/Images/lib_forklift_400x300", "Simulator/FreeForkLift/Prefabs/Forklift", "vehicle")
.AddGridItem("Truck", "Simulator/Images/lib_forklift_400x300", "Simulator/FreeForkLift/Prefabs/Forklift", "vehicle");
roots.Add(vehiclesSection);
// ========================================
// 섹션 3: Objects (그리드 레이아웃)
// ========================================
var objectsSection = UTKAccordionItemData.CreateSection("Objects", isExpanded: true);
objectsSection
.AddGridItem("Pallet", "Simulator/Images/lib_pallet_400x300", "Simulator/FreeForkLift/Prefabs/PalletEmpty", "object")
.AddGridItem("Box", "Simulator/Images/lib_pallet_400x300", "Simulator/FreeForkLift/Prefabs/PalletEmpty", "object");
roots.Add(objectsSection);
// 새로운 통합 API로 데이터 설정
_accordionWindow.SetData(roots);
Debug.Log($"[UTKAccordionListWindowSample] 새 API로 데이터 생성 완료: {roots.Count}개 섹션");
}
#endregion
}