ui 바인딩 로직 수정
This commit is contained in:
parent
acb4a8a170
commit
3e93ad0b39
@ -53,10 +53,8 @@ MonoBehaviour:
|
|||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
_viewImage: {fileID: 5168542205125039251}
|
_viewImage: {fileID: 5168542205125039251}
|
||||||
_nameLabel: {fileID: 3228882035781370616}
|
_nameLocalizeStringEvent: {fileID: 8756294767538431005}
|
||||||
_labelLocalizer: {fileID: 8756294767538431005}
|
_descriptionLocalizeStringEvent: {fileID: 7808277355010082239}
|
||||||
_descriptionLabel: {fileID: 4113829672213848922}
|
|
||||||
_descriptionLocalizer: {fileID: 7808277355010082239}
|
|
||||||
_cookWarePanel: {fileID: 4302691437602401827}
|
_cookWarePanel: {fileID: 4302691437602401827}
|
||||||
_cookwareImage: {fileID: 3915032697630876799}
|
_cookwareImage: {fileID: 3915032697630876799}
|
||||||
_tasteHashTagPanel: {fileID: 5646014643746221419}
|
_tasteHashTagPanel: {fileID: 5646014643746221419}
|
||||||
@ -9938,17 +9936,6 @@ RectTransform:
|
|||||||
m_CorrespondingSourceObject: {fileID: 9048682655274794071, guid: c7ae409f7430c9a408d36ccf54ef4164, type: 3}
|
m_CorrespondingSourceObject: {fileID: 9048682655274794071, guid: c7ae409f7430c9a408d36ccf54ef4164, type: 3}
|
||||||
m_PrefabInstance: {fileID: 7850329751525360396}
|
m_PrefabInstance: {fileID: 7850329751525360396}
|
||||||
m_PrefabAsset: {fileID: 0}
|
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
|
--- !u!114 &7808277355010082239 stripped
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
m_CorrespondingSourceObject: {fileID: 48813585706763955, guid: c7ae409f7430c9a408d36ccf54ef4164, type: 3}
|
m_CorrespondingSourceObject: {fileID: 48813585706763955, guid: c7ae409f7430c9a408d36ccf54ef4164, type: 3}
|
||||||
@ -11320,17 +11307,6 @@ RectTransform:
|
|||||||
m_CorrespondingSourceObject: {fileID: 9048682655274794071, guid: c7ae409f7430c9a408d36ccf54ef4164, type: 3}
|
m_CorrespondingSourceObject: {fileID: 9048682655274794071, guid: c7ae409f7430c9a408d36ccf54ef4164, type: 3}
|
||||||
m_PrefabInstance: {fileID: 8730773236163191470}
|
m_PrefabInstance: {fileID: 8730773236163191470}
|
||||||
m_PrefabAsset: {fileID: 0}
|
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
|
--- !u!114 &8756294767538431005 stripped
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
m_CorrespondingSourceObject: {fileID: 48813585706763955, guid: c7ae409f7430c9a408d36ccf54ef4164, type: 3}
|
m_CorrespondingSourceObject: {fileID: 48813585706763955, guid: c7ae409f7430c9a408d36ccf54ef4164, type: 3}
|
||||||
|
@ -1,17 +1,60 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using TMPro;
|
using TMPro;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.Localization;
|
||||||
using UnityEngine.Localization.Components;
|
using UnityEngine.Localization.Components;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
|
|
||||||
namespace DDD
|
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>
|
public class ItemDetailView : MonoBehaviour, IUiView<RestaurantManagementViewModel>, IEventHandler<ItemSlotSelectedEvent>
|
||||||
{
|
{
|
||||||
[SerializeField] private Image _viewImage;
|
[SerializeField] private Image _viewImage;
|
||||||
[SerializeField] private TextMeshProUGUI _nameLabel;
|
[SerializeField] private LocalizeStringEvent _nameLocalizeStringEvent;
|
||||||
[SerializeField] private LocalizeStringEvent _labelLocalizer;
|
[SerializeField] private LocalizeStringEvent _descriptionLocalizeStringEvent;
|
||||||
[SerializeField] private TextMeshProUGUI _descriptionLabel;
|
|
||||||
[SerializeField] private LocalizeStringEvent _descriptionLocalizer;
|
|
||||||
[SerializeField] private Transform _cookWarePanel;
|
[SerializeField] private Transform _cookWarePanel;
|
||||||
[SerializeField] private Image _cookwareImage;
|
[SerializeField] private Image _cookwareImage;
|
||||||
[SerializeField] private Transform _tasteHashTagPanel;
|
[SerializeField] private Transform _tasteHashTagPanel;
|
||||||
@ -21,51 +64,53 @@ public class ItemDetailView : MonoBehaviour, IUiView<RestaurantManagementViewMod
|
|||||||
|
|
||||||
private RestaurantManagementViewModel _viewModel;
|
private RestaurantManagementViewModel _viewModel;
|
||||||
|
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
EventBus.Unregister<ItemSlotSelectedEvent>(this);
|
||||||
|
}
|
||||||
|
|
||||||
public void Initialize(RestaurantManagementViewModel viewModel)
|
public void Initialize(RestaurantManagementViewModel viewModel)
|
||||||
{
|
{
|
||||||
_viewModel = viewModel;
|
_viewModel = viewModel;
|
||||||
|
|
||||||
_nameLabel.text = string.Empty;
|
|
||||||
_descriptionLabel.text = string.Empty;
|
|
||||||
_cookwareImage.sprite = null;
|
_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()
|
public void OnOpenedEvents()
|
||||||
{
|
{
|
||||||
UpdateView();
|
UpdateView();
|
||||||
|
|
||||||
_viewModel.OnCategoryChanged += UpdateCategory;
|
_viewModel.OnCategoryChanged += HandleCategoryChanged;
|
||||||
|
|
||||||
EventBus.Register<ItemSlotSelectedEvent>(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnClosedEvents()
|
public void OnClosedEvents()
|
||||||
{
|
{
|
||||||
if (_viewModel)
|
if (_viewModel)
|
||||||
{
|
{
|
||||||
_viewModel.OnCategoryChanged -= UpdateCategory;
|
_viewModel.OnCategoryChanged -= HandleCategoryChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
EventBus.Unregister<ItemSlotSelectedEvent>(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateView()
|
public void UpdateView()
|
||||||
{
|
{
|
||||||
UpdateCategory(_viewModel.CurrentCategory);
|
|
||||||
|
|
||||||
if (_viewModel.SelectedItem == null)
|
if (_viewModel.SelectedItem == null)
|
||||||
{
|
{
|
||||||
_labelLocalizer.StringReference = null;
|
|
||||||
_descriptionLocalizer.StringReference = null;
|
|
||||||
_cookwareImage.sprite = null;
|
|
||||||
ClearHashTags();
|
ClearHashTags();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_labelLocalizer.StringReference = _viewModel.GetItemName();
|
|
||||||
_descriptionLocalizer.StringReference = _viewModel.GetItemDescription();
|
|
||||||
_cookwareImage.sprite = _viewModel.GetCookwareSprite();
|
|
||||||
|
|
||||||
UpdateTasteHashTags();
|
UpdateTasteHashTags();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,15 +146,8 @@ private void UpdateTasteHashTags()
|
|||||||
LayoutRebuilder.ForceRebuildLayoutImmediate(_tasteHashTagContent2);
|
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();
|
Canvas.ForceUpdateCanvases();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,6 +123,8 @@ protected override GameObject GetInitialSelected()
|
|||||||
protected override void SetupBindings()
|
protected override void SetupBindings()
|
||||||
{
|
{
|
||||||
BindingHelper.BindImageFilled(_bindingContext, _completeBatchFilledImage, nameof(RestaurantManagementViewModel.NormalizedHoldProgress));
|
BindingHelper.BindImageFilled(_bindingContext, _completeBatchFilledImage, nameof(RestaurantManagementViewModel.NormalizedHoldProgress));
|
||||||
|
|
||||||
|
_itemDetailView.SetupBindings(_bindingContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeViews()
|
private void InitializeViews()
|
||||||
|
@ -173,7 +173,10 @@ public void SetCategory(InventoryCategoryType category)
|
|||||||
{
|
{
|
||||||
if (CurrentCategory == category) return;
|
if (CurrentCategory == category) return;
|
||||||
|
|
||||||
|
BeginUpdate();
|
||||||
CurrentCategory = category;
|
CurrentCategory = category;
|
||||||
|
RecomputeItemDetail();
|
||||||
|
EndUpdate();
|
||||||
OnCategoryChanged?.Invoke(category);
|
OnCategoryChanged?.Invoke(category);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -365,56 +368,68 @@ public void OnInventoryChanged()
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region ItemDetailView
|
#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 CookwareDetailPanel = "CookwareDetailPanel";
|
||||||
private const string IngredientDetailPanel = "IngredientDetailPanel";
|
private const string IngredientDetailPanel = "IngredientDetailPanel";
|
||||||
private const string RecipeDetailPanel = "RecipeDetailPanel";
|
private const string RecipeDetailPanel = "RecipeDetailPanel";
|
||||||
|
|
||||||
public ItemViewModel SelectedItem { get; private set; }
|
|
||||||
|
|
||||||
public void SetSelectedItem(ItemViewModel item)
|
public void SetSelectedItem(ItemViewModel item)
|
||||||
{
|
{
|
||||||
if (SelectedItem == item) return;
|
if (SelectedItem == item) return;
|
||||||
|
|
||||||
|
BeginUpdate();
|
||||||
SelectedItem = item;
|
SelectedItem = item;
|
||||||
|
RecomputeItemDetail();
|
||||||
|
EndUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TasteHashTagSlotUi CreateHashTag(RectTransform parent)
|
public TasteHashTagSlotUi CreateHashTag(RectTransform parent)
|
||||||
{
|
{
|
||||||
return Instantiate(GetRestaurantManagementData().TasteHashTagSlotUiPrefab, parent, false);
|
return Instantiate(GetRestaurantManagementData().TasteHashTagSlotUiPrefab, parent, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetViewItemKey()
|
private void RecomputeItemDetail()
|
||||||
{
|
{
|
||||||
return SelectedItem.ItemType == ItemType.Recipe ? SelectedItem.GetRecipeResultKey : SelectedItem.Id;
|
var background = CurrentCategory switch
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
{
|
{
|
||||||
InventoryCategoryType.Food or InventoryCategoryType.Drink
|
InventoryCategoryType.Food or InventoryCategoryType.Drink => DataManager.Instance.GetSprite(RecipeDetailPanel),
|
||||||
=> DataManager.Instance.GetSprite(RecipeDetailPanel),
|
InventoryCategoryType.Ingredient => DataManager.Instance.GetSprite(IngredientDetailPanel),
|
||||||
InventoryCategoryType.Ingredient
|
InventoryCategoryType.Cookware or InventoryCategoryType.Special => DataManager.Instance.GetSprite(CookwareDetailPanel),
|
||||||
=> DataManager.Instance.GetSprite(IngredientDetailPanel),
|
_ => null
|
||||||
InventoryCategoryType.Cookware or InventoryCategoryType.Special
|
};
|
||||||
=> DataManager.Instance.GetSprite(CookwareDetailPanel),
|
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
|
_ => 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;
|
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
|
#endregion
|
||||||
|
|
||||||
#region TodayMenuView
|
#region TodayMenuView
|
||||||
|
@ -8,9 +8,6 @@ public class TodayRestaurantStateView : MonoBehaviour, IUiView<RestaurantManagem
|
|||||||
[SerializeField] private Transform _todayWorkerContent;
|
[SerializeField] private Transform _todayWorkerContent;
|
||||||
[SerializeField] private Transform _todayCookwareContent;
|
[SerializeField] private Transform _todayCookwareContent;
|
||||||
|
|
||||||
private List<ItemSlotUi> _workerSlots;
|
|
||||||
private List<ItemSlotUi> _cookwareSlots;
|
|
||||||
|
|
||||||
private RestaurantManagementViewModel _viewModel;
|
private RestaurantManagementViewModel _viewModel;
|
||||||
|
|
||||||
public void Initialize(RestaurantManagementViewModel viewModel)
|
public void Initialize(RestaurantManagementViewModel viewModel)
|
||||||
|
@ -9,12 +9,6 @@ public abstract class SimpleViewModel : MonoBehaviour, INotifyPropertyChanged
|
|||||||
{
|
{
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
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 Initialize() { }
|
||||||
public virtual void Cleanup() { }
|
public virtual void Cleanup() { }
|
||||||
|
|
||||||
@ -24,6 +18,15 @@ public virtual void Cleanup() { }
|
|||||||
/// <param name="propertyName">변경된 속성 이름 (자동으로 설정됨)</param>
|
/// <param name="propertyName">변경된 속성 이름 (자동으로 설정됨)</param>
|
||||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
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));
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,35 +46,38 @@ protected bool SetField<T>(ref T field, T value, [CallerMemberName] string prope
|
|||||||
OnPropertyChanged(propertyName);
|
OnPropertyChanged(propertyName);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 배치 업데이트를 위한 플래그
|
|
||||||
/// </summary>
|
|
||||||
private bool _isUpdating;
|
|
||||||
|
|
||||||
/// <summary>
|
private int _updateDepth;
|
||||||
/// 배치 업데이트 중 보류된 알림들
|
|
||||||
/// </summary>
|
|
||||||
private readonly HashSet<string> _pendingNotifications = new();
|
private readonly HashSet<string> _pendingNotifications = new();
|
||||||
|
|
||||||
|
protected void BeginUpdate()
|
||||||
|
{
|
||||||
|
_updateDepth++;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 배치 업데이트 시작 - 여러 속성 변경을 한 번에 처리
|
|
||||||
/// </summary>
|
|
||||||
protected void BeginUpdate() => _isUpdating = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 배치 업데이트 종료 - 보류된 모든 알림을 처리
|
|
||||||
/// </summary>
|
|
||||||
protected void EndUpdate()
|
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)
|
if (_pendingNotifications.Count > 0)
|
||||||
{
|
{
|
||||||
foreach (var prop in _pendingNotifications)
|
// 복사 후 클리어(핸들러 내부에서 BeginUpdate가 호출되어도 안전)
|
||||||
{
|
var toNotify = new List<string>(_pendingNotifications);
|
||||||
OnPropertyChanged(prop);
|
|
||||||
}
|
|
||||||
_pendingNotifications.Clear();
|
_pendingNotifications.Clear();
|
||||||
|
|
||||||
|
foreach (var prop in toNotify)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Linq.Expressions;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using TMPro;
|
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.UI;
|
|
||||||
|
|
||||||
namespace DDD
|
namespace DDD
|
||||||
{
|
{
|
||||||
@ -16,6 +14,12 @@ public class BindingContext
|
|||||||
private readonly Dictionary<string, List<IBindingTarget>> _bindings = new();
|
private readonly Dictionary<string, List<IBindingTarget>> _bindings = new();
|
||||||
private readonly Dictionary<string, IValueConverter> _converters = new();
|
private readonly Dictionary<string, IValueConverter> _converters = new();
|
||||||
private INotifyPropertyChanged _dataContext;
|
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>
|
/// <summary>
|
||||||
/// 데이터 컨텍스트 (ViewModel) 설정
|
/// 데이터 컨텍스트 (ViewModel) 설정
|
||||||
@ -61,6 +65,14 @@ public void Bind(string propertyPath, IBindingTarget target, IValueConverter con
|
|||||||
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
|
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
UpdateBinding(e.PropertyName);
|
UpdateBinding(e.PropertyName);
|
||||||
|
var prefix = e.PropertyName + ".";
|
||||||
|
foreach (var key in _bindings.Keys)
|
||||||
|
{
|
||||||
|
if (key.StartsWith(prefix, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
UpdateBinding(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq.Expressions;
|
||||||
using TMPro;
|
using TMPro;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.Localization.Components;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
|
|
||||||
namespace DDD
|
namespace DDD
|
||||||
@ -11,6 +14,12 @@ public static void BindText(BindingContext context, TextMeshProUGUI text, string
|
|||||||
var target = new TextBindingTarget(text, propertyPath);
|
var target = new TextBindingTarget(text, propertyPath);
|
||||||
context?.Bind(propertyPath, target, converter);
|
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)
|
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);
|
var target = new SliderBindingTarget(slider, propertyPath);
|
||||||
context?.Bind(propertyPath, target, converter);
|
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 TMPro;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.Localization;
|
||||||
|
using UnityEngine.Localization.Components;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
|
|
||||||
namespace DDD
|
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
|
public class ImageBindingTarget : IBindingTarget
|
||||||
{
|
{
|
||||||
private readonly Image _image;
|
private readonly Image _image;
|
||||||
@ -51,10 +80,14 @@ public ImageBindingTarget(Image image, string propertyPath)
|
|||||||
|
|
||||||
public void UpdateValue(object value)
|
public void UpdateValue(object value)
|
||||||
{
|
{
|
||||||
if (_image != null && value is Sprite sprite)
|
if (value is Sprite sprite)
|
||||||
{
|
{
|
||||||
_image.sprite = sprite;
|
_image.sprite = sprite;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_image.sprite = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user