310 lines
8.6 KiB
C#
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
|
|
}
|
|
}
|