ProjectDDD/Assets/_DDD/_Scripts/GameUi/New/Utils/BindingContext.cs
2025-08-20 15:22:08 +09:00

259 lines
7.7 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using UnityEngine;
using UnityEngine.UI;
namespace DDD.MVVM
{
/// <summary>
/// 바인딩 타겟 인터페이스
/// UI 요소와 ViewModel 속성을 연결하는 역할
/// </summary>
public interface IBindingTarget
{
/// <summary>
/// 바인딩된 속성의 경로
/// </summary>
string PropertyPath { get; }
/// <summary>
/// UI 요소의 값을 업데이트
/// </summary>
/// <param name="value">새로운 값</param>
void UpdateValue(object value);
}
/// <summary>
/// Text 컴포넌트에 대한 바인딩 타겟
/// </summary>
public class TextBindingTarget : IBindingTarget
{
private readonly Text _text;
public string PropertyPath { get; }
public TextBindingTarget(Text text, string propertyPath)
{
_text = text;
PropertyPath = propertyPath;
}
public void UpdateValue(object value)
{
if (_text != null)
_text.text = value?.ToString() ?? string.Empty;
}
}
/// <summary>
/// Image 컴포넌트에 대한 바인딩 타겟
/// </summary>
public class ImageBindingTarget : IBindingTarget
{
private readonly Image _image;
public string PropertyPath { get; }
public ImageBindingTarget(Image image, string propertyPath)
{
_image = image;
PropertyPath = propertyPath;
}
public void UpdateValue(object value)
{
if (_image != null && value is Sprite sprite)
_image.sprite = sprite;
}
}
/// <summary>
/// GameObject의 활성화 상태에 대한 바인딩 타겟
/// </summary>
public class ActiveBindingTarget : IBindingTarget
{
private readonly GameObject _gameObject;
public string PropertyPath { get; }
public ActiveBindingTarget(GameObject gameObject, string propertyPath)
{
_gameObject = gameObject;
PropertyPath = propertyPath;
}
public void UpdateValue(object value)
{
if (_gameObject != null)
_gameObject.SetActive(value is bool active && active);
}
}
/// <summary>
/// Slider 컴포넌트에 대한 바인딩 타겟
/// </summary>
public class SliderBindingTarget : IBindingTarget
{
private readonly Slider _slider;
public string PropertyPath { get; }
public SliderBindingTarget(Slider slider, string propertyPath)
{
_slider = slider;
PropertyPath = propertyPath;
}
public void UpdateValue(object value)
{
if (_slider != null && value is float floatValue)
_slider.value = floatValue;
}
}
/// <summary>
/// 바인딩 컨텍스트 - ViewModel과 View 간의 데이터 바인딩을 관리
/// </summary>
public class BindingContext
{
private readonly Dictionary<string, List<IBindingTarget>> _bindings = new();
private readonly Dictionary<string, IValueConverter> _converters = new();
private INotifyPropertyChanged _dataContext;
/// <summary>
/// 데이터 컨텍스트 (ViewModel) 설정
/// </summary>
/// <param name="dataContext">바인딩할 ViewModel</param>
public void SetDataContext(INotifyPropertyChanged dataContext)
{
if (_dataContext != null)
_dataContext.PropertyChanged -= OnPropertyChanged;
_dataContext = dataContext;
if (_dataContext != null)
{
_dataContext.PropertyChanged += OnPropertyChanged;
RefreshAllBindings();
}
}
/// <summary>
/// 속성 바인딩 추가
/// </summary>
/// <param name="propertyPath">바인딩할 속성 경로</param>
/// <param name="target">바인딩 타겟</param>
/// <param name="converter">값 변환기 (선택사항)</param>
public void Bind(string propertyPath, IBindingTarget target, IValueConverter converter = null)
{
if (!_bindings.ContainsKey(propertyPath))
_bindings[propertyPath] = new List<IBindingTarget>();
_bindings[propertyPath].Add(target);
if (converter != null)
_converters[propertyPath] = converter;
// 즉시 초기값 설정
UpdateBinding(propertyPath);
}
/// <summary>
/// 속성 변경 이벤트 핸들러
/// </summary>
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
UpdateBinding(e.PropertyName);
}
/// <summary>
/// 특정 속성의 바인딩 업데이트
/// </summary>
/// <param name="propertyPath">업데이트할 속성 경로</param>
private void UpdateBinding(string propertyPath)
{
if (!_bindings.ContainsKey(propertyPath)) return;
var value = GetPropertyValue(propertyPath);
// 컨버터 적용
if (_converters.TryGetValue(propertyPath, out var converter))
value = converter.Convert(value);
foreach (var target in _bindings[propertyPath])
{
target.UpdateValue(value);
}
}
/// <summary>
/// 속성 값 가져오기 (리플렉션 사용)
/// </summary>
/// <param name="propertyPath">속성 경로</param>
/// <returns>속성 값</returns>
private object GetPropertyValue(string propertyPath)
{
if (_dataContext == null) return null;
// 중첩 속성 지원 (예: "ItemData.Name")
var properties = propertyPath.Split('.');
object current = _dataContext;
foreach (var prop in properties)
{
if (current == null) return null;
var property = current.GetType().GetProperty(prop);
current = property?.GetValue(current);
}
return current;
}
/// <summary>
/// 모든 바인딩 새로고침
/// </summary>
private void RefreshAllBindings()
{
foreach (var propertyPath in _bindings.Keys)
{
UpdateBinding(propertyPath);
}
}
/// <summary>
/// 리소스 정리
/// </summary>
public void Dispose()
{
if (_dataContext != null)
_dataContext.PropertyChanged -= OnPropertyChanged;
_bindings.Clear();
_converters.Clear();
}
}
/// <summary>
/// 속성 경로 캐시 - 성능 최적화를 위한 리플렉션 결과 캐싱
/// </summary>
public static class PropertyPathCache
{
private static readonly Dictionary<Type, Dictionary<string, PropertyInfo>> _cache = new();
/// <summary>
/// 캐시된 PropertyInfo 가져오기
/// </summary>
/// <param name="type">타입</param>
/// <param name="propertyName">속성 이름</param>
/// <returns>PropertyInfo</returns>
public static PropertyInfo GetProperty(Type type, string propertyName)
{
if (!_cache.ContainsKey(type))
_cache[type] = new Dictionary<string, PropertyInfo>();
if (!_cache[type].ContainsKey(propertyName))
_cache[type][propertyName] = type.GetProperty(propertyName);
return _cache[type][propertyName];
}
}
}