Passion & UI
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b651e1fc3b3065b40ae2c20a253de26b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,58 @@
|
||||
using System.Collections.Generic;
|
||||
using Cielonos.MainGame.Inventory;
|
||||
using SLSUtilities.General;
|
||||
using SLSUtilities.UI;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Cielonos.MainGame.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 单条物品描述条目 UI 组件。
|
||||
/// 渲染管线:Localize → DisplayTextResolver.Resolve → InputGlyphParser.Parse → TMP_Text。
|
||||
/// </summary>
|
||||
public class ItemDescriptionEntry : MonoBehaviour
|
||||
{
|
||||
private const string LocalizationTable = "Items";
|
||||
|
||||
[Tooltip("描述文本,显示 descriptionKey 的本地化内容(含动态数值替换和按键图标解析)。")]
|
||||
public TMP_Text descriptionText;
|
||||
|
||||
/// <summary>
|
||||
/// 使用 <see cref="ItemDescription"/> 的数据填充描述条目。
|
||||
/// 完整渲染管线:本地化 → {key} 占位符替换 → [Token] 按键图标解析。
|
||||
/// </summary>
|
||||
/// <param name="description">描述数据。</param>
|
||||
/// <param name="descriptionArgs">可选的动态值字典,用于替换本地化文本中的 {key} 占位符。</param>
|
||||
public void SetDescription(ItemDescription description, Dictionary<string, string> descriptionArgs = null)
|
||||
{
|
||||
if (description == null)
|
||||
{
|
||||
Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (descriptionText == null) return;
|
||||
|
||||
if (string.IsNullOrEmpty(description.descriptionKey))
|
||||
{
|
||||
descriptionText.text = string.Empty;
|
||||
return;
|
||||
}
|
||||
|
||||
string localizedText = description.descriptionKey.Localize(LocalizationTable);
|
||||
localizedText = DisplayTextResolver.Resolve(localizedText, descriptionArgs);
|
||||
descriptionText.text = InputGlyphParser.Parse(localizedText);
|
||||
}
|
||||
|
||||
/// <summary>清空条目内容。</summary>
|
||||
public void Clear()
|
||||
{
|
||||
if (descriptionText != null)
|
||||
{
|
||||
descriptionText.text = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4bcc59ecfb9425b4a85a1e3ad51c8739
|
||||
@@ -0,0 +1,177 @@
|
||||
using System.Collections.Generic;
|
||||
using Cielonos.MainGame.Inventory;
|
||||
using Cielonos.MainGame.UI;
|
||||
using Sirenix.OdinInspector;
|
||||
using SLSUtilities.General;
|
||||
using TMPro;
|
||||
using UniRx;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Localization.Settings;
|
||||
using UnityEngine.Serialization;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace SLSUtilities.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 通用物品详情面板,显示物品图标、名称、类型、描述条目、标签等信息。
|
||||
/// 可被不同的 UIPage 复用(机械台、商店、背包检视等)。
|
||||
/// </summary>
|
||||
public class ItemDetailPanel : UIElementBase
|
||||
{
|
||||
[Title("Detail Panel References")]
|
||||
public TMP_Text selectionHintText;
|
||||
//public Image itemIcon;
|
||||
public TMP_Text nameText;
|
||||
public TMP_Text typeRarityText;
|
||||
public TMP_Text institutionText;
|
||||
public RectTransform tagContainer;
|
||||
public GameObject tagPrefab;
|
||||
|
||||
// ─────────────────── 描述条目 ───────────────────
|
||||
|
||||
[Title("Description Entries")]
|
||||
[Tooltip("描述条目的容器,应挂载 VerticalLayoutGroup 以实现垂直排列。")]
|
||||
public RectTransform descriptionContainer;
|
||||
|
||||
[Tooltip("描述条目预制体,需挂载 ItemDescriptionEntry 组件。")]
|
||||
public GameObject descriptionEntryPrefab;
|
||||
|
||||
// ─────────────────── 运行时数据 ───────────────────
|
||||
|
||||
private ItemBase currentItem;
|
||||
private readonly List<ItemDescriptionEntry> activeEntries = new List<ItemDescriptionEntry>();
|
||||
|
||||
/// <summary>当前显示的物品。</summary>
|
||||
public ItemBase CurrentItem => currentItem;
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定物品的数据填充详情面板并显示。
|
||||
/// </summary>
|
||||
public void SetItem(ItemBase item)
|
||||
{
|
||||
currentItem = item;
|
||||
|
||||
if (item == null || item.contentData == null)
|
||||
{
|
||||
ClearPanel();
|
||||
return;
|
||||
}
|
||||
|
||||
ContentData data = item.contentData;
|
||||
|
||||
// 图标
|
||||
/*Sprite icon = data.itemIcon;
|
||||
if (itemIcon != null)
|
||||
{
|
||||
itemIcon.sprite = icon;
|
||||
itemIcon.enabled = icon != null;
|
||||
}*/
|
||||
|
||||
// 名称(本地化)
|
||||
nameText.text = data.displayNameKey.Localize("Items");
|
||||
|
||||
// 类型
|
||||
typeRarityText.text = data.itemType.ToString() + " - " + data.itemRarity.ToString();
|
||||
|
||||
//机构
|
||||
institutionText.text = string.Empty;
|
||||
for (var index = 0; index < data.institutions.Count; index++)
|
||||
{
|
||||
var institution = data.institutions[index];
|
||||
string comma = LocalizationSettings.SelectedLocale.Identifier.Code.StartsWith("zh") ? "、" : ", ";
|
||||
if(index < data.institutions.Count - 1)
|
||||
institutionText.text += institution.Localize("Items") + comma;
|
||||
else
|
||||
institutionText.text += institution.Localize("Items");
|
||||
}
|
||||
|
||||
// 描述条目
|
||||
RefreshDescriptions(data);
|
||||
|
||||
// 标签
|
||||
RefreshTags(data);
|
||||
|
||||
selectionHintText.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
/// <summary>清空面板内容并隐藏。</summary>
|
||||
public void ClearPanel()
|
||||
{
|
||||
currentItem = null;
|
||||
|
||||
//if (itemIcon != null) itemIcon.enabled = false;
|
||||
if (nameText != null) nameText.text = string.Empty;
|
||||
if (typeRarityText != null) typeRarityText.text = string.Empty;
|
||||
|
||||
ClearDescriptions();
|
||||
ClearTags();
|
||||
selectionHintText.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 描述条目
|
||||
// ================================================================
|
||||
|
||||
private void RefreshDescriptions(ContentData data)
|
||||
{
|
||||
ClearDescriptions();
|
||||
|
||||
if (descriptionContainer == null || descriptionEntryPrefab == null) return;
|
||||
if (data.descriptions == null || data.descriptions.Count == 0) return;
|
||||
|
||||
Dictionary<string, string> descriptionArgs = currentItem?.GetDescriptionArgs();
|
||||
|
||||
foreach (ItemDescription desc in data.descriptions)
|
||||
{
|
||||
ItemDescriptionEntry entry = Instantiate(descriptionEntryPrefab, descriptionContainer).GetComponent<ItemDescriptionEntry>();
|
||||
entry.SetDescription(desc, descriptionArgs);
|
||||
activeEntries.Add(entry);
|
||||
}
|
||||
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(descriptionContainer);
|
||||
}
|
||||
|
||||
private void ClearDescriptions()
|
||||
{
|
||||
foreach (ItemDescriptionEntry entry in activeEntries)
|
||||
{
|
||||
if (entry != null)
|
||||
{
|
||||
Destroy(entry.gameObject);
|
||||
}
|
||||
}
|
||||
activeEntries.Clear();
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 标签
|
||||
// ================================================================
|
||||
|
||||
private void RefreshTags(ContentData data)
|
||||
{
|
||||
ClearTags();
|
||||
|
||||
if (tagContainer == null || tagPrefab == null || data.tags == null) return;
|
||||
|
||||
foreach (string tag in data.tags)
|
||||
{
|
||||
GameObject tagObj = Instantiate(tagPrefab, tagContainer);
|
||||
TMP_Text tagText = tagObj.GetComponentInChildren<TMP_Text>();
|
||||
if (tagText != null)
|
||||
{
|
||||
tagText.text = tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearTags()
|
||||
{
|
||||
if (tagContainer == null) return;
|
||||
|
||||
for (int i = tagContainer.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
Destroy(tagContainer.GetChild(i).gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cielonos.MainGame.Inventory;
|
||||
using Cielonos.MainGame.Items;
|
||||
using SLSUtilities.General;
|
||||
using SLSUtilities.UI;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Cielonos.MainGame.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 单选类别筛选下拉框,基于 <see cref="TMP_Dropdown"/>。
|
||||
/// 每个选项对应一组 <see cref="ItemType"/>,选中后构建对应的 <see cref="ItemFilter"/>。
|
||||
/// 第一个选项通常配置为"全部"(types 留空即可)。
|
||||
/// <para>可复用于背包、商店等任何需要按类型筛选物品的 UI 页面。</para>
|
||||
/// </summary>
|
||||
public class ItemCategoryDropdown : UIElementBase
|
||||
{
|
||||
[Serializable]
|
||||
public struct CategoryEntry
|
||||
{
|
||||
[Tooltip("显示标签,可填入本地化 Key(如 'UI_Category_All'),会自动调用 Localize()。")]
|
||||
public string label;
|
||||
|
||||
[Tooltip("对应的物品类型。为空时表示显示所有类型(不筛选)。")]
|
||||
public ItemType[] types;
|
||||
}
|
||||
|
||||
[Header("Dropdown")]
|
||||
public TMP_Dropdown dropdown;
|
||||
|
||||
[Header("Categories")]
|
||||
[Tooltip("类别选项列表。第一个通常为'全部'(types 留空)。")]
|
||||
public List<CategoryEntry> categoryEntries = new List<CategoryEntry>
|
||||
{
|
||||
new CategoryEntry { label = "全部", types = new[] { ItemType.MainWeapon, ItemType.Support, ItemType.Passive, ItemType.Consumable } },
|
||||
new CategoryEntry { label = "主武器", types = new[] { ItemType.MainWeapon } },
|
||||
new CategoryEntry { label = "支援装备", types = new[] { ItemType.Support } },
|
||||
new CategoryEntry { label = "被动装备", types = new[] { ItemType.Passive } },
|
||||
new CategoryEntry { label = "消耗品", types = new[] { ItemType.Consumable } }
|
||||
};
|
||||
|
||||
/// <summary>当筛选条件变化时触发,参数为对应的 <see cref="ItemFilter"/>。</summary>
|
||||
public event Action<ItemFilter> OnFilterChanged;
|
||||
|
||||
/// <summary>当前生效的筛选条件。</summary>
|
||||
public ItemFilter CurrentFilter { get; private set; } = ItemFilter.None;
|
||||
|
||||
// ================================================================
|
||||
// 生命周期
|
||||
// ================================================================
|
||||
|
||||
private void Start()
|
||||
{
|
||||
InitializeOptions();
|
||||
|
||||
if (dropdown != null)
|
||||
{
|
||||
dropdown.onValueChanged.AddListener(OnValueChanged);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 公开接口
|
||||
// ================================================================
|
||||
|
||||
/// <summary>
|
||||
/// 使用 <see cref="categoryEntries"/> 重新初始化下拉选项。
|
||||
/// 语言切换后可手动调用以刷新标签文本。
|
||||
/// </summary>
|
||||
public void InitializeOptions()
|
||||
{
|
||||
if (dropdown == null) return;
|
||||
|
||||
dropdown.ClearOptions();
|
||||
List<string> labels = new List<string>(categoryEntries.Count);
|
||||
|
||||
foreach (CategoryEntry entry in categoryEntries)
|
||||
{
|
||||
labels.Add(entry.label);
|
||||
}
|
||||
|
||||
dropdown.AddOptions(labels);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据当前选中索引构建 <see cref="ItemFilter"/>。
|
||||
/// types 为空的选项返回 <see cref="ItemFilter.None"/>(即显示所有)。
|
||||
/// </summary>
|
||||
public ItemFilter BuildFilter()
|
||||
{
|
||||
return CurrentFilter;
|
||||
}
|
||||
|
||||
/// <summary>重置为第一个选项(通常为"全部"),不触发事件。</summary>
|
||||
public void ResetToAll()
|
||||
{
|
||||
CurrentFilter = ItemFilter.None;
|
||||
|
||||
if (dropdown != null)
|
||||
{
|
||||
dropdown.SetValueWithoutNotify(0);
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────── 内部 ───────────────────
|
||||
|
||||
private void OnValueChanged(int index)
|
||||
{
|
||||
if (index < 0 || index >= categoryEntries.Count) return;
|
||||
|
||||
CategoryEntry entry = categoryEntries[index];
|
||||
CurrentFilter = (entry.types == null || entry.types.Length == 0)
|
||||
? ItemFilter.None
|
||||
: ItemFilter.ByType(entry.types);
|
||||
|
||||
OnFilterChanged?.Invoke(CurrentFilter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b4d9f5140e87ab84f9f53b7fd7839ea3
|
||||
@@ -1,118 +0,0 @@
|
||||
using Cielonos.MainGame.Inventory;
|
||||
using Sirenix.OdinInspector;
|
||||
using SLSUtilities.General;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace SLSUtilities.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 通用物品详情面板,显示物品图标、名称、稀有度、描述、标签等信息。
|
||||
/// 可被不同的 UIPage 复用(机械台、商店、背包检视等)。
|
||||
/// </summary>
|
||||
public class ItemDetailPanel : UIElementBase
|
||||
{
|
||||
[Title("Detail Panel References")]
|
||||
public Image itemIcon;
|
||||
public Image rarityFrame;
|
||||
public TMP_Text itemNameText;
|
||||
public TMP_Text itemTypeText;
|
||||
public TMP_Text itemDescriptionText;
|
||||
public RectTransform tagContainer;
|
||||
public GameObject tagPrefab;
|
||||
|
||||
private ItemBase currentItem;
|
||||
|
||||
/// <summary>当前显示的物品。</summary>
|
||||
public ItemBase CurrentItem => currentItem;
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定物品的数据填充详情面板并显示。
|
||||
/// </summary>
|
||||
public void SetItem(ItemBase item)
|
||||
{
|
||||
currentItem = item;
|
||||
|
||||
if (item == null || item.contentData == null)
|
||||
{
|
||||
ClearPanel();
|
||||
return;
|
||||
}
|
||||
|
||||
ContentData data = item.contentData;
|
||||
|
||||
// 图标:主武器使用 rectIcon,其他使用 squareIcon
|
||||
Sprite icon = data.itemType == ItemType.MainWeapon ? data.rectIcon : data.squareIcon;
|
||||
if (itemIcon != null)
|
||||
{
|
||||
itemIcon.sprite = icon;
|
||||
itemIcon.enabled = icon != null;
|
||||
}
|
||||
|
||||
// 名称(本地化)
|
||||
if (itemNameText != null)
|
||||
{
|
||||
itemNameText.text = data.displayNameKey.Localize();
|
||||
}
|
||||
|
||||
// 类型
|
||||
if (itemTypeText != null)
|
||||
{
|
||||
itemTypeText.text = data.itemType.ToString();
|
||||
}
|
||||
|
||||
// 描述(本地化)
|
||||
if (itemDescriptionText != null)
|
||||
{
|
||||
itemDescriptionText.text = data.descriptionKey.Localize();
|
||||
}
|
||||
|
||||
// 标签
|
||||
RefreshTags(data);
|
||||
|
||||
Show();
|
||||
}
|
||||
|
||||
/// <summary>清空面板内容并隐藏。</summary>
|
||||
public void ClearPanel()
|
||||
{
|
||||
currentItem = null;
|
||||
|
||||
if (itemIcon != null) itemIcon.enabled = false;
|
||||
if (itemNameText != null) itemNameText.text = string.Empty;
|
||||
if (itemTypeText != null) itemTypeText.text = string.Empty;
|
||||
if (itemDescriptionText != null) itemDescriptionText.text = string.Empty;
|
||||
|
||||
ClearTags();
|
||||
Hide();
|
||||
}
|
||||
|
||||
private void RefreshTags(ContentData data)
|
||||
{
|
||||
ClearTags();
|
||||
|
||||
if (tagContainer == null || tagPrefab == null || data.tags == null) return;
|
||||
|
||||
foreach (string tag in data.tags)
|
||||
{
|
||||
GameObject tagObj = Instantiate(tagPrefab, tagContainer);
|
||||
TMP_Text tagText = tagObj.GetComponentInChildren<TMP_Text>();
|
||||
if (tagText != null)
|
||||
{
|
||||
tagText.text = tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearTags()
|
||||
{
|
||||
if (tagContainer == null) return;
|
||||
|
||||
for (int i = tagContainer.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
Destroy(tagContainer.GetChild(i).gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cielonos.MainGame.Items;
|
||||
using SLSUtilities.General;
|
||||
using SLSUtilities.UI;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Cielonos.MainGame.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 单选排序模式下拉框,基于 <see cref="TMP_Dropdown"/>。
|
||||
/// 提供多种 <see cref="ItemSortMode"/>,切换后通知订阅者。
|
||||
/// <para>可复用于背包、商店等任何需要物品排序的 UI 页面。</para>
|
||||
/// </summary>
|
||||
public class ItemSortDropdown : UIElementBase
|
||||
{
|
||||
[Header("Dropdown")]
|
||||
public TMP_Dropdown dropdown;
|
||||
|
||||
[Header("Labels")]
|
||||
[Tooltip("排序模式的显示标签,按顺序对应 Default / ByRarity / ByName。\n" +
|
||||
"可填入本地化 Key(如 'UI_Sort_Default'),会自动调用 Localize()。\n" +
|
||||
"若无对应翻译则直接显示原文。")]
|
||||
public List<string> sortModeLabels = new List<string>
|
||||
{
|
||||
"默认排序",
|
||||
"按稀有度",
|
||||
"按名称"
|
||||
};
|
||||
|
||||
/// <summary>当排序模式变化时触发。</summary>
|
||||
public event Action<ItemSortMode> OnSortChanged;
|
||||
|
||||
/// <summary>当前选中的排序模式。</summary>
|
||||
public ItemSortMode CurrentMode { get; private set; } = ItemSortMode.Default;
|
||||
|
||||
// ================================================================
|
||||
// 生命周期
|
||||
// ================================================================
|
||||
|
||||
private void Start()
|
||||
{
|
||||
InitializeOptions();
|
||||
|
||||
if (dropdown != null)
|
||||
{
|
||||
dropdown.onValueChanged.AddListener(OnValueChanged);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 公开接口
|
||||
// ================================================================
|
||||
|
||||
/// <summary>
|
||||
/// 使用 <see cref="sortModeLabels"/> 重新初始化下拉选项。
|
||||
/// 语言切换后可手动调用以刷新标签文本。
|
||||
/// </summary>
|
||||
public void InitializeOptions()
|
||||
{
|
||||
if (dropdown == null) return;
|
||||
|
||||
dropdown.ClearOptions();
|
||||
dropdown.AddOptions(sortModeLabels/*.ConvertAll(label => label.Localize("Items"))*/);
|
||||
}
|
||||
|
||||
/// <summary>重置为默认排序模式(不触发事件)。</summary>
|
||||
public void ResetToDefault()
|
||||
{
|
||||
CurrentMode = ItemSortMode.Default;
|
||||
|
||||
if (dropdown != null)
|
||||
{
|
||||
dropdown.SetValueWithoutNotify(0);
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────── 内部 ───────────────────
|
||||
|
||||
private void OnValueChanged(int index)
|
||||
{
|
||||
int modeCount = Enum.GetValues(typeof(ItemSortMode)).Length;
|
||||
|
||||
if (index >= 0 && index < modeCount)
|
||||
{
|
||||
CurrentMode = (ItemSortMode)index;
|
||||
OnSortChanged?.Invoke(CurrentMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9aaaaa3c6c02ad246b7a25a59005f425
|
||||
Reference in New Issue
Block a user