ui 바인딩 로직 수정
This commit is contained in:
parent
acb4a8a170
commit
3e93ad0b39
@ -53,10 +53,8 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
_viewImage: {fileID: 5168542205125039251}
|
||||
_nameLabel: {fileID: 3228882035781370616}
|
||||
_labelLocalizer: {fileID: 8756294767538431005}
|
||||
_descriptionLabel: {fileID: 4113829672213848922}
|
||||
_descriptionLocalizer: {fileID: 7808277355010082239}
|
||||
_nameLocalizeStringEvent: {fileID: 8756294767538431005}
|
||||
_descriptionLocalizeStringEvent: {fileID: 7808277355010082239}
|
||||
_cookWarePanel: {fileID: 4302691437602401827}
|
||||
_cookwareImage: {fileID: 3915032697630876799}
|
||||
_tasteHashTagPanel: {fileID: 5646014643746221419}
|
||||
@ -9938,17 +9936,6 @@ RectTransform:
|
||||
m_CorrespondingSourceObject: {fileID: 9048682655274794071, guid: c7ae409f7430c9a408d36ccf54ef4164, type: 3}
|
||||
m_PrefabInstance: {fileID: 7850329751525360396}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!114 &4113829672213848922 stripped
|
||||
MonoBehaviour:
|
||||
m_CorrespondingSourceObject: {fileID: 6189840460486090838, guid: c7ae409f7430c9a408d36ccf54ef4164, type: 3}
|
||||
m_PrefabInstance: {fileID: 7850329751525360396}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
--- !u!114 &7808277355010082239 stripped
|
||||
MonoBehaviour:
|
||||
m_CorrespondingSourceObject: {fileID: 48813585706763955, guid: c7ae409f7430c9a408d36ccf54ef4164, type: 3}
|
||||
@ -11320,17 +11307,6 @@ RectTransform:
|
||||
m_CorrespondingSourceObject: {fileID: 9048682655274794071, guid: c7ae409f7430c9a408d36ccf54ef4164, type: 3}
|
||||
m_PrefabInstance: {fileID: 8730773236163191470}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!114 &3228882035781370616 stripped
|
||||
MonoBehaviour:
|
||||
m_CorrespondingSourceObject: {fileID: 6189840460486090838, guid: c7ae409f7430c9a408d36ccf54ef4164, type: 3}
|
||||
m_PrefabInstance: {fileID: 8730773236163191470}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
--- !u!114 &8756294767538431005 stripped
|
||||
MonoBehaviour:
|
||||
m_CorrespondingSourceObject: {fileID: 48813585706763955, guid: c7ae409f7430c9a408d36ccf54ef4164, type: 3}
|
||||
|
@ -1,17 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Localization;
|
||||
using UnityEngine.Localization.Components;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace DDD
|
||||
{
|
||||
public sealed class ItemDetailSnapshot
|
||||
{
|
||||
public Sprite BackgroundSprite { get; }
|
||||
public bool ShowTastePanel { get; }
|
||||
public bool ShowCookwarePanel { get; }
|
||||
public Sprite CookwareSprite { get; }
|
||||
public LocalizedString Name { get; }
|
||||
public LocalizedString Description { get; }
|
||||
public IReadOnlyList<TasteData> Tastes { get; }
|
||||
public Material TasteMaterial { get; }
|
||||
|
||||
public ItemDetailSnapshot(
|
||||
Sprite backgroundSprite,
|
||||
bool showTastePanel,
|
||||
bool showCookwarePanel,
|
||||
Sprite cookwareSprite,
|
||||
LocalizedString name,
|
||||
LocalizedString description,
|
||||
IReadOnlyList<TasteData> tastes,
|
||||
Material tasteMaterial)
|
||||
{
|
||||
BackgroundSprite = backgroundSprite;
|
||||
ShowTastePanel = showTastePanel;
|
||||
ShowCookwarePanel = showCookwarePanel;
|
||||
CookwareSprite = cookwareSprite;
|
||||
Name = name;
|
||||
Description = description;
|
||||
Tastes = tastes;
|
||||
TasteMaterial = tasteMaterial;
|
||||
}
|
||||
|
||||
public static readonly ItemDetailSnapshot Empty = new(
|
||||
backgroundSprite: null,
|
||||
showTastePanel: false,
|
||||
showCookwarePanel: false,
|
||||
cookwareSprite: null,
|
||||
name: null,
|
||||
description: null,
|
||||
tastes: System.Array.Empty<TasteData>(),
|
||||
tasteMaterial: null);
|
||||
}
|
||||
|
||||
public class ItemDetailView : MonoBehaviour, IUiView<RestaurantManagementViewModel>, IEventHandler<ItemSlotSelectedEvent>
|
||||
{
|
||||
[SerializeField] private Image _viewImage;
|
||||
[SerializeField] private TextMeshProUGUI _nameLabel;
|
||||
[SerializeField] private LocalizeStringEvent _labelLocalizer;
|
||||
[SerializeField] private TextMeshProUGUI _descriptionLabel;
|
||||
[SerializeField] private LocalizeStringEvent _descriptionLocalizer;
|
||||
[SerializeField] private LocalizeStringEvent _nameLocalizeStringEvent;
|
||||
[SerializeField] private LocalizeStringEvent _descriptionLocalizeStringEvent;
|
||||
[SerializeField] private Transform _cookWarePanel;
|
||||
[SerializeField] private Image _cookwareImage;
|
||||
[SerializeField] private Transform _tasteHashTagPanel;
|
||||
@ -21,51 +64,53 @@ public class ItemDetailView : MonoBehaviour, IUiView<RestaurantManagementViewMod
|
||||
|
||||
private RestaurantManagementViewModel _viewModel;
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
EventBus.Unregister<ItemSlotSelectedEvent>(this);
|
||||
}
|
||||
|
||||
public void Initialize(RestaurantManagementViewModel viewModel)
|
||||
{
|
||||
_viewModel = viewModel;
|
||||
|
||||
_nameLabel.text = string.Empty;
|
||||
_descriptionLabel.text = string.Empty;
|
||||
|
||||
_cookwareImage.sprite = null;
|
||||
|
||||
EventBus.Register<ItemSlotSelectedEvent>(this);
|
||||
}
|
||||
|
||||
public void SetupBindings(BindingContext bindingContext)
|
||||
{
|
||||
BindingHelper.BindImage<RestaurantManagementViewModel>(bindingContext, _viewImage, viewModel => viewModel.ItemDetail.BackgroundSprite);
|
||||
BindingHelper.BindImage<RestaurantManagementViewModel>(bindingContext, _cookwareImage, viewModel => viewModel.ItemDetail.CookwareSprite);
|
||||
BindingHelper.BindActive<RestaurantManagementViewModel>(bindingContext, _tasteHashTagPanel.gameObject, viewModel => viewModel.ItemDetail.ShowTastePanel);
|
||||
BindingHelper.BindActive<RestaurantManagementViewModel>(bindingContext, _cookWarePanel.gameObject, viewModel => viewModel.ItemDetail.ShowCookwarePanel);
|
||||
BindingHelper.BindLocalizedStringEvent<RestaurantManagementViewModel>(bindingContext, _nameLocalizeStringEvent, viewModel => viewModel.ItemDetail.Name);
|
||||
BindingHelper.BindLocalizedStringEvent<RestaurantManagementViewModel>(bindingContext, _descriptionLocalizeStringEvent, viewModel => viewModel.ItemDetail.Description);
|
||||
}
|
||||
|
||||
public void OnOpenedEvents()
|
||||
{
|
||||
UpdateView();
|
||||
|
||||
_viewModel.OnCategoryChanged += UpdateCategory;
|
||||
|
||||
EventBus.Register<ItemSlotSelectedEvent>(this);
|
||||
_viewModel.OnCategoryChanged += HandleCategoryChanged;
|
||||
}
|
||||
|
||||
public void OnClosedEvents()
|
||||
{
|
||||
if (_viewModel)
|
||||
{
|
||||
_viewModel.OnCategoryChanged -= UpdateCategory;
|
||||
_viewModel.OnCategoryChanged -= HandleCategoryChanged;
|
||||
}
|
||||
|
||||
EventBus.Unregister<ItemSlotSelectedEvent>(this);
|
||||
}
|
||||
|
||||
public void UpdateView()
|
||||
{
|
||||
UpdateCategory(_viewModel.CurrentCategory);
|
||||
|
||||
if (_viewModel.SelectedItem == null)
|
||||
{
|
||||
_labelLocalizer.StringReference = null;
|
||||
_descriptionLocalizer.StringReference = null;
|
||||
_cookwareImage.sprite = null;
|
||||
ClearHashTags();
|
||||
return;
|
||||
}
|
||||
|
||||
_labelLocalizer.StringReference = _viewModel.GetItemName();
|
||||
_descriptionLocalizer.StringReference = _viewModel.GetItemDescription();
|
||||
_cookwareImage.sprite = _viewModel.GetCookwareSprite();
|
||||
|
||||
UpdateTasteHashTags();
|
||||
}
|
||||
|
||||
@ -101,15 +146,8 @@ private void UpdateTasteHashTags()
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(_tasteHashTagContent2);
|
||||
}
|
||||
|
||||
public void UpdateCategory(InventoryCategoryType category)
|
||||
public void HandleCategoryChanged(InventoryCategoryType category)
|
||||
{
|
||||
_viewImage.sprite = _viewModel.GetDetailBackground(category);
|
||||
|
||||
bool showTaste = _viewModel.ShouldShowTaste(category);
|
||||
bool showCookware = _viewModel.ShouldShowCookware(category);
|
||||
_tasteHashTagPanel.gameObject.SetActive(showTaste);
|
||||
_cookWarePanel.gameObject.SetActive(showCookware);
|
||||
|
||||
Canvas.ForceUpdateCanvases();
|
||||
}
|
||||
|
||||
|
@ -123,6 +123,8 @@ protected override GameObject GetInitialSelected()
|
||||
protected override void SetupBindings()
|
||||
{
|
||||
BindingHelper.BindImageFilled(_bindingContext, _completeBatchFilledImage, nameof(RestaurantManagementViewModel.NormalizedHoldProgress));
|
||||
|
||||
_itemDetailView.SetupBindings(_bindingContext);
|
||||
}
|
||||
|
||||
private void InitializeViews()
|
||||
|
@ -173,7 +173,10 @@ public void SetCategory(InventoryCategoryType category)
|
||||
{
|
||||
if (CurrentCategory == category) return;
|
||||
|
||||
BeginUpdate();
|
||||
CurrentCategory = category;
|
||||
RecomputeItemDetail();
|
||||
EndUpdate();
|
||||
OnCategoryChanged?.Invoke(category);
|
||||
}
|
||||
|
||||
@ -365,56 +368,68 @@ public void OnInventoryChanged()
|
||||
#endregion
|
||||
|
||||
#region ItemDetailView
|
||||
|
||||
private ItemDetailSnapshot _itemDetail = ItemDetailSnapshot.Empty;
|
||||
public ItemDetailSnapshot ItemDetail
|
||||
{
|
||||
get => _itemDetail;
|
||||
private set => SetField(ref _itemDetail, value);
|
||||
}
|
||||
|
||||
public ItemViewModel SelectedItem { get; private set; }
|
||||
|
||||
private const string CookwareDetailPanel = "CookwareDetailPanel";
|
||||
private const string IngredientDetailPanel = "IngredientDetailPanel";
|
||||
private const string RecipeDetailPanel = "RecipeDetailPanel";
|
||||
|
||||
public ItemViewModel SelectedItem { get; private set; }
|
||||
|
||||
public void SetSelectedItem(ItemViewModel item)
|
||||
{
|
||||
if (SelectedItem == item) return;
|
||||
|
||||
BeginUpdate();
|
||||
SelectedItem = item;
|
||||
RecomputeItemDetail();
|
||||
EndUpdate();
|
||||
}
|
||||
|
||||
public TasteHashTagSlotUi CreateHashTag(RectTransform parent)
|
||||
{
|
||||
return Instantiate(GetRestaurantManagementData().TasteHashTagSlotUiPrefab, parent, false);
|
||||
}
|
||||
|
||||
private string GetViewItemKey()
|
||||
|
||||
private void RecomputeItemDetail()
|
||||
{
|
||||
return SelectedItem.ItemType == ItemType.Recipe ? SelectedItem.GetRecipeResultKey : SelectedItem.Id;
|
||||
}
|
||||
|
||||
public LocalizedString GetItemName()
|
||||
{
|
||||
var key = GetViewItemKey();
|
||||
return LocalizationManager.Instance.GetLocalizedName(key);
|
||||
}
|
||||
|
||||
public LocalizedString GetItemDescription()
|
||||
{
|
||||
var key = GetViewItemKey();
|
||||
return LocalizationManager.Instance.GetLocalizedDescription(key);
|
||||
}
|
||||
|
||||
public Sprite GetDetailBackground(InventoryCategoryType category)
|
||||
{
|
||||
return category switch
|
||||
var background = CurrentCategory switch
|
||||
{
|
||||
InventoryCategoryType.Food or InventoryCategoryType.Drink
|
||||
=> DataManager.Instance.GetSprite(RecipeDetailPanel),
|
||||
InventoryCategoryType.Ingredient
|
||||
=> DataManager.Instance.GetSprite(IngredientDetailPanel),
|
||||
InventoryCategoryType.Cookware or InventoryCategoryType.Special
|
||||
=> DataManager.Instance.GetSprite(CookwareDetailPanel),
|
||||
InventoryCategoryType.Food or InventoryCategoryType.Drink => DataManager.Instance.GetSprite(RecipeDetailPanel),
|
||||
InventoryCategoryType.Ingredient => DataManager.Instance.GetSprite(IngredientDetailPanel),
|
||||
InventoryCategoryType.Cookware or InventoryCategoryType.Special => DataManager.Instance.GetSprite(CookwareDetailPanel),
|
||||
_ => null
|
||||
};
|
||||
bool showTaste = CurrentCategory is InventoryCategoryType.Food or InventoryCategoryType.Drink or InventoryCategoryType.Ingredient;
|
||||
bool showCookware = CurrentCategory is InventoryCategoryType.Food or InventoryCategoryType.Drink;
|
||||
var cookwareSprite = SelectedItem.GetCookwareIcon;
|
||||
string key = SelectedItem.ItemType == ItemType.Recipe ? SelectedItem.GetRecipeResultKey : SelectedItem.Id;
|
||||
var nameLocalizedString = LocalizationManager.Instance.GetLocalizedName(key);
|
||||
var descriptionLocalizedString = LocalizationManager.Instance.GetLocalizedDescription(key);
|
||||
var tastes = SelectedItem.GetTasteDatas;
|
||||
var tasteMat = SelectedItem.RecipeType switch
|
||||
{
|
||||
RecipeType.FoodRecipe => RestaurantData.Instance.ManagementData.FoodTasteMaterial,
|
||||
RecipeType.DrinkRecipe => RestaurantData.Instance.ManagementData.DrinkTasteMaterial,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
public Sprite GetCookwareSprite() => SelectedItem.GetCookwareIcon;
|
||||
ItemDetail = new ItemDetailSnapshot(
|
||||
backgroundSprite: background,
|
||||
showTastePanel: showTaste,
|
||||
showCookwarePanel: showCookware,
|
||||
cookwareSprite: cookwareSprite,
|
||||
name: nameLocalizedString,
|
||||
description: descriptionLocalizedString,
|
||||
tastes: tastes,
|
||||
tasteMaterial: tasteMat);
|
||||
}
|
||||
|
||||
public IReadOnlyList<TasteData> GetTastes() => SelectedItem?.GetTasteDatas;
|
||||
|
||||
@ -430,12 +445,6 @@ public Material GetTasteMaterial()
|
||||
};
|
||||
}
|
||||
|
||||
public bool ShouldShowTaste(InventoryCategoryType category)
|
||||
=> category is InventoryCategoryType.Food or InventoryCategoryType.Drink or InventoryCategoryType.Ingredient;
|
||||
|
||||
public bool ShouldShowCookware(InventoryCategoryType category)
|
||||
=> category is InventoryCategoryType.Food or InventoryCategoryType.Drink;
|
||||
|
||||
#endregion
|
||||
|
||||
#region TodayMenuView
|
||||
|
@ -8,9 +8,6 @@ public class TodayRestaurantStateView : MonoBehaviour, IUiView<RestaurantManagem
|
||||
[SerializeField] private Transform _todayWorkerContent;
|
||||
[SerializeField] private Transform _todayCookwareContent;
|
||||
|
||||
private List<ItemSlotUi> _workerSlots;
|
||||
private List<ItemSlotUi> _cookwareSlots;
|
||||
|
||||
private RestaurantManagementViewModel _viewModel;
|
||||
|
||||
public void Initialize(RestaurantManagementViewModel viewModel)
|
||||
|
@ -9,12 +9,6 @@ public abstract class SimpleViewModel : MonoBehaviour, INotifyPropertyChanged
|
||||
{
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
protected virtual void Awake() { }
|
||||
protected virtual void OnEnable() { }
|
||||
protected virtual void Start() { }
|
||||
protected virtual void OnDisable() { }
|
||||
protected virtual void OnDestroy() { }
|
||||
|
||||
public virtual void Initialize() { }
|
||||
public virtual void Cleanup() { }
|
||||
|
||||
@ -24,6 +18,15 @@ public virtual void Cleanup() { }
|
||||
/// <param name="propertyName">변경된 속성 이름 (자동으로 설정됨)</param>
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(propertyName)) return;
|
||||
|
||||
if (_updateDepth > 0)
|
||||
{
|
||||
// 배치 업데이트 중: 나중에 일괄 발행
|
||||
_pendingNotifications.Add(propertyName);
|
||||
return;
|
||||
}
|
||||
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
@ -43,35 +46,38 @@ protected bool SetField<T>(ref T field, T value, [CallerMemberName] string prope
|
||||
OnPropertyChanged(propertyName);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 배치 업데이트를 위한 플래그
|
||||
/// </summary>
|
||||
private bool _isUpdating;
|
||||
|
||||
/// <summary>
|
||||
/// 배치 업데이트 중 보류된 알림들
|
||||
/// </summary>
|
||||
private int _updateDepth;
|
||||
private readonly HashSet<string> _pendingNotifications = new();
|
||||
|
||||
protected void BeginUpdate()
|
||||
{
|
||||
_updateDepth++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 배치 업데이트 시작 - 여러 속성 변경을 한 번에 처리
|
||||
/// </summary>
|
||||
protected void BeginUpdate() => _isUpdating = true;
|
||||
|
||||
/// <summary>
|
||||
/// 배치 업데이트 종료 - 보류된 모든 알림을 처리
|
||||
/// </summary>
|
||||
protected void EndUpdate()
|
||||
{
|
||||
_isUpdating = false;
|
||||
if (_updateDepth == 0)
|
||||
{
|
||||
Debug.LogWarning("EndUpdate called without matching BeginUpdate.");
|
||||
return;
|
||||
}
|
||||
|
||||
_updateDepth--;
|
||||
|
||||
// 아직 상위 배치가 진행 중이면 플러시하지 않음
|
||||
if (_updateDepth > 0) return;
|
||||
|
||||
if (_pendingNotifications.Count > 0)
|
||||
{
|
||||
foreach (var prop in _pendingNotifications)
|
||||
{
|
||||
OnPropertyChanged(prop);
|
||||
}
|
||||
// 복사 후 클리어(핸들러 내부에서 BeginUpdate가 호출되어도 안전)
|
||||
var toNotify = new List<string>(_pendingNotifications);
|
||||
_pendingNotifications.Clear();
|
||||
|
||||
foreach (var prop in toNotify)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace DDD
|
||||
{
|
||||
@ -16,6 +14,12 @@ public class BindingContext
|
||||
private readonly Dictionary<string, List<IBindingTarget>> _bindings = new();
|
||||
private readonly Dictionary<string, IValueConverter> _converters = new();
|
||||
private INotifyPropertyChanged _dataContext;
|
||||
|
||||
public void Bind<TVm, TProp>(Expression<Func<TVm, TProp>> property, IBindingTarget target, IValueConverter converter = null)
|
||||
{
|
||||
var path = BindingPath.For(property);
|
||||
Bind(path, target, converter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 데이터 컨텍스트 (ViewModel) 설정
|
||||
@ -61,6 +65,14 @@ public void Bind(string propertyPath, IBindingTarget target, IValueConverter con
|
||||
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
UpdateBinding(e.PropertyName);
|
||||
var prefix = e.PropertyName + ".";
|
||||
foreach (var key in _bindings.Keys)
|
||||
{
|
||||
if (key.StartsWith(prefix, StringComparison.Ordinal))
|
||||
{
|
||||
UpdateBinding(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,5 +1,8 @@
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Localization.Components;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace DDD
|
||||
@ -11,6 +14,12 @@ public static void BindText(BindingContext context, TextMeshProUGUI text, string
|
||||
var target = new TextBindingTarget(text, propertyPath);
|
||||
context?.Bind(propertyPath, target, converter);
|
||||
}
|
||||
|
||||
public static void BindLocalizedStringEvent(BindingContext context, LocalizeStringEvent localizeEvent, string propertyPath, IValueConverter converter = null)
|
||||
{
|
||||
var target = new LocalizeStringEventBindingTarget(localizeEvent, propertyPath);
|
||||
context?.Bind(propertyPath, target, converter);
|
||||
}
|
||||
|
||||
public static void BindImage(BindingContext context, Image image, string propertyPath, IValueConverter converter = null)
|
||||
{
|
||||
@ -35,5 +44,41 @@ public static void BindSlider(BindingContext context, Slider slider, string prop
|
||||
var target = new SliderBindingTarget(slider, propertyPath);
|
||||
context?.Bind(propertyPath, target, converter);
|
||||
}
|
||||
|
||||
public static void BindText<TVm>(BindingContext ctx, TextMeshProUGUI text,
|
||||
Expression<Func<TVm, object>> prop, IValueConverter conv = null)
|
||||
=> BindText(ctx, text, BindingPath.For(prop), conv);
|
||||
|
||||
public static void BindLocalizedStringEvent<TVm>(BindingContext ctx, LocalizeStringEvent loc,
|
||||
Expression<Func<TVm, object>> prop, IValueConverter conv = null)
|
||||
=> BindLocalizedStringEvent(ctx, loc, BindingPath.For(prop), conv);
|
||||
|
||||
public static void BindImage<TVm>(BindingContext ctx, Image img,
|
||||
Expression<Func<TVm, object>> prop, IValueConverter conv = null)
|
||||
=> BindImage(ctx, img, BindingPath.For(prop), conv);
|
||||
|
||||
public static void BindImageFilled<TVm>(BindingContext ctx, Image img,
|
||||
Expression<Func<TVm, object>> prop, IValueConverter conv = null)
|
||||
=> BindImageFilled(ctx, img, BindingPath.For(prop), conv);
|
||||
|
||||
public static void BindActive<TVm>(BindingContext ctx, GameObject go,
|
||||
Expression<Func<TVm, object>> prop, IValueConverter conv = null)
|
||||
=> BindActive(ctx, go, BindingPath.For(prop), conv);
|
||||
|
||||
public static void BindSlider<TVm>(BindingContext ctx, Slider slider,
|
||||
Expression<Func<TVm, object>> prop, IValueConverter conv = null)
|
||||
=> BindSlider(ctx, slider, BindingPath.For(prop), conv);
|
||||
|
||||
public static void BindImage<TVm, TProp>(BindingContext ctx, Image img,
|
||||
Expression<Func<TVm, TProp>> prop, IValueConverter conv = null)
|
||||
=> BindImage(ctx, img, BindingPath.For(prop), conv);
|
||||
|
||||
public static void BindActive<TVm, TProp>(BindingContext ctx, GameObject go,
|
||||
Expression<Func<TVm, TProp>> prop, IValueConverter conv = null)
|
||||
=> BindActive(ctx, go, BindingPath.For(prop), conv);
|
||||
|
||||
public static void BindLocalizedStringEvent<TVm, TProp>(BindingContext ctx, LocalizeStringEvent loc,
|
||||
Expression<Func<TVm, TProp>> prop, IValueConverter conv = null)
|
||||
=> BindLocalizedStringEvent(ctx, loc, BindingPath.For(prop), conv);
|
||||
}
|
||||
}
|
56
Assets/_DDD/_Scripts/GameUi/Utils/Binding/BindingPath.cs
Normal file
56
Assets/_DDD/_Scripts/GameUi/Utils/Binding/BindingPath.cs
Normal file
@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace DDD
|
||||
{
|
||||
public static class BindingPath
|
||||
{
|
||||
public static string For<TViewModel, TProp>(Expression<Func<TViewModel, TProp>> expression)
|
||||
{
|
||||
if (expression == null) throw new ArgumentNullException(nameof(expression));
|
||||
|
||||
Expression expr = expression.Body;
|
||||
|
||||
// 값형식 → object 캐스팅 제거
|
||||
if (expr is UnaryExpression unary && unary.NodeType == ExpressionType.Convert)
|
||||
{
|
||||
expr = unary.Operand;
|
||||
}
|
||||
|
||||
var members = new Stack<string>();
|
||||
while (expr is MemberExpression me)
|
||||
{
|
||||
members.Push(me.Member.Name);
|
||||
expr = me.Expression;
|
||||
|
||||
if (expr is ParameterExpression) break; // 루트 도달
|
||||
|
||||
if (expr is UnaryExpression innerUnary && innerUnary.NodeType == ExpressionType.Convert)
|
||||
expr = innerUnary.Operand;
|
||||
}
|
||||
|
||||
return string.Join(".", members);
|
||||
}
|
||||
|
||||
// 편의용: object 리턴 시그니처도 지원
|
||||
public static string For<TViewModel>(Expression<Func<TViewModel, object>> expression)
|
||||
{
|
||||
if (expression == null) throw new ArgumentNullException(nameof(expression));
|
||||
Expression expr = expression.Body;
|
||||
if (expr is UnaryExpression unary && unary.NodeType == ExpressionType.Convert)
|
||||
expr = unary.Operand;
|
||||
|
||||
var members = new Stack<string>();
|
||||
while (expr is MemberExpression me)
|
||||
{
|
||||
members.Push(me.Member.Name);
|
||||
expr = me.Expression;
|
||||
if (expr is ParameterExpression) break;
|
||||
if (expr is UnaryExpression innerUnary && innerUnary.NodeType == ExpressionType.Convert)
|
||||
expr = innerUnary.Operand;
|
||||
}
|
||||
return string.Join(".", members);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35fee64a12cf410e8758bef3294aa1ee
|
||||
timeCreated: 1756065063
|
@ -1,5 +1,7 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Localization;
|
||||
using UnityEngine.Localization.Components;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace DDD
|
||||
@ -38,6 +40,33 @@ public void UpdateValue(object value)
|
||||
}
|
||||
}
|
||||
|
||||
public class LocalizeStringEventBindingTarget : IBindingTarget
|
||||
{
|
||||
private readonly LocalizeStringEvent _localizeEvent;
|
||||
public string PropertyPath { get; }
|
||||
|
||||
public LocalizeStringEventBindingTarget(LocalizeStringEvent localizeEvent, string propertyPath)
|
||||
{
|
||||
_localizeEvent = localizeEvent;
|
||||
PropertyPath = propertyPath;
|
||||
}
|
||||
|
||||
public void UpdateValue(object value)
|
||||
{
|
||||
if (_localizeEvent == null) return;
|
||||
|
||||
if (value is LocalizedString localized)
|
||||
{
|
||||
_localizeEvent.StringReference = localized;
|
||||
}
|
||||
else
|
||||
{
|
||||
// null 또는 다른 타입인 경우 초기화
|
||||
_localizeEvent.StringReference = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ImageBindingTarget : IBindingTarget
|
||||
{
|
||||
private readonly Image _image;
|
||||
@ -51,10 +80,14 @@ public ImageBindingTarget(Image image, string propertyPath)
|
||||
|
||||
public void UpdateValue(object value)
|
||||
{
|
||||
if (_image != null && value is Sprite sprite)
|
||||
if (value is Sprite sprite)
|
||||
{
|
||||
_image.sprite = sprite;
|
||||
}
|
||||
else
|
||||
{
|
||||
_image.sprite = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user