Files
XericLibrary-Publish/Runtime/UI/MacroUI.cs
2025-04-10 15:32:00 +08:00

494 lines
16 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using XericLibrary.Runtime.CustomEditor;
using Object = UnityEngine.Object;
using Sirenix.OdinInspector;
#if UNITY_EDITOR
using Sirenix.Utilities.Editor;
#endif
namespace XericLibrary.Runtime.MacroLibrary
{
/// <summary>
/// UI相关的扩展
/// </summary>
public static class MacroUI
{
#region toggle
private static FieldInfo togglesFieldInfo = typeof(ToggleGroup).GetField("m_Toggles", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
public static List<Toggle> GetToggles(this ToggleGroup toggleGroup)
{
if (toggleGroup == null)
{
throw new ArgumentNullException(nameof(toggleGroup));
}
if (togglesFieldInfo == null)
{
throw new InvalidOperationException("Unable to access the 'm_Toggles' field.");
}
var toggles = togglesFieldInfo.GetValue(toggleGroup) as List<Toggle>;
if (toggles == null)
{
throw new InvalidCastException("The 'm_Toggles' field is not of type List<Toggle>.");
}
return toggles;
// return (List<Toggle>)togglesFieldInfo.GetValue(toggleGroup);
}
/// <summary>
/// 获取当前单选项组中激活的索引
/// </summary>
/// <param name="toggleGroup"></param>
/// <returns></returns>
[Obsolete("索引依赖单选项自身在大纲中的顺序在运行时顺序可能与编辑时不一致请使用ToggleMapping")]
public static int GetActiveToggleIndex(this ToggleGroup toggleGroup)
{
var index = -1;
foreach (var toggle in toggleGroup.GetToggles())
{
index++;
if (toggle.isOn)
return index;
}
return -1;
}
/// <summary>
/// 获取当前单选项组的数量
/// </summary>
/// <param name="toggleGroup"></param>
/// <returns></returns>
public static int GetToggleCount(this ToggleGroup toggleGroup)
{
return toggleGroup.GetToggles().Count;
}
/// <summary>
/// 在单选项组上注册一个事件,当组中的任意成员变成激活状态时调用(其他的不会发生调用)。
/// </summary>
/// <param name="toggleGroup"></param>
/// <param name="onToggleChange"></param>
public static void OnToggleGroupChangeEvent(this ToggleGroup toggleGroup, Action<Toggle> onToggleChange)
{
foreach (var toggle in toggleGroup.GetToggles())
{
toggle.onValueChanged.AddListener(a =>
{
if (!a) return;
onToggleChange?.Invoke(toggle);
});
}
}
/// <summary>
/// 清空单选项组中的所有事件(与注册所有事件对应,但那个事件没法单独注销)
/// </summary>
/// <param name="toggleGroup"></param>
public static void RemoveToggleGroupChangeEvent(this ToggleGroup toggleGroup)
{
foreach (var toggle in toggleGroup.GetToggles())
{
toggle.onValueChanged.RemoveAllListeners();
}
}
#region toggle
/// <summary>
/// toggle映射集
/// <code>
/// toggle映射集必须使用unity序列化管理否则和直接使用toggleGroup没区别目的是解决toggleGroup在打包后大纲视图的索引可能发生错位的问题。
/// </code>
/// </summary>
[Serializable]
public class ToggleMapping : IEnumerable<Toggle>
{
#region
/// <summary>
/// 在单选项目切换时产生回调,返回选中的单选项目在组中的引用
/// </summary>
public Action<Toggle> OnAnyToggleSwitchOn;
/// <summary>
/// 在单选项目切换时产生回调,返回选中的单选项目在组中的索引
/// </summary>
public Action<int> OnAnyToggleIndexSwitchOn;
#endregion
#region
[LabelText("单选组")] public ToggleGroup ToggleGroup;
[SerializeField, LabelText("编辑单选项目顺序")]
[ListDrawerSettings(OnTitleBarGUI = "GetAndSortToggle")]
private List<Toggle> toggleList = new List<Toggle>();
// 当前选中的项目
private int nowSelectToggleIndex = 0;
private Toggle nowSelectToggle = null;
public Transform TogglesContext => ToggleGroup.transform;
/// <summary>
/// 当前选中的toggle索引
/// </summary>
public int NowSelectToggleIndex => nowSelectToggleIndex;
/// <summary>
/// 当前选中的toggle
/// </summary>
public Toggle NowSelectToggle => nowSelectToggle;
/// <summary>
/// 获取并给列表排序(顺序不一定与拼音有关)
/// </summary>
public void GetAndSortToggle()
{
#if UNITY_EDITOR
// 自动获取并排序
if (SirenixEditorGUI.ToolbarButton(EditorIcons.Refresh))
{
var newToggleList = MacroSort.FullCharacterOrderSort(ToggleGroup.GetToggles(), a => a.name).ToList();
if (newToggleList.Count <= 0 || newToggleList == null)
Debug.LogError("如果无法更新获取自动排序toggle可能是因为toggleGroup被隐藏了手动将其激活后再获取即可。");
else
toggleList = newToggleList;
}
// 反转顺序
if (SirenixEditorGUI.ToolbarButton(EditorIcons.TriangleDown))
{
toggleList.Reverse();
}
#else
var newToggleList = MacroSort.FullCharacterOrderSort(ToggleGroup.GetToggles(), a => a.name).ToList();
if (newToggleList.Count <= 0 || newToggleList == null)
Debug.LogError("如果无法更新获取自动排序toggle可能是因为toggleGroup被隐藏了手动将其激活后再获取即可。");
else
toggleList = newToggleList;
#endif
}
#endregion
#region
/// <summary>
/// 获取索引下的单选项组件
/// </summary>
/// <param name="index"></param>
public Toggle this[int index] => toggleList[index];
public int Count => toggleList.Count;
public IEnumerator<Toggle> GetEnumerator()
{
return toggleList.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
#region
/// <summary>
/// 初始化
/// <code>
/// 自动将所有的按键绑定回调事件;如果作为刷新函数调用,请确保已经完全组索引。
/// 或者可以直接清空旧项目(CleanToggle),然后逐个添加(AddToggle)
/// </code>
/// </summary>
public void Initialize()
{
// 未指定组时,说明压根没用这部分功能,用不着初始化。
if (ToggleGroup == null)
return;
// 防呆警告
if (toggleList.Count <= 0)
{
var toggles = ToggleGroup.GetToggles();
if (toggles.Count != toggleList.Count)
{
toggleList = toggles;
Debug.LogWarning($"在初始化单选项组时,{ToggleGroup.name}并未预先指定索引顺序,将默认使用大纲顺序。");
}
}
// 事件初始化
for (int i = 0; i < toggleList.Count; i++)
{
var toggle = toggleList[i];
ToggleAddEvent(toggle);
if (toggle.isOn)
{
nowSelectToggleIndex = i;
nowSelectToggle = toggle;
}
}
if (nowSelectToggle == null)
SetToggleOn(0);
}
/// <summary>
/// 添加一个toggle
/// </summary>
/// <param name="t"></param>
/// <returns>返回toggle的索引</returns>
public int AddToggle(Toggle t)
{
ToggleAddEvent(t);
var resultIndex = toggleList.Count;
toggleList.Add(t);
return resultIndex;
}
/// <summary>
/// 移除一个toggle这不会影响其他toggle的索引但此处移除的位置会为空。
/// <code>
/// 注 这不会销毁toggle。
/// </code>
/// </summary>
/// <param name="t"></param>
/// <returns>是否成功移除toggle</returns>
public bool RemoveToggle(Toggle t)
{
var index = toggleList.IndexOf(t);
if (index < 0)
return false;
t.onValueChanged.RemoveAllListeners();
toggleList[index] = null;
return true;
}
/// <summary>
/// 清除toggle
/// </summary>
/// <param name="allowDestroy">是否同时销毁所有toggle</param>
public void CleanToggle(bool allowDestroy)
{
foreach (var t in toggleList)
{
t.onValueChanged.RemoveAllListeners();
if (allowDestroy)
Object.Destroy(t);
}
toggleList.Clear();
}
/// <summary>
/// toggle注册的事件只有当按下时才需要调用此事件。
/// </summary>
/// <param name="t"></param>
private void ToggleAddEvent(Toggle t)
{
t.onValueChanged.AddListener(a =>
{
if (a) ToggleRegister(t);
});
}
/// <summary>
/// toggle注册的事件只有当按下时才需要调用此事件。
/// </summary>
/// <param name="t"></param>
private void ToggleRegister(Toggle t)
{
nowSelectToggle = t;
nowSelectToggleIndex = GetIndex(t);
OnAnyToggleSwitchOn?.Invoke(t);
OnAnyToggleIndexSwitchOn?.Invoke(nowSelectToggleIndex);
}
/// <summary>
/// 获取toggle代表的索引
/// </summary>
/// <param name="target"></param>
/// <returns>如果这个toggle不存在于当前的单选项组中返回-1</returns>
public int GetIndex(Toggle target)
{
return toggleList.IndexOf(target);
}
/// <summary>
/// 获取toggle代表的索引 如果toggle不存在于这个映射集中将返回否
/// </summary>
/// <param name="target"></param>
/// <param name="index"></param>
/// <returns></returns>
public bool TryGetIndex(Toggle target, out int index)
{
index = toggleList.IndexOf(target);
return index >= 0;
}
/// <summary>
/// 设置单选项激活
/// </summary>
/// <param name="target"></param>
public void SetToggleOn(Toggle target)
{
target.isOn = true;
}
/// <summary>
/// 设置单选项激活
/// </summary>
/// <param name="index"></param>
public void SetToggleOn(int index)
{
if (0 < index && index < toggleList.Count)
{
SetToggleOn(toggleList[index]);
}
}
/// <summary>
/// 设置单选项激活
/// </summary>
/// <param name="target"></param>
public void SetToggleOnWithoutNotify(Toggle target)
{
target.SetIsOnWithoutNotify(true);
}
/// <summary>
/// 设置单选项激活
/// </summary>
/// <param name="index"></param>
public void SetToggleOnWithoutNotify(int index)
{
if (0 < index && index < toggleList.Count)
{
SetToggleOnWithoutNotify(toggleList[index]);
}
}
/// <summary>
/// 清除映射结构(不会清除toggle实例)
/// </summary>
public void Clear()
{
ToggleGroup.RemoveToggleGroupChangeEvent();
toggleList.Clear();
}
/// <summary>
/// 清除映射结构并销毁所有toggle组件
/// </summary>
public void RemoveAllToggle()
{
for (int i = toggleList.Count - 1; i >= 0; i--)
{
Object.Destroy(toggleList[i]);
}
Clear();
}
#endregion
}
#endregion
#endregion
#region
/// <summary>
/// 所有按钮注册一个事件
/// </summary>
/// <param name="buttons"></param>
/// <param name="targetEvent"></param>
public static void RegisterOnClickEvent(this IEnumerable<Button> buttons, UnityAction targetEvent)
{
foreach (var button in buttons)
{
button.onClick.AddListener(targetEvent);
}
}
/// <summary>
/// 所有按钮注销一个事件
/// </summary>
/// <param name="buttons"></param>
/// <param name="targetEvent"></param>
public static void LogoutOnClickEvent(this IEnumerable<Button> buttons, UnityAction targetEvent)
{
foreach (var button in buttons)
{
button.onClick.RemoveListener(targetEvent);
}
}
/// <summary>
/// 设置所有按钮的可交互性
/// </summary>
/// <param name="buttons"></param>
/// <param name="interactable"></param>
public static void SetButtonInteractable(this IEnumerable<Button> buttons, bool interactable)
{
foreach (var button in buttons)
{
button.interactable = interactable;
}
}
#endregion
#region
public static void SetText(this IEnumerable<TextMeshProUGUI> texts, string text)
{
foreach (var t in texts)
{
t.text = text;
}
}
#endregion
#region UI变换获取扩展
/// <summary>
/// 获取一个组件的矩形变换组件
/// </summary>
/// <param name="target"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static RectTransform RectTransform<T>(this T target)
where T : Component
=> target.transform as RectTransform;
#endregion
}
}