// 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
}