Files
XRLib/Assets/Scripts/UVC/UIToolkit/Card/UTKCard.cs
2026-01-08 20:15:57 +09:00

310 lines
8.6 KiB
C#

#nullable enable
using System;
using UnityEngine;
using UnityEngine.UIElements;
namespace UVC.UIToolkit
{
/// <summary>
/// 카드 컴포넌트.
/// 콘텐츠를 카드 형태로 표시합니다.
/// </summary>
[UxmlElement]
public partial class UTKCard : VisualElement, IDisposable
{
#region Constants
private const string USS_PATH = "UIToolkit/Card/UTKCard";
#endregion
#region Fields
private bool _disposed;
private VisualElement? _imageContainer;
private VisualElement? _header;
private Label? _titleLabel;
private Label? _subtitleLabel;
private VisualElement? _content;
private VisualElement? _actions;
private string _title = "";
private string _subtitle = "";
private Texture2D? _image;
private CardVariant _variant = CardVariant.Elevated;
private bool _isClickable;
#endregion
#region Events
/// <summary>카드 클릭 이벤트</summary>
public event Action? OnClicked;
#endregion
#region Properties
/// <summary>카드 제목</summary>
[UxmlAttribute]
public string Title
{
get => _title;
set
{
_title = value;
if (_titleLabel != null)
{
_titleLabel.text = value;
_titleLabel.style.display = string.IsNullOrEmpty(value) ? DisplayStyle.None : DisplayStyle.Flex;
}
UpdateHeaderVisibility();
}
}
/// <summary>카드 부제목</summary>
[UxmlAttribute]
public string Subtitle
{
get => _subtitle;
set
{
_subtitle = value;
if (_subtitleLabel != null)
{
_subtitleLabel.text = value;
_subtitleLabel.style.display = string.IsNullOrEmpty(value) ? DisplayStyle.None : DisplayStyle.Flex;
}
UpdateHeaderVisibility();
}
}
/// <summary>카드 이미지</summary>
public Texture2D? Image
{
get => _image;
set
{
_image = value;
UpdateImage();
}
}
/// <summary>카드 스타일</summary>
[UxmlAttribute]
public CardVariant Variant
{
get => _variant;
set
{
_variant = value;
UpdateVariant();
}
}
/// <summary>클릭 가능 여부</summary>
[UxmlAttribute]
public bool IsClickable
{
get => _isClickable;
set
{
_isClickable = value;
EnableInClassList("utk-card--clickable", value);
focusable = value;
}
}
/// <summary>콘텐츠 컨테이너</summary>
public VisualElement? ContentContainer => _content;
/// <summary>액션 컨테이너</summary>
public VisualElement? ActionsContainer => _actions;
#endregion
#region Enums
public enum CardVariant
{
Elevated,
Outlined,
Filled
}
#endregion
#region Constructor
public UTKCard()
{
UTKThemeManager.Instance.ApplyThemeToElement(this);
var uss = Resources.Load<StyleSheet>(USS_PATH);
if (uss != null)
{
styleSheets.Add(uss);
}
CreateUI();
SetupEvents();
SubscribeToThemeChanges();
}
public UTKCard(string title, string subtitle = "") : this()
{
Title = title;
Subtitle = subtitle;
}
#endregion
#region UI Creation
private void CreateUI()
{
AddToClassList("utk-card");
// Image Container
_imageContainer = new VisualElement { name = "image-container" };
_imageContainer.AddToClassList("utk-card__image");
_imageContainer.style.display = DisplayStyle.None;
hierarchy.Add(_imageContainer);
// Header
_header = new VisualElement { name = "header" };
_header.AddToClassList("utk-card__header");
hierarchy.Add(_header);
_titleLabel = new Label { name = "title" };
_titleLabel.AddToClassList("utk-card__title");
_titleLabel.style.display = DisplayStyle.None;
_header.Add(_titleLabel);
_subtitleLabel = new Label { name = "subtitle" };
_subtitleLabel.AddToClassList("utk-card__subtitle");
_subtitleLabel.style.display = DisplayStyle.None;
_header.Add(_subtitleLabel);
// Content
_content = new VisualElement { name = "content" };
_content.AddToClassList("utk-card__content");
hierarchy.Add(_content);
// Actions
_actions = new VisualElement { name = "actions" };
_actions.AddToClassList("utk-card__actions");
hierarchy.Add(_actions);
UpdateVariant();
UpdateHeaderVisibility();
}
private void SetupEvents()
{
RegisterCallback<ClickEvent>(OnClick);
RegisterCallback<KeyDownEvent>(OnKeyDown);
}
private void SubscribeToThemeChanges()
{
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
RegisterCallback<DetachFromPanelEvent>(_ =>
{
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
});
}
private void OnThemeChanged(UTKTheme theme)
{
UTKThemeManager.Instance.ApplyThemeToElement(this);
}
#endregion
#region Event Handlers
private void OnClick(ClickEvent evt)
{
if (!_isClickable) return;
OnClicked?.Invoke();
evt.StopPropagation();
}
private void OnKeyDown(KeyDownEvent evt)
{
if (!_isClickable) return;
if (evt.keyCode == KeyCode.Return || evt.keyCode == KeyCode.Space)
{
OnClicked?.Invoke();
evt.StopPropagation();
}
}
#endregion
#region Methods
private void UpdateVariant()
{
RemoveFromClassList("utk-card--elevated");
RemoveFromClassList("utk-card--outlined");
RemoveFromClassList("utk-card--filled");
var variantClass = _variant switch
{
CardVariant.Outlined => "utk-card--outlined",
CardVariant.Filled => "utk-card--filled",
_ => "utk-card--elevated"
};
AddToClassList(variantClass);
}
private void UpdateImage()
{
if (_imageContainer == null) return;
if (_image != null)
{
_imageContainer.style.backgroundImage = new StyleBackground(_image);
_imageContainer.style.display = DisplayStyle.Flex;
}
else
{
_imageContainer.style.backgroundImage = StyleKeyword.None;
_imageContainer.style.display = DisplayStyle.None;
}
}
private void UpdateHeaderVisibility()
{
if (_header == null) return;
bool hasContent = !string.IsNullOrEmpty(_title) || !string.IsNullOrEmpty(_subtitle);
_header.style.display = hasContent ? DisplayStyle.Flex : DisplayStyle.None;
}
/// <summary>
/// 콘텐츠 추가
/// </summary>
public new void Add(VisualElement element)
{
_content?.Add(element);
}
/// <summary>
/// 액션 버튼 추가
/// </summary>
public void AddAction(VisualElement element)
{
_actions?.Add(element);
}
/// <summary>
/// 액션 영역 표시/숨김
/// </summary>
public void SetActionsVisible(bool visible)
{
if (_actions != null)
{
_actions.style.display = visible ? DisplayStyle.Flex : DisplayStyle.None;
}
}
#endregion
#region IDisposable
public void Dispose()
{
if (_disposed) return;
_disposed = true;
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
OnClicked = null;
}
#endregion
}
}