기본 Styleguide 완료
This commit is contained in:
492
Assets/Scripts/UVC/UIToolkit/Common/UTKImage.cs
Normal file
492
Assets/Scripts/UVC/UIToolkit/Common/UTKImage.cs
Normal file
@@ -0,0 +1,492 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace UVC.UIToolkit
|
||||
{
|
||||
/// <summary>
|
||||
/// 이미지 컴포넌트.
|
||||
/// Resources 폴더 내부 경로 또는 HTTP/HTTPS URL에서 이미지를 로드하여 표시합니다.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>UTKImage란?</b></para>
|
||||
/// <para>
|
||||
/// UTKImage는 UXML에서 직접 사용할 수 있는 이미지 표시 컴포넌트입니다.
|
||||
/// <c>src</c> 속성에 경로를 지정하면 자동으로 이미지를 로드하여 표시합니다.
|
||||
/// 내부 리소스(Resources 폴더)와 외부 URL(HTTP/HTTPS) 모두 지원합니다.
|
||||
/// </para>
|
||||
///
|
||||
/// <para><b>지원하는 이미지 소스:</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><b>내부 리소스</b> - Assets/Resources 폴더 내 이미지 경로 (확장자 제외)</description></item>
|
||||
/// <item><description><b>외부 URL</b> - http:// 또는 https://로 시작하는 이미지 URL</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>주요 속성:</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><c>src</c> - 이미지 소스 경로 (Resources 경로 또는 HTTP URL)</description></item>
|
||||
/// <item><description><c>scale-mode</c> - 이미지 스케일 모드 (StretchToFill, ScaleAndCrop, ScaleToFit)</description></item>
|
||||
/// <item><description><c>tint-color</c> - 이미지 틴트 색상</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>이미지 스케일 모드:</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><c>StretchToFill</c> - 요소 크기에 맞게 늘림 (비율 무시)</description></item>
|
||||
/// <item><description><c>ScaleAndCrop</c> - 비율 유지하며 요소를 채움 (잘릴 수 있음)</description></item>
|
||||
/// <item><description><c>ScaleToFit</c> - 비율 유지하며 요소 안에 맞춤 (여백 발생 가능)</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>실제 활용 예시:</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>프로필 이미지, 아바타</description></item>
|
||||
/// <item><description>썸네일, 미리보기 이미지</description></item>
|
||||
/// <item><description>배너, 광고 이미지</description></item>
|
||||
/// <item><description>제품 이미지, 갤러리</description></item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <para><b>C# 코드에서 사용:</b></para>
|
||||
/// <code>
|
||||
/// // 내부 리소스 이미지 로드
|
||||
/// var image = new UTKImage();
|
||||
/// image.Src = "UIToolkit/Images/icon_setting_22";
|
||||
/// image.style.width = 100;
|
||||
/// image.style.height = 100;
|
||||
///
|
||||
/// // 외부 URL 이미지 로드
|
||||
/// var webImage = new UTKImage();
|
||||
/// webImage.Src = "https://example.com/image.png";
|
||||
/// webImage.ScaleMode = ScaleMode.ScaleToFit;
|
||||
///
|
||||
/// // 생성자에서 소스 지정
|
||||
/// var profileImage = new UTKImage("https://api.example.com/avatar/123");
|
||||
/// profileImage.style.width = 64;
|
||||
/// profileImage.style.height = 64;
|
||||
/// profileImage.style.borderTopLeftRadius = 32;
|
||||
/// profileImage.style.borderTopRightRadius = 32;
|
||||
/// profileImage.style.borderBottomLeftRadius = 32;
|
||||
/// profileImage.style.borderBottomRightRadius = 32;
|
||||
///
|
||||
/// // 이미지 로드 이벤트 핸들링
|
||||
/// image.OnImageLoaded += (texture) => Debug.Log("이미지 로드 완료");
|
||||
/// image.OnImageFailed += (error) => Debug.LogError($"로드 실패: {error}");
|
||||
///
|
||||
/// // 틴트 색상 적용
|
||||
/// image.TintColor = Color.red;
|
||||
///
|
||||
/// // 비동기 로드
|
||||
/// await image.LoadAsync("https://example.com/image.png", cancellationToken);
|
||||
/// </code>
|
||||
/// <para><b>UXML에서 사용:</b></para>
|
||||
/// <code>
|
||||
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
|
||||
/// <!-- 내부 리소스 이미지 -->
|
||||
/// <utk:UTKImage src="UIToolkit/Images/icon_setting_22"
|
||||
/// style="width: 48px; height: 48px;" />
|
||||
///
|
||||
/// <!-- 외부 URL 이미지 -->
|
||||
/// <utk:UTKImage src="https://example.com/image.png"
|
||||
/// style="width: 200px; height: 150px;"
|
||||
/// scale-mode="ScaleToFit" />
|
||||
///
|
||||
/// <!-- 둥근 프로필 이미지 -->
|
||||
/// <utk:UTKImage src="https://api.example.com/avatar"
|
||||
/// class="profile-avatar" />
|
||||
///
|
||||
/// <!-- 틴트 색상 적용 -->
|
||||
/// <utk:UTKImage src="UIToolkit/Images/icon_home"
|
||||
/// tint-color="#FF5500" />
|
||||
/// </ui:UXML>
|
||||
/// </code>
|
||||
/// </example>
|
||||
[UxmlElement]
|
||||
public partial class UTKImage : VisualElement, IDisposable
|
||||
{
|
||||
#region Constants
|
||||
private const string USS_PATH = "UIToolkit/Common/UTKImage";
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
private bool _disposed;
|
||||
private string _src = "";
|
||||
private ScaleMode _scaleMode = ScaleMode.ScaleToFit;
|
||||
private Color _tintColor = Color.white;
|
||||
private Texture2D? _loadedTexture;
|
||||
private CancellationTokenSource? _loadCts;
|
||||
private bool _isExternalTexture;
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
/// <summary>이미지 로드 완료 이벤트</summary>
|
||||
public event Action<Texture2D>? OnImageLoaded;
|
||||
/// <summary>이미지 로드 실패 이벤트</summary>
|
||||
public event Action<string>? OnImageFailed;
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
/// <summary>
|
||||
/// 이미지 소스 경로.
|
||||
/// Resources 폴더 내 경로(확장자 제외) 또는 HTTP/HTTPS URL을 지정합니다.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 내부 리소스
|
||||
/// image.Src = "UIToolkit/Images/icon_setting_22";
|
||||
///
|
||||
/// // 외부 URL
|
||||
/// image.Src = "https://example.com/image.png";
|
||||
/// </code>
|
||||
/// </example>
|
||||
[UxmlAttribute("src")]
|
||||
public string Src
|
||||
{
|
||||
get => _src;
|
||||
set
|
||||
{
|
||||
if (_src == value) return;
|
||||
_src = value;
|
||||
LoadImageFromSource();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>이미지 스케일 모드</summary>
|
||||
[UxmlAttribute("scale-mode")]
|
||||
public ScaleMode ScaleMode
|
||||
{
|
||||
get => _scaleMode;
|
||||
set
|
||||
{
|
||||
_scaleMode = value;
|
||||
UpdateBackgroundScaleMode();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>이미지 틴트 색상</summary>
|
||||
[UxmlAttribute("tint-color")]
|
||||
public Color TintColor
|
||||
{
|
||||
get => _tintColor;
|
||||
set
|
||||
{
|
||||
_tintColor = value;
|
||||
style.unityBackgroundImageTintColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>현재 로드된 텍스처</summary>
|
||||
public Texture2D? LoadedTexture => _loadedTexture;
|
||||
|
||||
/// <summary>이미지 로드 중 여부</summary>
|
||||
public bool IsLoading => _loadCts != null && !_loadCts.IsCancellationRequested;
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
public UTKImage()
|
||||
{
|
||||
UTKThemeManager.Instance.ApplyThemeToElement(this);
|
||||
|
||||
var uss = Resources.Load<StyleSheet>(USS_PATH);
|
||||
if (uss != null)
|
||||
{
|
||||
styleSheets.Add(uss);
|
||||
}
|
||||
|
||||
SetupStyles();
|
||||
SubscribeToThemeChanges();
|
||||
|
||||
RegisterCallback<AttachToPanelEvent>(OnAttachToPanel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 소스를 지정하여 이미지를 생성합니다.
|
||||
/// </summary>
|
||||
/// <param name="src">이미지 소스 경로 (Resources 경로 또는 HTTP URL)</param>
|
||||
public UTKImage(string src) : this()
|
||||
{
|
||||
_src = src;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Texture2D로 직접 이미지를 생성합니다.
|
||||
/// </summary>
|
||||
/// <param name="texture">표시할 텍스처</param>
|
||||
public UTKImage(Texture2D texture) : this()
|
||||
{
|
||||
SetTexture(texture);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Setup
|
||||
private void SetupStyles()
|
||||
{
|
||||
AddToClassList("utk-image");
|
||||
style.unityBackgroundScaleMode = _scaleMode;
|
||||
}
|
||||
|
||||
private void SubscribeToThemeChanges()
|
||||
{
|
||||
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
|
||||
RegisterCallback<DetachFromPanelEvent>(_ =>
|
||||
{
|
||||
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
|
||||
});
|
||||
}
|
||||
|
||||
private void OnThemeChanged(UTKTheme theme)
|
||||
{
|
||||
UTKThemeManager.Instance.ApplyThemeToElement(this);
|
||||
}
|
||||
|
||||
private void OnAttachToPanel(AttachToPanelEvent evt)
|
||||
{
|
||||
UnregisterCallback<AttachToPanelEvent>(OnAttachToPanel);
|
||||
|
||||
// UXML에서 설정된 src가 있으면 로드
|
||||
if (!string.IsNullOrEmpty(_src))
|
||||
{
|
||||
LoadImageFromSource();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Image Loading
|
||||
private void LoadImageFromSource()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_src))
|
||||
{
|
||||
ClearImage();
|
||||
return;
|
||||
}
|
||||
|
||||
// 이전 로드 취소
|
||||
CancelCurrentLoad();
|
||||
|
||||
if (IsExternalUrl(_src))
|
||||
{
|
||||
LoadFromUrlAsync(_src).Forget();
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadFromResources(_src);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resources 폴더에서 이미지를 동기로 로드합니다.
|
||||
/// </summary>
|
||||
/// <param name="resourcePath">Resources 폴더 내 경로 (확장자 제외)</param>
|
||||
public void LoadFromResources(string resourcePath)
|
||||
{
|
||||
var texture = Resources.Load<Texture2D>(resourcePath);
|
||||
if (texture != null)
|
||||
{
|
||||
_isExternalTexture = false;
|
||||
ApplyTexture(texture);
|
||||
OnImageLoaded?.Invoke(texture);
|
||||
}
|
||||
else
|
||||
{
|
||||
var errorMsg = $"리소스를 찾을 수 없습니다: {resourcePath}";
|
||||
Debug.LogWarning($"[UTKImage] {errorMsg}");
|
||||
OnImageFailed?.Invoke(errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resources 폴더에서 이미지를 비동기로 로드합니다.
|
||||
/// </summary>
|
||||
/// <param name="resourcePath">Resources 폴더 내 경로 (확장자 제외)</param>
|
||||
/// <param name="ct">취소 토큰</param>
|
||||
public async UniTask LoadFromResourcesAsync(string resourcePath, CancellationToken ct = default)
|
||||
{
|
||||
CancelCurrentLoad();
|
||||
_loadCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
|
||||
try
|
||||
{
|
||||
var request = Resources.LoadAsync<Texture2D>(resourcePath);
|
||||
await request.ToUniTask(cancellationToken: _loadCts.Token);
|
||||
|
||||
var texture = request.asset as Texture2D;
|
||||
if (texture != null)
|
||||
{
|
||||
_isExternalTexture = false;
|
||||
ApplyTexture(texture);
|
||||
OnImageLoaded?.Invoke(texture);
|
||||
}
|
||||
else
|
||||
{
|
||||
var errorMsg = $"리소스를 찾을 수 없습니다: {resourcePath}";
|
||||
Debug.LogWarning($"[UTKImage] {errorMsg}");
|
||||
OnImageFailed?.Invoke(errorMsg);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 취소됨
|
||||
}
|
||||
finally
|
||||
{
|
||||
_loadCts?.Dispose();
|
||||
_loadCts = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 외부 URL에서 이미지를 비동기로 로드합니다.
|
||||
/// </summary>
|
||||
/// <param name="url">이미지 URL (http:// 또는 https://)</param>
|
||||
/// <param name="ct">취소 토큰</param>
|
||||
public async UniTask LoadFromUrlAsync(string url, CancellationToken ct = default)
|
||||
{
|
||||
CancelCurrentLoad();
|
||||
_loadCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
|
||||
try
|
||||
{
|
||||
AddToClassList("utk-image--loading");
|
||||
|
||||
using var request = UnityWebRequestTexture.GetTexture(url);
|
||||
await request.SendWebRequest().ToUniTask(cancellationToken: _loadCts.Token);
|
||||
|
||||
if (request.result == UnityWebRequest.Result.Success)
|
||||
{
|
||||
var texture = DownloadHandlerTexture.GetContent(request);
|
||||
if (texture != null)
|
||||
{
|
||||
// 이전 외부 텍스처 정리
|
||||
CleanupExternalTexture();
|
||||
|
||||
_isExternalTexture = true;
|
||||
ApplyTexture(texture);
|
||||
OnImageLoaded?.Invoke(texture);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var errorMsg = $"이미지 로드 실패: {request.error}";
|
||||
Debug.LogWarning($"[UTKImage] {errorMsg}");
|
||||
OnImageFailed?.Invoke(errorMsg);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 취소됨
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMsg = $"이미지 로드 중 오류: {ex.Message}";
|
||||
Debug.LogError($"[UTKImage] {errorMsg}");
|
||||
OnImageFailed?.Invoke(errorMsg);
|
||||
}
|
||||
finally
|
||||
{
|
||||
RemoveFromClassList("utk-image--loading");
|
||||
_loadCts?.Dispose();
|
||||
_loadCts = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 소스에서 이미지를 비동기로 로드합니다.
|
||||
/// Resources 경로 또는 HTTP URL을 자동으로 감지합니다.
|
||||
/// </summary>
|
||||
/// <param name="source">이미지 소스 (Resources 경로 또는 HTTP URL)</param>
|
||||
/// <param name="ct">취소 토큰</param>
|
||||
public async UniTask LoadAsync(string source, CancellationToken ct = default)
|
||||
{
|
||||
_src = source;
|
||||
|
||||
if (IsExternalUrl(source))
|
||||
{
|
||||
await LoadFromUrlAsync(source, ct);
|
||||
}
|
||||
else
|
||||
{
|
||||
await LoadFromResourcesAsync(source, ct);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Texture2D를 직접 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="texture">표시할 텍스처</param>
|
||||
public void SetTexture(Texture2D texture)
|
||||
{
|
||||
CleanupExternalTexture();
|
||||
_isExternalTexture = false;
|
||||
ApplyTexture(texture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이미지를 제거합니다.
|
||||
/// </summary>
|
||||
public void ClearImage()
|
||||
{
|
||||
CleanupExternalTexture();
|
||||
_loadedTexture = null;
|
||||
style.backgroundImage = StyleKeyword.None;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
private void ApplyTexture(Texture2D texture)
|
||||
{
|
||||
_loadedTexture = texture;
|
||||
style.backgroundImage = new StyleBackground(texture);
|
||||
style.unityBackgroundScaleMode = _scaleMode;
|
||||
style.unityBackgroundImageTintColor = _tintColor;
|
||||
}
|
||||
|
||||
private void UpdateBackgroundScaleMode()
|
||||
{
|
||||
style.unityBackgroundScaleMode = _scaleMode;
|
||||
}
|
||||
|
||||
private void CancelCurrentLoad()
|
||||
{
|
||||
if (_loadCts != null)
|
||||
{
|
||||
_loadCts.Cancel();
|
||||
_loadCts.Dispose();
|
||||
_loadCts = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void CleanupExternalTexture()
|
||||
{
|
||||
if (_isExternalTexture && _loadedTexture != null)
|
||||
{
|
||||
UnityEngine.Object.Destroy(_loadedTexture);
|
||||
_loadedTexture = null;
|
||||
_isExternalTexture = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsExternalUrl(string source)
|
||||
{
|
||||
return source.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
|
||||
source.StartsWith("https://", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
|
||||
CancelCurrentLoad();
|
||||
CleanupExternalTexture();
|
||||
|
||||
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
|
||||
OnImageLoaded = null;
|
||||
OnImageFailed = null;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user