// MIT License - Copyright (c) 2025 wallstop // Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE namespace WallstopStudios.UnityHelpers.Editor.Settings { #if UNITY_EDITOR using System; using System.Collections.Generic; using UnityEditor; using UnityEngine; using WallstopStudios.UnityHelpers.Core.DataStructure.Adapters; using WallstopStudios.UnityHelpers.Core.Helper; using WallstopStudios.UnityHelpers.Utils; /// /// Provides centralized notification when WButton or WEnumToggleButtons custom color entries /// are modified in Unity Helpers settings. Inspectors using specific color keys can subscribe /// to receive targeted repaint notifications. /// internal static class ColorKeyChangeNotifier { /// /// Fired when one or more WButton custom color entries change. /// The HashSet contains the normalized color keys that were modified. /// internal static event Action> OnWButtonColorKeysChanged; /// /// Fired when one or more WEnumToggleButtons custom color entries change. /// The HashSet contains the normalized color keys that were modified. /// internal static event Action> OnWEnumToggleButtonsColorKeysChanged; private static readonly Dictionary PreviousWButtonButtonColors = new( StringComparer.OrdinalIgnoreCase ); private static readonly Dictionary PreviousWButtonTextColors = new( StringComparer.OrdinalIgnoreCase ); private static readonly Dictionary PreviousWEnumSelectedBackgrounds = new( StringComparer.OrdinalIgnoreCase ); private static readonly Dictionary PreviousWEnumSelectedTexts = new( StringComparer.OrdinalIgnoreCase ); private static readonly Dictionary PreviousWEnumInactiveBackgrounds = new( StringComparer.OrdinalIgnoreCase ); private static readonly Dictionary PreviousWEnumInactiveTexts = new( StringComparer.OrdinalIgnoreCase ); private static HashSet _changedWButtonKeys; private static HashSet _changedWEnumKeys; /// /// Captures the current state of custom color dictionaries so we can detect changes later. /// Call this before the settings UI is drawn. /// internal static void CaptureCurrentState(SerializedObject settingsObject) { if (settingsObject == null) { return; } CaptureWButtonColors(settingsObject); CaptureWEnumToggleButtonsColors(settingsObject); } /// /// Compares current state against the captured snapshot and fires events for any changed keys. /// Call this after settings have been applied. /// internal static void DetectAndNotifyChanges(SerializedObject settingsObject) { if (settingsObject == null) { return; } _changedWButtonKeys = DetectWButtonChanges(settingsObject); _changedWEnumKeys = DetectWEnumToggleButtonsChanges(settingsObject); if (_changedWButtonKeys != null && _changedWButtonKeys.Count > 0) { OnWButtonColorKeysChanged?.Invoke(_changedWButtonKeys); } if (_changedWEnumKeys != null && _changedWEnumKeys.Count > 0) { OnWEnumToggleButtonsColorKeysChanged?.Invoke(_changedWEnumKeys); } // Recapture state for next comparison CaptureWButtonColors(settingsObject); CaptureWEnumToggleButtonsColors(settingsObject); } /// /// Requests a repaint of all inspector windows that may be affected by color key changes. /// internal static void RepaintAffectedInspectors() { // Use InspectorWindow.RepaintAllInspectors via reflection or simply repaint all // EditorWindow.GetWindow can't be used for InspectorWindow directly in all cases // Instead, use the tracker approach to get all active editors EditorWindow[] allWindows = Resources.FindObjectsOfTypeAll(); for (int index = 0; index < allWindows.Length; index++) { EditorWindow window = allWindows[index]; if (window == null) { continue; } string typeName = window.GetType().Name; if ( string.Equals(typeName, "InspectorWindow", StringComparison.Ordinal) || string.Equals(typeName, "PropertyEditor", StringComparison.Ordinal) ) { window.Repaint(); } } } /// /// Clears all cached state. Useful for tests or domain reload scenarios. /// internal static void ClearCache() { PreviousWButtonButtonColors.Clear(); PreviousWButtonTextColors.Clear(); PreviousWEnumSelectedBackgrounds.Clear(); PreviousWEnumSelectedTexts.Clear(); PreviousWEnumInactiveBackgrounds.Clear(); PreviousWEnumInactiveTexts.Clear(); _changedWButtonKeys = null; _changedWEnumKeys = null; } private static void CaptureWButtonColors(SerializedObject settingsObject) { PreviousWButtonButtonColors.Clear(); PreviousWButtonTextColors.Clear(); SerializedProperty customColors = settingsObject.FindProperty( UnityHelpersSettings.SerializedPropertyNames.WButtonCustomColors ); if (customColors == null) { return; } SerializedProperty keys = customColors.FindPropertyRelative( SerializableDictionarySerializedPropertyNames.Keys ); SerializedProperty values = customColors.FindPropertyRelative( SerializableDictionarySerializedPropertyNames.Values ); if (keys == null || values == null) { return; } int count = Mathf.Min(keys.arraySize, values.arraySize); for (int index = 0; index < count; index++) { SerializedProperty keyProp = keys.GetArrayElementAtIndex(index); SerializedProperty valueProp = values.GetArrayElementAtIndex(index); if (keyProp == null || valueProp == null) { continue; } string key = keyProp.stringValue; if (string.IsNullOrEmpty(key)) { continue; } SerializedProperty buttonColor = valueProp.FindPropertyRelative( UnityHelpersSettings.SerializedPropertyNames.WButtonCustomColorButton ); SerializedProperty textColor = valueProp.FindPropertyRelative( UnityHelpersSettings.SerializedPropertyNames.WButtonCustomColorText ); if (buttonColor != null) { PreviousWButtonButtonColors[key] = buttonColor.colorValue; } if (textColor != null) { PreviousWButtonTextColors[key] = textColor.colorValue; } } } private static void CaptureWEnumToggleButtonsColors(SerializedObject settingsObject) { PreviousWEnumSelectedBackgrounds.Clear(); PreviousWEnumSelectedTexts.Clear(); PreviousWEnumInactiveBackgrounds.Clear(); PreviousWEnumInactiveTexts.Clear(); SerializedProperty customColors = settingsObject.FindProperty( UnityHelpersSettings.SerializedPropertyNames.WEnumToggleButtonsCustomColors ); if (customColors == null) { return; } SerializedProperty keys = customColors.FindPropertyRelative( SerializableDictionarySerializedPropertyNames.Keys ); SerializedProperty values = customColors.FindPropertyRelative( SerializableDictionarySerializedPropertyNames.Values ); if (keys == null || values == null) { return; } int count = Mathf.Min(keys.arraySize, values.arraySize); for (int index = 0; index < count; index++) { SerializedProperty keyProp = keys.GetArrayElementAtIndex(index); SerializedProperty valueProp = values.GetArrayElementAtIndex(index); if (keyProp == null || valueProp == null) { continue; } string key = keyProp.stringValue; if (string.IsNullOrEmpty(key)) { continue; } SerializedProperty selectedBg = valueProp.FindPropertyRelative( UnityHelpersSettings .SerializedPropertyNames .WEnumToggleButtonsSelectedBackground ); SerializedProperty selectedText = valueProp.FindPropertyRelative( UnityHelpersSettings.SerializedPropertyNames.WEnumToggleButtonsSelectedText ); SerializedProperty inactiveBg = valueProp.FindPropertyRelative( UnityHelpersSettings .SerializedPropertyNames .WEnumToggleButtonsInactiveBackground ); SerializedProperty inactiveText = valueProp.FindPropertyRelative( UnityHelpersSettings.SerializedPropertyNames.WEnumToggleButtonsInactiveText ); if (selectedBg != null) { PreviousWEnumSelectedBackgrounds[key] = selectedBg.colorValue; } if (selectedText != null) { PreviousWEnumSelectedTexts[key] = selectedText.colorValue; } if (inactiveBg != null) { PreviousWEnumInactiveBackgrounds[key] = inactiveBg.colorValue; } if (inactiveText != null) { PreviousWEnumInactiveTexts[key] = inactiveText.colorValue; } } } private static HashSet DetectWButtonChanges(SerializedObject settingsObject) { HashSet changedKeys = null; SerializedProperty customColors = settingsObject.FindProperty( UnityHelpersSettings.SerializedPropertyNames.WButtonCustomColors ); if (customColors == null) { return changedKeys; } SerializedProperty keys = customColors.FindPropertyRelative( SerializableDictionarySerializedPropertyNames.Keys ); SerializedProperty values = customColors.FindPropertyRelative( SerializableDictionarySerializedPropertyNames.Values ); if (keys == null || values == null) { return changedKeys; } HashSet currentKeys = new(StringComparer.OrdinalIgnoreCase); int count = Mathf.Min(keys.arraySize, values.arraySize); for (int index = 0; index < count; index++) { SerializedProperty keyProp = keys.GetArrayElementAtIndex(index); SerializedProperty valueProp = values.GetArrayElementAtIndex(index); if (keyProp == null || valueProp == null) { continue; } string key = keyProp.stringValue; if (string.IsNullOrEmpty(key)) { continue; } currentKeys.Add(key); SerializedProperty buttonColor = valueProp.FindPropertyRelative( UnityHelpersSettings.SerializedPropertyNames.WButtonCustomColorButton ); SerializedProperty textColor = valueProp.FindPropertyRelative( UnityHelpersSettings.SerializedPropertyNames.WButtonCustomColorText ); bool buttonChanged = false; bool textChanged = false; if (buttonColor != null) { if ( !PreviousWButtonButtonColors.TryGetValue(key, out Color previousButton) || !ColorsEqual(previousButton, buttonColor.colorValue) ) { buttonChanged = true; } } if (textColor != null) { if ( !PreviousWButtonTextColors.TryGetValue(key, out Color previousText) || !ColorsEqual(previousText, textColor.colorValue) ) { textChanged = true; } } if (buttonChanged || textChanged) { changedKeys ??= new HashSet(StringComparer.OrdinalIgnoreCase); changedKeys.Add(key); } } // Check for removed keys foreach (string previousKey in PreviousWButtonButtonColors.Keys) { if (!currentKeys.Contains(previousKey)) { changedKeys ??= new HashSet(StringComparer.OrdinalIgnoreCase); changedKeys.Add(previousKey); } } return changedKeys; } private static HashSet DetectWEnumToggleButtonsChanges( SerializedObject settingsObject ) { HashSet changedKeys = null; SerializedProperty customColors = settingsObject.FindProperty( UnityHelpersSettings.SerializedPropertyNames.WEnumToggleButtonsCustomColors ); if (customColors == null) { return changedKeys; } SerializedProperty keys = customColors.FindPropertyRelative( SerializableDictionarySerializedPropertyNames.Keys ); SerializedProperty values = customColors.FindPropertyRelative( SerializableDictionarySerializedPropertyNames.Values ); if (keys == null || values == null) { return changedKeys; } HashSet currentKeys = new(StringComparer.OrdinalIgnoreCase); int count = Mathf.Min(keys.arraySize, values.arraySize); for (int index = 0; index < count; index++) { SerializedProperty keyProp = keys.GetArrayElementAtIndex(index); SerializedProperty valueProp = values.GetArrayElementAtIndex(index); if (keyProp == null || valueProp == null) { continue; } string key = keyProp.stringValue; if (string.IsNullOrEmpty(key)) { continue; } currentKeys.Add(key); SerializedProperty selectedBg = valueProp.FindPropertyRelative( UnityHelpersSettings .SerializedPropertyNames .WEnumToggleButtonsSelectedBackground ); SerializedProperty selectedText = valueProp.FindPropertyRelative( UnityHelpersSettings.SerializedPropertyNames.WEnumToggleButtonsSelectedText ); SerializedProperty inactiveBg = valueProp.FindPropertyRelative( UnityHelpersSettings .SerializedPropertyNames .WEnumToggleButtonsInactiveBackground ); SerializedProperty inactiveText = valueProp.FindPropertyRelative( UnityHelpersSettings.SerializedPropertyNames.WEnumToggleButtonsInactiveText ); bool anyChanged = false; if (selectedBg != null) { if ( !PreviousWEnumSelectedBackgrounds.TryGetValue(key, out Color previous) || !ColorsEqual(previous, selectedBg.colorValue) ) { anyChanged = true; } } if (!anyChanged && selectedText != null) { if ( !PreviousWEnumSelectedTexts.TryGetValue(key, out Color previous) || !ColorsEqual(previous, selectedText.colorValue) ) { anyChanged = true; } } if (!anyChanged && inactiveBg != null) { if ( !PreviousWEnumInactiveBackgrounds.TryGetValue(key, out Color previous) || !ColorsEqual(previous, inactiveBg.colorValue) ) { anyChanged = true; } } if (!anyChanged && inactiveText != null) { if ( !PreviousWEnumInactiveTexts.TryGetValue(key, out Color previous) || !ColorsEqual(previous, inactiveText.colorValue) ) { anyChanged = true; } } if (anyChanged) { changedKeys ??= new HashSet(StringComparer.OrdinalIgnoreCase); changedKeys.Add(key); } } // Check for removed keys foreach (string previousKey in PreviousWEnumSelectedBackgrounds.Keys) { if (!currentKeys.Contains(previousKey)) { changedKeys ??= new HashSet(StringComparer.OrdinalIgnoreCase); changedKeys.Add(previousKey); } } return changedKeys; } private static bool ColorsEqual(Color a, Color b) { const float tolerance = 0.001f; return Mathf.Abs(a.r - b.r) < tolerance && Mathf.Abs(a.g - b.g) < tolerance && Mathf.Abs(a.b - b.b) < tolerance && Mathf.Abs(a.a - b.a) < tolerance; } } #endif }