#nullable enable using System; using UnityEngine; using UnityEngine.UIElements; namespace UVC.UIToolkit { /// /// 프로그레스 바 컴포넌트. /// Unity ProgressBar를 래핑하여 커스텀 스타일을 적용합니다. /// 작업의 진행 상황을 시각적으로 표시합니다. /// /// /// ProgressBar(진행률 표시줄)란? /// /// ProgressBar는 작업의 완료 정도를 시각적으로 표시하는 UI 컨트롤입니다. /// 파일 다운로드, 로딩, 설치 진행률 등 사용자에게 대기 시간과 완료 상태를 알려줍니다. /// /// /// 주요 속성: /// /// Value - 현재 진행 값 /// MinValue - 최소값 (보통 0) /// MaxValue - 최대값 (보통 100) /// ShowValue - 값 텍스트 표시 여부 /// ShowPercentage - 퍼센트로 표시 (true) 또는 값/최대값으로 표시 (false) /// IsIndeterminate - 불확정 상태 (진행률을 알 수 없을 때 애니메이션) /// Variant - 스타일 변형 (Default, Success, Warning, Error) /// /// /// Variant 스타일: /// /// Default - 기본 파란색 (일반 진행) /// Success - 녹색 (완료, 성공) /// Warning - 주황색 (주의 필요) /// Error - 빨간색 (오류, 실패) /// /// /// 불확정 상태(Indeterminate): /// /// 작업 완료 시점을 알 수 없을 때 사용합니다 (예: 서버 응답 대기). /// 바가 좌우로 움직이는 애니메이션으로 "작업 중"임을 표시합니다. /// /// /// 실제 활용 예시: /// /// 파일 다운로드/업로드 진행률 /// 게임 로딩 화면 /// 설치/업데이트 진행 상태 /// 데이터 처리 진행률 /// 퀘스트/미션 달성도 /// /// /// /// C# 코드에서 사용: /// /// // 다운로드 진행률 표시 /// var downloadProgress = new UTKProgressBar("다운로드 중...", 0, 100); /// downloadProgress.ShowPercentage = true; /// /// // 비동기 다운로드 시뮬레이션 /// async UniTask DownloadFile() /// { /// for (int i = 0; i <= 100; i++) /// { /// downloadProgress.Value = i; /// await UniTask.Delay(50); /// } /// downloadProgress.Variant = UTKProgressBar.ProgressBarVariant.Success; /// } /// /// // 불확정 상태 (서버 응답 대기) /// var loadingBar = new UTKProgressBar(); /// loadingBar.IsIndeterminate = true; // 무한 애니메이션 /// /// // 상태별 색상 변경 /// progressBar.Variant = UTKProgressBar.ProgressBarVariant.Warning; // 주의 /// progressBar.Variant = UTKProgressBar.ProgressBarVariant.Error; // 실패 /// /// // 값/최대값 형식 (예: 50/100) /// var itemProgress = new UTKProgressBar("수집한 아이템", 0, 100); /// itemProgress.ShowPercentage = false; // "50/100" 형식으로 표시 /// itemProgress.Value = 50; /// /// UXML에서 사용: /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// [UxmlElement] public partial class UTKProgressBar : ProgressBar, IDisposable { #region Constants private const string USS_PATH = "UIToolkit/Slider/UTKProgressBar"; #endregion #region Fields private bool _disposed; private bool _showValue = true; private bool _showPercentage = true; private bool _isIndeterminate; private ProgressBarVariant _variant = ProgressBarVariant.Default; private string _baseTitle = ""; #endregion #region Properties /// 현재 값 public float Value { get => value; set => SetValue(value); } /// 최소값 public float MinValue { get => lowValue; set { lowValue = value; UpdateValueLabel(); } } /// 최대값 public float MaxValue { get => highValue; set { highValue = value; UpdateValueLabel(); } } /// 값 표시 여부 [UxmlAttribute("show-value")] public bool ShowValue { get => _showValue; set { _showValue = value; UpdateValueLabel(); } } /// 퍼센트로 표시 여부 [UxmlAttribute("show-percentage")] public bool ShowPercentage { get => _showPercentage; set { _showPercentage = value; UpdateValueLabel(); } } /// 불확정 상태 (애니메이션) [UxmlAttribute("is-indeterminate")] public bool IsIndeterminate { get => _isIndeterminate; set { _isIndeterminate = value; EnableInClassList("utk-progress--indeterminate", value); UpdateValueLabel(); } } /// 스타일 변형 [UxmlAttribute("variant")] public ProgressBarVariant Variant { get => _variant; set { _variant = value; UpdateVariant(); } } #endregion #region Enums public enum ProgressBarVariant { Default, Success, Warning, Error } #endregion #region Constructor public UTKProgressBar() : base() { UTKThemeManager.Instance.ApplyThemeToElement(this); var uss = Resources.Load(USS_PATH); if (uss != null) { styleSheets.Add(uss); } SetupStyles(); SubscribeToThemeChanges(); // UXML에서 value가 설정된 후 title 갱신 RegisterCallback(OnAttachToPanel); } private void OnAttachToPanel(AttachToPanelEvent evt) { UnregisterCallback(OnAttachToPanel); UpdateValueLabel(); } public UTKProgressBar(string text, float value = 0f, float maxValue = 100f) : this() { title = text; highValue = maxValue; SetValue(value); } #endregion #region Setup private void SetupStyles() { AddToClassList("utk-progress"); UpdateVariant(); UpdateValueLabel(); } private void SubscribeToThemeChanges() { UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged; RegisterCallback(_ => { UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged; }); } private void OnThemeChanged(UTKTheme theme) { UTKThemeManager.Instance.ApplyThemeToElement(this); } #endregion #region Methods /// /// 값 설정 /// public void SetValue(float newValue) { value = Mathf.Clamp(newValue, lowValue, highValue); UpdateValueLabel(); } private void UpdateValueLabel() { if (_isIndeterminate || !_showValue) { title = _baseTitle; return; } if (_showPercentage) { float range = highValue - lowValue; float percent = range > 0 ? (value - lowValue) / range * 100 : 0; title = $"{percent:F0}%"; } else { title = $"{value:F0}/{highValue:F0}"; } } private void UpdateVariant() { RemoveFromClassList("utk-progress--default"); RemoveFromClassList("utk-progress--success"); RemoveFromClassList("utk-progress--warning"); RemoveFromClassList("utk-progress--error"); var variantClass = _variant switch { ProgressBarVariant.Success => "utk-progress--success", ProgressBarVariant.Warning => "utk-progress--warning", ProgressBarVariant.Error => "utk-progress--error", _ => "utk-progress--default" }; AddToClassList(variantClass); } #endregion #region IDisposable public void Dispose() { if (_disposed) return; _disposed = true; UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged; } #endregion } }