using UnityEngine; using UnityEngine.UI; using XericLibrary.Runtime.SuperStyleSheet; using XericUI.Core.StyleSheetUI; using XericUI.Helper; namespace XericUI.OverrideUI { /// /// 基于 SuperStyleSheet 的开关组件。 /// 通过 TransitionMode 选择颜色切换、纹理切换或两者并用, /// 额外支持 isOn 覆盖和 Checkmark 样式。 /// [AddComponentMenu("Xeric UI Vessel/UI/Style Toggle", 52)] public class XericStyleToggle : Toggle, IStyleRefreshable { /// 样式过渡模式 public enum TransitionMode { /// 仅颜色切换 Color, /// 仅纹理/Sprite 切换 Sprite, /// 颜色 + 纹理切换 ColorAndSprite } #region 序列化字段 [Header("过渡模式")] [SerializeField] [Tooltip("选择颜色 / 纹理 / 同时切换")] private TransitionMode m_TransitionMode = TransitionMode.ColorAndSprite; [Header("样式表状态")] [SerializeField] [Tooltip("五态颜色样式块(ColorTint)")] private StyleSheetState m_ColorState = new StyleSheetState("toggle", Transition.ColorTint); [SerializeField] [Tooltip("五态纹理样式块(SpriteState)")] private StyleSheetState m_TextureState = new StyleSheetState("toggle", Transition.SpriteSwap); [Header("Toggle 专用")] [SerializeField] [Tooltip("isOn=true 时的颜色覆盖")] private StyleField m_OnOverrideColor = new StyleField(); [SerializeField] [Tooltip("isOn=true 时的纹理覆盖")] private StyleField m_OnOverrideTexture = new StyleField(); [SerializeField] [Tooltip("Checkmark 打勾标志的样式")] private StyleField m_CheckmarkStyle = new StyleField(); [SerializeField] [Tooltip("启用样式表驱动过渡")] private bool m_EnableStyleTransition = true; #endregion #region 运行时 [System.NonSerialized] private StyleSheetSelectionState m_CurrentState = StyleSheetSelectionState.Normal; [System.NonSerialized] private Graphic m_TargetGraphic; [System.NonSerialized] private Image m_TargetImage; [System.NonSerialized] private Graphic m_CheckmarkGraphic; [System.NonSerialized] private Image m_CheckmarkImage; #endregion #region 生命周期 protected override void Awake() { base.Awake(); m_TargetGraphic = targetGraphic; m_TargetImage = m_TargetGraphic as Image; Transform checkmark = transform.Find("Background/Checkmark") ?? transform.Find("Checkmark"); if (checkmark != null) { m_CheckmarkGraphic = checkmark.GetComponent(); m_CheckmarkImage = checkmark.GetComponent(); } } protected override void OnEnable() { base.OnEnable(); RevalidateStyles(); StyleRefreshManager.Register(this); onValueChanged.AddListener(OnToggleValueChanged); ApplyForState(m_CurrentState); } #if UNITY_EDITOR /// 编辑器下属性变更时即刻刷新(路径从空变为有效等场景) protected override void OnValidate() { base.OnValidate(); RevalidateStyles(); ApplyForState(m_CurrentState); } #endif /// 重新注册/订阅所有 StyleField(供 OnEnable 和 OnValidate 共用) private void RevalidateStyles() { // 先退订防止双重订阅 m_ColorState?.UnsubscribeAll(OnStyleChanged); m_TextureState?.UnsubscribeAll(OnStyleChanged); m_OnOverrideColor.Member.OnValueChanged -= OnStyleChanged; m_OnOverrideTexture.Member.OnValueChanged -= OnStyleChanged; m_CheckmarkStyle.Member.OnValueChanged -= OnStyleChanged; m_ColorState?.RegisterAll(); m_TextureState?.RegisterAll(); m_ColorState?.SubscribeAll(OnStyleChanged); m_TextureState?.SubscribeAll(OnStyleChanged); m_OnOverrideColor?.Register(); m_OnOverrideColor.Member.OnValueChanged += OnStyleChanged; m_OnOverrideTexture?.Register(); m_OnOverrideTexture.Member.OnValueChanged += OnStyleChanged; m_CheckmarkStyle?.Register(); m_CheckmarkStyle.Member.OnValueChanged += OnStyleChanged; } protected override void OnDisable() { StyleRefreshManager.Unregister(this); onValueChanged.RemoveListener(OnToggleValueChanged); m_ColorState?.UnsubscribeAll(OnStyleChanged); m_TextureState?.UnsubscribeAll(OnStyleChanged); m_ColorState?.UnregisterAll(); m_TextureState?.UnregisterAll(); if (m_OnOverrideColor != null) { m_OnOverrideColor.Member.OnValueChanged -= OnStyleChanged; m_OnOverrideColor.Unregister(); } if (m_OnOverrideTexture != null) { m_OnOverrideTexture.Member.OnValueChanged -= OnStyleChanged; m_OnOverrideTexture.Unregister(); } if (m_CheckmarkStyle != null) { m_CheckmarkStyle.Member.OnValueChanged -= OnStyleChanged; m_CheckmarkStyle.Unregister(); } base.OnDisable(); } #endregion #region 状态过渡 protected override void DoStateTransition(SelectionState state, bool instant) { if (!gameObject.activeInHierarchy) return; m_CurrentState = (StyleSheetSelectionState)(int)state; if (m_EnableStyleTransition) ApplyForState(m_CurrentState, instant); else base.DoStateTransition(state, instant); } private void OnToggleValueChanged(bool _) { if (m_EnableStyleTransition) ApplyForState(m_CurrentState); } #endregion #region 样式应用 public void RefreshStyle() { ApplyForState(m_CurrentState, false); } private void ApplyForState(StyleSheetSelectionState state, bool instant = false) { if (m_TransitionMode == TransitionMode.Color || m_TransitionMode == TransitionMode.ColorAndSprite) { // 颜色:On 覆盖优先 StyleField colorField = isOn && m_OnOverrideColor?.GetValue() != null ? m_OnOverrideColor : m_ColorState?.GetForState(state); if (colorField != null && m_TargetGraphic != null) { StyleValue val = colorField.GetValue(); if (val != null) { float dur = instant ? 0f : (m_ColorState?.FadeDuration ?? 0.1f); m_TargetGraphic.CrossFadeColor((Color)val, dur, true, true); } } } if (m_TransitionMode == TransitionMode.Sprite || m_TransitionMode == TransitionMode.ColorAndSprite) { // 纹理:On 覆盖优先 StyleField texField = isOn && m_OnOverrideTexture?.GetValue() != null ? m_OnOverrideTexture : m_TextureState?.GetForState(state); if (texField != null && m_TargetImage != null) { StyleValue val = texField.GetValue(); Texture2D tex = val?.GetValueAs(); if (tex != null) m_TargetImage.overrideSprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f)); } } // Checkmark(独立于 TransitionMode) if (m_CheckmarkGraphic != null && m_CheckmarkStyle != null) { StyleValue val = m_CheckmarkStyle.GetValue(); if (val != null) m_CheckmarkGraphic.color = val; } } private void OnStyleChanged(StyleValue _) { if (gameObject.activeInHierarchy) ApplyForState(m_CurrentState); } #endregion #region 组件替换 #if UNITY_EDITOR [UnityEditor.MenuItem("CONTEXT/XericStyleToggle/Xeric UI/强制刷新样式", false, 1)] private static void ForceRefreshStyle(UnityEditor.MenuCommand command) { StyleRefreshManager.ForceRefreshAll(); } [UnityEditor.MenuItem("CONTEXT/Toggle/Replace To/Xeric (Style)", true)] private static bool ValidateReplaceToStyleToggle(UnityEditor.MenuCommand command) => command.context is Toggle and not XericStyleToggle; [UnityEditor.MenuItem("CONTEXT/Toggle/Replace To/Xeric (Style)", false, 11)] private static void ReplaceToStyleToggle(UnityEditor.MenuCommand command) => command.ReplaceCommandContext( o => (o.graphic, o.group, o.onValueChanged, o.navigation), (t, d) => { t.graphic = d.graphic; t.group = d.group; t.onValueChanged = d.onValueChanged; t.navigation = d.navigation; }); [UnityEditor.MenuItem("CONTEXT/Toggle/Replace To/Unity Toggle (Style)", true)] private static bool ValidateReplaceStyleToggleToUnity(UnityEditor.MenuCommand command) => command.context is XericStyleToggle; [UnityEditor.MenuItem("CONTEXT/Toggle/Replace To/Unity Toggle (Style)", false, 100)] private static void ReplaceStyleToggleToUnity(UnityEditor.MenuCommand command) => command.ReplaceCommandContext( o => (o.graphic, o.group, o.onValueChanged, o.navigation), (t, d) => { t.graphic = d.graphic; t.group = d.group; t.onValueChanged = d.onValueChanged; t.navigation = d.navigation; }); #endif #endregion } }