diff --git a/Editor/AlicizaX.UI.Extension.Editor.asmdef b/Editor/AlicizaX.UI.Extension.Editor.asmdef index 744e6a5..b3faa76 100644 --- a/Editor/AlicizaX.UI.Extension.Editor.asmdef +++ b/Editor/AlicizaX.UI.Extension.Editor.asmdef @@ -5,7 +5,10 @@ "GUID:6546d7765b4165b40850b3667f981c26", "GUID:6055be8ebefd69e48b49212b09b47b2f", "GUID:760f1778adc613f49a4394fb41ff0bbc", - "GUID:75b6f2078d190f14dbda4a5b747d709c" + "GUID:75b6f2078d190f14dbda4a5b747d709c", + "GUID:1619e00706139ce488ff80c0daeea8e7", + "GUID:fb064c8bf96bac94e90d2f39090daa94", + "GUID:acfef7cabed3b0a42b25edb1cd4fa259" ], "includePlatforms": [ "Editor" diff --git a/Editor/UX/UXTextMeshProEditor.cs b/Editor/UX/UXTextMeshProEditor.cs index eb4aed6..5039b88 100644 --- a/Editor/UX/UXTextMeshProEditor.cs +++ b/Editor/UX/UXTextMeshProEditor.cs @@ -1,19 +1,126 @@ #if TEXTMESHPRO_SUPPORT +using System.Collections.Generic; +using System.Linq; +using AlicizaX.Localization; +using AlicizaX.Localization.Editor; using UnityEditor; +using UnityEngine; +using UnityEngine.UIElements; +using UnityEditor.Experimental.GraphView; + namespace UnityEngine.UI { + internal readonly struct TableSelectionData + { + public readonly int Id; + public readonly string CombineKey; // SectionName/Key → 菜单层级 + public readonly string CombineValue; // SectionName.Key → 存储用 + + public TableSelectionData(int id, string combineKey, string combineValue) + { + Id = id; + CombineKey = combineKey; + CombineValue = combineValue; + } + } + [CustomEditor(typeof(UXTextMeshPro), true)] [CanEditMultipleObjects] internal class UXTextMeshProEditor : TMPro.EditorUtilities.TMP_EditorPanelUI { - private SerializedProperty text; private SerializedProperty localizationID; + private SerializedProperty m_localizationKey; + + private List allTables = new(); + private Dictionary allTableNames = new(); + private Dictionary previewLabelDic = new(); + private List allSelection = new(); + private int selectedSelectionIndex = 0; + + public override VisualElement CreateInspectorGUI() + { + RefreshAllTables(); + return base.CreateInspectorGUI(); + } + + private void RefreshAllTables() + { + allTables.Clear(); + string[] guids = AssetDatabase.FindAssets("t:GameLocaizationTable"); + foreach (string guid in guids) + { + string assetPath = AssetDatabase.GUIDToAssetPath(guid); + GameLocaizationTable table = AssetDatabase.LoadAssetAtPath(assetPath); + if (table != null) + { + allTables.Add(table); + } + } + + InitAllTables(); + } + + private void InitAllTables() + { + allTableNames.Clear(); + allSelection.Clear(); + previewLabelDic.Clear(); + allSelection.Add("None"); + allTableNames.TryAdd(0, new TableSelectionData(0, "None", string.Empty)); + + var localizationLanguage = allTables.Select(e => e.Languages.Find(t => t.LanguageName == LocalizationConfiguration.Instance.GenerateScriptCodeFirstConfig)).ToList(); + foreach (var localization in localizationLanguage) + { + foreach (var item in localization.Strings) + { + previewLabelDic.TryAdd(item.Key, item.Value); + } + } + + foreach (var table in allTables) + { + foreach (var sheet in table.TableSheet) + { + foreach (var selection in sheet.SectionSheet) + { + string combineKey = $"{table.name}/{sheet.SectionName}/{selection.Key}"; + string combineValue = $"{sheet.SectionName}.{selection.Key}"; + int id = selection.Id; + allTableNames.TryAdd(id, new TableSelectionData(id, combineKey, combineValue)); + allSelection.Add(combineKey); + } + } + } + } + + protected string GetPreviewLabel() + { + return previewLabelDic.TryGetValue(m_localizationKey.stringValue, out var label) + ? label + : "None"; + } + + + protected void FindSelectSelection() + { + selectedSelectionIndex = 0; + if (allTableNames.TryGetValue(localizationID.intValue, out TableSelectionData data)) + { + int idx = allSelection.FindIndex(t => t == data.CombineKey); + if (idx >= 0) + selectedSelectionIndex = idx; + } + } protected override void OnEnable() { - text = serializedObject.FindProperty("m_text"); localizationID = serializedObject.FindProperty("m_localizationID"); + m_localizationKey = serializedObject.FindProperty("m_localizationKey"); + + RefreshAllTables(); + FindSelectSelection(); + base.OnEnable(); } @@ -21,13 +128,125 @@ namespace UnityEngine.UI { serializedObject.Update(); + // m_localizationKey 只读 + EditorGUI.BeginDisabledGroup(true); + EditorGUILayout.PropertyField(m_localizationKey); + EditorGUILayout.LabelField("Text", GetPreviewLabel()); + EditorGUI.EndDisabledGroup(); - EditorGUILayout.PropertyField(localizationID); + // 检查是否找不到对应 ID 的 key + if (localizationID.intValue > 0 && !allTableNames.ContainsKey(localizationID.intValue)) + { + EditorGUILayout.HelpBox($"已选择的多语言 Key (ID={localizationID.intValue}) 已被删除,但仍然保留该 ID。", MessageType.Warning); + } + EditorGUILayout.Space(); + + // 下拉按钮 + if (GUILayout.Button(allSelection[selectedSelectionIndex], EditorStyles.popup)) + { + var mousePos = GUIUtility.GUIToScreenPoint(Event.current.mousePosition); + SearchWindow.Open( + new SearchWindowContext(mousePos), + ScriptableObject.CreateInstance().Init(allSelection, ApplySelection) + ); + } serializedObject.ApplyModifiedProperties(); base.OnInspectorGUI(); } + + private void ApplySelection(string selectedKey) + { + if (selectedKey == "None") + { + localizationID.intValue = 0; + m_localizationKey.stringValue = string.Empty; + selectedSelectionIndex = 0; + } + else + { + foreach (var kvp in allTableNames) + { + if (kvp.Value.CombineKey == selectedKey) + { + localizationID.intValue = kvp.Value.Id; + m_localizationKey.stringValue = kvp.Value.CombineValue; + selectedSelectionIndex = allSelection.IndexOf(kvp.Value.CombineKey); + break; + } + } + } + + serializedObject.ApplyModifiedProperties(); + } + } + + internal class LocalizationSearchProvider : ScriptableObject, ISearchWindowProvider + { + private List options; + private System.Action onSelect; + + public LocalizationSearchProvider Init(List options, System.Action onSelect) + { + this.options = options; + this.onSelect = onSelect; + return this; + } + + public List CreateSearchTree(SearchWindowContext context) + { + var tree = new List + { + new SearchTreeGroupEntry(new GUIContent("Localization Keys"), 0) + }; + + foreach (var option in options) + { + if (option == "None") + { + tree.Add(new SearchTreeEntry(new GUIContent("None")) { level = 1, userData = "None" }); + } + else + { + // `/` 自动分层 + string[] parts = option.Split('/'); + if (parts.Length == 1) + { + tree.Add(new SearchTreeEntry(new GUIContent(parts[0])) { level = 1, userData = option }); + } + else + { + // 前面的部分作为 group + for (int i = 0; i < parts.Length - 1; i++) + { + // 确保 group 唯一 + string groupName = string.Join("/", parts, 0, i + 1); + if (!tree.Exists(e => e.content.text == parts[i] && e.level == i + 1)) + { + tree.Add(new SearchTreeGroupEntry(new GUIContent(parts[i])) { level = i + 1 }); + } + } + + // 最后部分作为 Entry + tree.Add(new SearchTreeEntry(new GUIContent(parts[^1])) { level = parts.Length, userData = option }); + } + } + } + + return tree; + } + + public bool OnSelectEntry(SearchTreeEntry searchTreeEntry, SearchWindowContext context) + { + if (searchTreeEntry.userData is string key) + { + onSelect?.Invoke(key); + return true; + } + + return false; + } } } #endif diff --git a/Runtime/AlicizaX.UI.Extension.asmdef b/Runtime/AlicizaX.UI.Extension.asmdef index 7a2aba6..2466096 100644 --- a/Runtime/AlicizaX.UI.Extension.asmdef +++ b/Runtime/AlicizaX.UI.Extension.asmdef @@ -4,7 +4,9 @@ "references": [ "GUID:6055be8ebefd69e48b49212b09b47b2f", "GUID:80ecb87cae9c44d19824e70ea7229748", - "GUID:75469ad4d38634e559750d17036d5f7c" + "GUID:75469ad4d38634e559750d17036d5f7c", + "GUID:1619e00706139ce488ff80c0daeea8e7", + "GUID:189d55e03d78888459720d730f4d2424" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/Runtime/UXComponent/Button.meta b/Runtime/UXComponent/Button.meta new file mode 100644 index 0000000..45b15d6 --- /dev/null +++ b/Runtime/UXComponent/Button.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0e8bd5b405994da288d5a14f65ceb81b +timeCreated: 1758683565 \ No newline at end of file diff --git a/Runtime/UXComponent/UX/IButton.cs b/Runtime/UXComponent/Button/IButton.cs similarity index 100% rename from Runtime/UXComponent/UX/IButton.cs rename to Runtime/UXComponent/Button/IButton.cs diff --git a/Runtime/UXComponent/UX/IButton.cs.meta b/Runtime/UXComponent/Button/IButton.cs.meta similarity index 100% rename from Runtime/UXComponent/UX/IButton.cs.meta rename to Runtime/UXComponent/Button/IButton.cs.meta diff --git a/Runtime/UXComponent/UX/UXButton.cs b/Runtime/UXComponent/Button/UXButton.cs similarity index 100% rename from Runtime/UXComponent/UX/UXButton.cs rename to Runtime/UXComponent/Button/UXButton.cs diff --git a/Runtime/UXComponent/UX/UXButton.cs.meta b/Runtime/UXComponent/Button/UXButton.cs.meta similarity index 100% rename from Runtime/UXComponent/UX/UXButton.cs.meta rename to Runtime/UXComponent/Button/UXButton.cs.meta diff --git a/Runtime/UXComponent/Group.meta b/Runtime/UXComponent/Group.meta new file mode 100644 index 0000000..9742e51 --- /dev/null +++ b/Runtime/UXComponent/Group.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4474c46008f24d2ab4817687760f7c45 +timeCreated: 1758683576 \ No newline at end of file diff --git a/Runtime/UXComponent/UX/UXGroup.cs b/Runtime/UXComponent/Group/UXGroup.cs similarity index 100% rename from Runtime/UXComponent/UX/UXGroup.cs rename to Runtime/UXComponent/Group/UXGroup.cs diff --git a/Runtime/UXComponent/UX/UXGroup.cs.meta b/Runtime/UXComponent/Group/UXGroup.cs.meta similarity index 100% rename from Runtime/UXComponent/UX/UXGroup.cs.meta rename to Runtime/UXComponent/Group/UXGroup.cs.meta diff --git a/Runtime/UXComponent/Hotkey.meta b/Runtime/UXComponent/Hotkey.meta new file mode 100644 index 0000000..f6eea0f --- /dev/null +++ b/Runtime/UXComponent/Hotkey.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 24fbe05aa8721e14db91f27f5f5cfe6a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/UXComponent/Hotkey/HotkeyBindComponent.cs b/Runtime/UXComponent/Hotkey/HotkeyBindComponent.cs new file mode 100644 index 0000000..c6546ef --- /dev/null +++ b/Runtime/UXComponent/Hotkey/HotkeyBindComponent.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using AlicizaX.UI.Runtime; +using Sirenix.OdinInspector; +using UnityEngine; + +namespace AlicizaX.UI.Extension.UXComponent.Hotkey +{ + public class HotkeyBindComponent : MonoBehaviour + { + private UIHolderObjectBase _holderObjectBase; + + private void Awake() + { + _holderObjectBase = GetComponent(); + _holderObjectBase.OnWindowShowEvent += BindHotKeys; + _holderObjectBase.OnWindowClosedEvent += UnBindHotKeys; + } + + private void OnDestroy() + { + _holderObjectBase.OnWindowShowEvent -= BindHotKeys; + _holderObjectBase.OnWindowClosedEvent -= UnBindHotKeys; + } +#if UNITY_EDITOR + [InlineButton("SetHotKeyButtons")] +#endif + [SerializeField] + [HideLabel] + private UXButton[] hotButtons; + + +#if UNITY_EDITOR + private void SetHotKeyButtons() + { + var btns = transform.GetComponentsInChildren(true); + var hotBtnList = new List(); + for (int i = 0; i < btns.Length; i++) + { + if (btns[i].HasHotKeyRefrenced()) + { + hotBtnList.Add(btns[i]); + } + } + + hotButtons = hotBtnList.ToArray(); + } +#endif + + internal void BindHotKeys() + { + for (int i = 0; i < hotButtons.Length; i++) + { + hotButtons[i].BindHotKey(); + } + } + + internal void UnBindHotKeys() + { + for (int i = 0; i < hotButtons.Length; i++) + { + hotButtons[i].UnBindHotKey(); + } + } + } +} diff --git a/Runtime/UXComponent/Hotkey/HotkeyBindComponent.cs.meta b/Runtime/UXComponent/Hotkey/HotkeyBindComponent.cs.meta new file mode 100644 index 0000000..98017b4 --- /dev/null +++ b/Runtime/UXComponent/Hotkey/HotkeyBindComponent.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: fdcda7a93f3c4639a0cb68dd00509bb1 +timeCreated: 1758683821 \ No newline at end of file diff --git a/Runtime/UXComponent/UX/UXHotkey.cs b/Runtime/UXComponent/Hotkey/UXHotkey.cs similarity index 100% rename from Runtime/UXComponent/UX/UXHotkey.cs rename to Runtime/UXComponent/Hotkey/UXHotkey.cs diff --git a/Runtime/UXComponent/UX/UXHotkey.cs.meta b/Runtime/UXComponent/Hotkey/UXHotkey.cs.meta similarity index 100% rename from Runtime/UXComponent/UX/UXHotkey.cs.meta rename to Runtime/UXComponent/Hotkey/UXHotkey.cs.meta diff --git a/Runtime/UXComponent/Image.meta b/Runtime/UXComponent/Image.meta new file mode 100644 index 0000000..792a14d --- /dev/null +++ b/Runtime/UXComponent/Image.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d4b70cfbd2d7436085d694af49f29b79 +timeCreated: 1758683583 \ No newline at end of file diff --git a/Runtime/UXComponent/UX/UXImage.cs b/Runtime/UXComponent/Image/UXImage.cs similarity index 100% rename from Runtime/UXComponent/UX/UXImage.cs rename to Runtime/UXComponent/Image/UXImage.cs diff --git a/Runtime/UXComponent/UX/UXImage.cs.meta b/Runtime/UXComponent/Image/UXImage.cs.meta similarity index 100% rename from Runtime/UXComponent/UX/UXImage.cs.meta rename to Runtime/UXComponent/Image/UXImage.cs.meta diff --git a/Runtime/UXComponent/Text/UXTextMeshPro.cs b/Runtime/UXComponent/Text/UXTextMeshPro.cs index 69d2383..b89b9fd 100644 --- a/Runtime/UXComponent/Text/UXTextMeshPro.cs +++ b/Runtime/UXComponent/Text/UXTextMeshPro.cs @@ -6,7 +6,8 @@ namespace UnityEngine.UI { public class UXTextMeshPro : TextMeshProUGUI { - [SerializeField] private string m_localizationID = ""; + [SerializeField] private int m_localizationID; + [SerializeField] private string m_localizationKey = ""; protected override void Start() { @@ -17,9 +18,9 @@ namespace UnityEngine.UI protected void ChangeLanguage() { - if (!string.IsNullOrEmpty(m_localizationID) && !"None".Equals(m_localizationID) && UXComponentExtensionsHelper.LocalizationHelper != null) + if (!string.IsNullOrEmpty(m_localizationKey) && !"None".Equals(m_localizationKey) && UXComponentExtensionsHelper.LocalizationHelper != null) { - text = UXComponentExtensionsHelper.LocalizationHelper.GetString(m_localizationID); + text = UXComponentExtensionsHelper.LocalizationHelper.GetString(m_localizationKey); } } @@ -29,7 +30,7 @@ namespace UnityEngine.UI /// public void SetLocalization(string localizationID) { - m_localizationID = localizationID; + m_localizationKey = localizationID; ChangeLanguage(); } } diff --git a/Runtime/UXComponent/UXUIAnimation.cs b/Runtime/UXComponent/UXUIAnimation.cs new file mode 100644 index 0000000..e837796 --- /dev/null +++ b/Runtime/UXComponent/UXUIAnimation.cs @@ -0,0 +1,43 @@ +using System; +using AlicizaX.UI.Runtime; +using UnityEngine; + +namespace AlicizaX.UI.Extension.UXComponent +{ + [RequireComponent(typeof(AnimationFlow.Runtime.AnimationFlow))] + public class UXUIAnimation : MonoBehaviour + { + [SerializeField] private AnimationFlow.Runtime.AnimationFlow animationFlow; + [SerializeField] private string ShowAnimationName = "Show"; + [SerializeField] private string HideAnimationName = "Close"; + private UIHolderObjectBase _holderObjectBase; + + private void OnValidate() + { + animationFlow = GetComponent(); + } + + private void Awake() + { + _holderObjectBase = GetComponent(); + _holderObjectBase.OnWindowShowEvent += ShowAnimation; + _holderObjectBase.OnWindowClosedEvent += CloseAnimation; + } + + private void OnDestroy() + { + _holderObjectBase.OnWindowShowEvent -= ShowAnimation; + _holderObjectBase.OnWindowClosedEvent -= CloseAnimation; + } + + internal void ShowAnimation() + { + animationFlow.Play(ShowAnimationName); + } + + internal void CloseAnimation() + { + animationFlow.Play(HideAnimationName); + } + } +} diff --git a/Runtime/UXComponent/UXUIAnimation.cs.meta b/Runtime/UXComponent/UXUIAnimation.cs.meta new file mode 100644 index 0000000..f406231 --- /dev/null +++ b/Runtime/UXComponent/UXUIAnimation.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 72bd03ed7aed41ad81879dbb4dc96f71 +timeCreated: 1758701015 \ No newline at end of file