// MIT License - Copyright (c) 2025 wallstop
// Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE
namespace WallstopStudios.UnityHelpers.Editor.Utils
{
#if UNITY_EDITOR
using System;
using UnityEditor;
using UnityEngine;
///
/// Diagnostics helper for debugging WGroup theming restoration issues.
///
public static class WGroupThemingDiagnostics
{
///
/// When true, enables diagnostic logging for WGroup theming operations.
///
public static bool Enabled { get; set; } = false;
private const string LogPrefix = "[WGroupTheming] ";
internal static void LogCaptureColors(
Color contentColor,
Color guiColor,
Color backgroundColor,
bool isInsideWGroup,
int scopeDepth
)
{
if (!Enabled)
{
return;
}
Debug.Log(
$"{LogPrefix}CaptureWGroupColors: "
+ $"contentColor={FormatColor(contentColor)}, "
+ $"guiColor={FormatColor(guiColor)}, "
+ $"bgColor={FormatColor(backgroundColor)}, "
+ $"isInsideWGroup={isInsideWGroup}, "
+ $"scopeDepth={scopeDepth}"
);
}
internal static void LogExitTheming(
Color beforeContentColor,
Color beforeGuiColor,
Color beforeBgColor,
Color afterContentColor,
Color afterGuiColor,
Color afterBgColor
)
{
if (!Enabled)
{
return;
}
Debug.Log(
$"{LogPrefix}ExitWGroupTheming ENTER: "
+ $"BEFORE: content={FormatColor(beforeContentColor)}, "
+ $"gui={FormatColor(beforeGuiColor)}, "
+ $"bg={FormatColor(beforeBgColor)} | "
+ $"AFTER: content={FormatColor(afterContentColor)}, "
+ $"gui={FormatColor(afterGuiColor)}, "
+ $"bg={FormatColor(afterBgColor)}"
);
}
internal static void LogRestoreTheming(
string phase,
Color contentColor,
Color guiColor,
Color backgroundColor
)
{
if (!Enabled)
{
return;
}
Debug.Log(
$"{LogPrefix}RestoreWGroupTheming {phase}: "
+ $"content={FormatColor(contentColor)}, "
+ $"gui={FormatColor(guiColor)}, "
+ $"bg={FormatColor(backgroundColor)}"
);
}
internal static void LogDrawFoldout(
string propertyPath,
bool wasInsideWGroup,
Color currentContentColor,
Color currentGuiColor,
Color savedContentColor,
Color savedGuiColor
)
{
if (!Enabled)
{
return;
}
Debug.Log(
$"{LogPrefix}DrawFoldout: path={propertyPath}, "
+ $"wasInsideWGroup={wasInsideWGroup}, "
+ $"CURRENT: content={FormatColor(currentContentColor)}, "
+ $"gui={FormatColor(currentGuiColor)} | "
+ $"SAVED: content={FormatColor(savedContentColor)}, "
+ $"gui={FormatColor(savedGuiColor)}"
);
}
internal static void LogFoldoutStyleColors(string phase, GUIStyle foldout)
{
if (!Enabled)
{
return;
}
Debug.Log(
$"{LogPrefix}FoldoutStyle {phase}: "
+ $"normal={FormatColor(foldout.normal.textColor)}, "
+ $"onNormal={FormatColor(foldout.onNormal.textColor)}, "
+ $"hover={FormatColor(foldout.hover.textColor)}"
);
}
internal static string FormatColor(Color c)
{
return $"({c.r:F2},{c.g:F2},{c.b:F2},{c.a:F2})";
}
}
internal static class GroupGUIWidthUtility
{
private sealed class WidthPaddingScope : IDisposable
{
private readonly float _padding;
private readonly float _leftPadding;
private readonly float _rightPadding;
private readonly bool _trackScopeDepth;
private bool _disposed;
internal WidthPaddingScope(float horizontalPadding)
{
float resolved = Mathf.Max(0f, horizontalPadding);
float split = resolved * 0.5f;
_padding = resolved;
_leftPadding = split;
_rightPadding = resolved - split;
// Only track scope depth if there's actual padding to apply
// Zero padding should not increase scope depth as it has no visual effect
_trackScopeDepth = _padding > 0f;
if (_trackScopeDepth)
{
_scopeDepth++;
_totalPadding += _padding;
_totalLeftPadding += _leftPadding;
_totalRightPadding += _rightPadding;
}
}
internal WidthPaddingScope(
float horizontalPadding,
float leftPadding,
float rightPadding
)
{
_leftPadding = Mathf.Max(0f, leftPadding);
_rightPadding = Mathf.Max(0f, rightPadding);
float combined = Mathf.Max(0f, horizontalPadding);
if (combined <= 0f)
{
combined = _leftPadding + _rightPadding;
}
// Only track scope depth if there's actual padding to apply
// Zero padding should not increase scope depth as it has no visual effect
if (combined <= 0f)
{
_padding = 0f;
_leftPadding = 0f;
_rightPadding = 0f;
_trackScopeDepth = false;
return;
}
_padding = combined;
float resolvedLeft = _leftPadding;
float resolvedRight = _rightPadding;
if (resolvedLeft <= 0f && resolvedRight <= 0f)
{
float split = combined * 0.5f;
resolvedLeft = split;
resolvedRight = combined - split;
}
_leftPadding = resolvedLeft;
_rightPadding = resolvedRight;
_trackScopeDepth = true;
_scopeDepth++;
_totalPadding += _padding;
_totalLeftPadding += _leftPadding;
_totalRightPadding += _rightPadding;
}
public void Dispose()
{
if (_disposed || !_trackScopeDepth)
{
return;
}
_disposed = true;
_totalPadding = Mathf.Max(0f, _totalPadding - _padding);
_totalLeftPadding = Mathf.Max(0f, _totalLeftPadding - _leftPadding);
_totalRightPadding = Mathf.Max(0f, _totalRightPadding - _rightPadding);
_scopeDepth = Mathf.Max(0, _scopeDepth - 1);
}
}
private static float _totalPadding;
private static float _totalLeftPadding;
private static float _totalRightPadding;
private static int _scopeDepth;
private static bool _isInsideWGroupPropertyDraw;
private static WGroupThemeState? _currentThemeState;
internal static float CurrentHorizontalPadding => _totalPadding;
internal static float CurrentLeftPadding => _totalLeftPadding;
internal static float CurrentRightPadding => _totalRightPadding;
internal static int CurrentScopeDepth => _scopeDepth;
internal static bool IsInsideWGroupPropertyDraw => _isInsideWGroupPropertyDraw;
internal static WGroupThemeState? CurrentThemeState => _currentThemeState;
[System.Diagnostics.Conditional("UNITY_EDITOR")]
internal static void ResetForTests()
{
_totalPadding = 0f;
_totalLeftPadding = 0f;
_totalRightPadding = 0f;
_scopeDepth = 0;
_isInsideWGroupPropertyDraw = false;
_currentThemeState = null;
}
internal static IDisposable PushWGroupPropertyContext()
{
return new WGroupPropertyContextScope();
}
internal static void ApplyCurrentThemeColors()
{
if (_currentThemeState.HasValue)
{
WGroupThemeState state = _currentThemeState.Value;
GUI.color = state.GuiColor;
GUI.contentColor = state.ContentColor;
GUI.backgroundColor = state.BackgroundColor;
return;
}
GUI.color = Color.white;
GUI.contentColor = Color.white;
GUI.backgroundColor = Color.white;
}
///
/// Creates a scope that temporarily exits WGroup theming context.
/// Within this scope, IsInsideWGroup will return false and GUI colors will be reset to defaults.
/// Use this for complex property drawers (like SerializableDictionary/SerializableHashSet)
/// that have their own theming and don't work well with WGroup palette overrides.
///
internal static IDisposable ExitWGroupTheming()
{
return new ExitWGroupThemingScope();
}
///
/// Creates a scope that temporarily restores WGroup theming colors.
/// Use this INSIDE an ExitWGroupTheming scope when you need to draw specific elements
/// (like foldout labels) that should still use WGroup palette colors because they
/// render against the WGroup background.
///
/// The saved GUI colors to restore (from before exiting theming).
internal static IDisposable RestoreWGroupTheming(WGroupSavedColors savedColors)
{
return new RestoreWGroupThemingScope(savedColors);
}
///
/// Captures the current WGroup GUI colors before exiting theming.
/// Call this BEFORE entering ExitWGroupTheming to save the colors for later restoration.
/// If currently inside a WGroup with a palette, uses the theme state colors which
/// represent the actual palette colors (since GUI.contentColor may have been modified).
///
internal static WGroupSavedColors CaptureWGroupColors()
{
// If we have a current theme state (inside WGroup), use those colors
// since GUI.contentColor may have been modified by nested scopes
WGroupSavedColors colors;
if (_currentThemeState.HasValue)
{
WGroupThemeState state = _currentThemeState.Value;
colors = new WGroupSavedColors
{
ContentColor = state.ContentColor,
Color = state.GuiColor,
BackgroundColor = state.BackgroundColor,
};
}
else
{
colors = new WGroupSavedColors
{
ContentColor = GUI.contentColor,
Color = GUI.color,
BackgroundColor = GUI.backgroundColor,
};
}
WGroupThemingDiagnostics.LogCaptureColors(
colors.ContentColor,
colors.Color,
colors.BackgroundColor,
_currentThemeState != null,
_scopeDepth
);
return colors;
}
///
/// Cached foldout arrow texture for drawing themed foldouts.
///
private static Texture2D _foldoutArrowRight;
///
/// Cached expanded foldout arrow texture for drawing themed foldouts.
///
private static Texture2D _foldoutArrowDown;
///
/// Draws a foldout with proper WGroup theming. Unity's built-in EditorGUI.Foldout
/// uses pre-baked icon textures that don't respect GUI.contentColor, so this method
/// manually draws the arrow icon with the correct tint color.
///
/// The rectangle to draw the foldout in.
/// Current expansion state.
/// The label content to display.
/// Whether clicking the label toggles the foldout.
/// The new expansion state.
internal static bool DrawThemedFoldout(
Rect position,
bool isExpanded,
GUIContent content,
bool toggleOnLabelClick = true
)
{
// Cache arrow textures
if (_foldoutArrowRight == null)
{
_foldoutArrowRight =
EditorGUIUtility.IconContent("d_forward@2x").image as Texture2D;
if (_foldoutArrowRight == null)
{
_foldoutArrowRight =
EditorGUIUtility.IconContent("d_forward").image as Texture2D;
}
if (_foldoutArrowRight == null)
{
_foldoutArrowRight =
EditorGUIUtility.IconContent("forward@2x").image as Texture2D;
}
if (_foldoutArrowRight == null)
{
_foldoutArrowRight = EditorGUIUtility.IconContent("forward").image as Texture2D;
}
}
if (_foldoutArrowDown == null)
{
_foldoutArrowDown =
EditorGUIUtility.IconContent("d_icon dropdown@2x").image as Texture2D;
if (_foldoutArrowDown == null)
{
_foldoutArrowDown =
EditorGUIUtility.IconContent("d_icon dropdown").image as Texture2D;
}
if (_foldoutArrowDown == null)
{
_foldoutArrowDown =
EditorGUIUtility.IconContent("icon dropdown@2x").image as Texture2D;
}
if (_foldoutArrowDown == null)
{
_foldoutArrowDown =
EditorGUIUtility.IconContent("icon dropdown").image as Texture2D;
}
}
// If we don't have custom arrow textures, fall back to standard foldout
if (_foldoutArrowRight == null && _foldoutArrowDown == null)
{
return EditorGUI.Foldout(position, isExpanded, content, toggleOnLabelClick);
}
// Calculate rects
float arrowSize = EditorGUIUtility.singleLineHeight;
Rect arrowRect = new Rect(position.x, position.y, arrowSize, arrowSize);
Rect labelRect = new Rect(
position.x + arrowSize,
position.y,
position.width - arrowSize,
position.height
);
// Handle click on arrow
Event evt = Event.current;
if (evt.type == EventType.MouseDown && evt.button == 0)
{
if (arrowRect.Contains(evt.mousePosition))
{
isExpanded = !isExpanded;
evt.Use();
GUI.changed = true;
}
else if (toggleOnLabelClick && labelRect.Contains(evt.mousePosition))
{
isExpanded = !isExpanded;
evt.Use();
GUI.changed = true;
}
}
// Draw arrow icon with current GUI.contentColor tint
Texture2D arrowTexture = isExpanded ? _foldoutArrowDown : _foldoutArrowRight;
if (arrowTexture != null)
{
// Center the arrow in the rect
float iconSize = Mathf.Min(arrowRect.width, arrowRect.height) * 0.7f;
Rect iconRect = new Rect(
arrowRect.x + (arrowRect.width - iconSize) * 0.5f,
arrowRect.y + (arrowRect.height - iconSize) * 0.5f,
iconSize,
iconSize
);
// GUI.DrawTexture respects GUI.contentColor for tinting
GUI.DrawTexture(iconRect, arrowTexture, ScaleMode.ScaleToFit);
}
// Draw label with current style colors
EditorGUI.LabelField(labelRect, content);
return isExpanded;
}
///
/// Stores saved WGroup GUI colors for temporary restoration.
///
internal struct WGroupSavedColors
{
public Color ContentColor;
public Color Color;
public Color BackgroundColor;
}
///
/// Saved state for a GUIStyle, used to restore EditorStyles after exiting WGroup theming.
///
private struct StyleState
{
public Texture2D NormalBackground;
public Texture2D FocusedBackground;
public Texture2D ActiveBackground;
public Texture2D HoverBackground;
public Texture2D OnNormalBackground;
public Texture2D OnFocusedBackground;
public Texture2D OnActiveBackground;
public Texture2D OnHoverBackground;
public Color NormalTextColor;
public Color FocusedTextColor;
public Color ActiveTextColor;
public Color HoverTextColor;
public Color OnNormalTextColor;
public Color OnFocusedTextColor;
public Color OnActiveTextColor;
public Color OnHoverTextColor;
public bool IsValid;
}
internal readonly struct WGroupThemeState
{
public readonly Color GuiColor;
public readonly Color ContentColor;
public readonly Color BackgroundColor;
internal WGroupThemeState(Color guiColor, Color contentColor, Color backgroundColor)
{
GuiColor = guiColor;
ContentColor = contentColor;
BackgroundColor = backgroundColor;
}
}
private static StyleState SaveFullStyleState(GUIStyle style)
{
if (style == null)
{
return default;
}
return new StyleState
{
NormalBackground = style.normal.background,
FocusedBackground = style.focused.background,
ActiveBackground = style.active.background,
HoverBackground = style.hover.background,
OnNormalBackground = style.onNormal.background,
OnFocusedBackground = style.onFocused.background,
OnActiveBackground = style.onActive.background,
OnHoverBackground = style.onHover.background,
NormalTextColor = style.normal.textColor,
FocusedTextColor = style.focused.textColor,
ActiveTextColor = style.active.textColor,
HoverTextColor = style.hover.textColor,
OnNormalTextColor = style.onNormal.textColor,
OnFocusedTextColor = style.onFocused.textColor,
OnActiveTextColor = style.onActive.textColor,
OnHoverTextColor = style.onHover.textColor,
IsValid = true,
};
}
private static void RestoreFullStyleState(GUIStyle style, StyleState saved)
{
if (style == null || !saved.IsValid)
{
return;
}
style.normal.background = saved.NormalBackground;
style.focused.background = saved.FocusedBackground;
style.active.background = saved.ActiveBackground;
style.hover.background = saved.HoverBackground;
style.onNormal.background = saved.OnNormalBackground;
style.onFocused.background = saved.OnFocusedBackground;
style.onActive.background = saved.OnActiveBackground;
style.onHover.background = saved.OnHoverBackground;
style.normal.textColor = saved.NormalTextColor;
style.focused.textColor = saved.FocusedTextColor;
style.active.textColor = saved.ActiveTextColor;
style.hover.textColor = saved.HoverTextColor;
style.onNormal.textColor = saved.OnNormalTextColor;
style.onFocused.textColor = saved.OnFocusedTextColor;
style.onActive.textColor = saved.OnActiveTextColor;
style.onHover.textColor = saved.OnHoverTextColor;
}
private sealed class ExitWGroupThemingScope : IDisposable
{
private readonly bool _previousIsInsideWGroupPropertyDraw;
private readonly Color _previousContentColor;
private readonly Color _previousColor;
private readonly Color _previousBackgroundColor;
private readonly WGroupThemeState? _previousThemeState;
// Saved EditorStyles states - WGroupColorScope modifies these globally
private readonly StyleState _savedTextField;
private readonly StyleState _savedNumberField;
private readonly StyleState _savedObjectField;
private readonly StyleState _savedPopup;
private readonly StyleState _savedHelpBox;
private readonly StyleState _savedFoldout;
private readonly StyleState _savedLabel;
private readonly StyleState _savedToggle;
private readonly StyleState _savedMiniButton;
private readonly StyleState _savedMiniButtonLeft;
private readonly StyleState _savedMiniButtonMid;
private readonly StyleState _savedMiniButtonRight;
private bool _disposed;
internal ExitWGroupThemingScope()
{
_previousIsInsideWGroupPropertyDraw = _isInsideWGroupPropertyDraw;
_previousContentColor = GUI.contentColor;
_previousColor = GUI.color;
_previousBackgroundColor = GUI.backgroundColor;
_previousThemeState = _currentThemeState;
// Save current EditorStyles state (which may have been modified by WGroupColorScope)
_savedTextField = SaveFullStyleState(EditorStyles.textField);
_savedNumberField = SaveFullStyleState(EditorStyles.numberField);
_savedObjectField = SaveFullStyleState(EditorStyles.objectField);
_savedPopup = SaveFullStyleState(EditorStyles.popup);
_savedHelpBox = SaveFullStyleState(EditorStyles.helpBox);
_savedFoldout = SaveFullStyleState(EditorStyles.foldout);
_savedLabel = SaveFullStyleState(EditorStyles.label);
_savedToggle = SaveFullStyleState(EditorStyles.toggle);
_savedMiniButton = SaveFullStyleState(EditorStyles.miniButton);
_savedMiniButtonLeft = SaveFullStyleState(EditorStyles.miniButtonLeft);
_savedMiniButtonMid = SaveFullStyleState(EditorStyles.miniButtonMid);
_savedMiniButtonRight = SaveFullStyleState(EditorStyles.miniButtonRight);
// Clear WGroup context
_currentThemeState = null;
_isInsideWGroupPropertyDraw = false;
// Reset GUI colors to skin-appropriate defaults
// GUI.contentColor is the main control for text rendering color - must be reset!
// Pro skin (dark theme) uses light text, Personal skin (light theme) uses dark text
Color skinTextColor = EditorGUIUtility.isProSkin
? new Color(0.82f, 0.82f, 0.82f, 1f) // Light gray for dark theme
: new Color(0.09f, 0.09f, 0.09f, 1f); // Dark gray for light theme
GUI.contentColor = skinTextColor;
GUI.color = Color.white;
GUI.backgroundColor = Color.white;
// Reset EditorStyles backgrounds to Unity defaults (null = use internal default)
// WGroupColorScope modifies backgrounds on EditorStyles.
ResetStyleToSkinDefaults(EditorStyles.textField);
ResetStyleToSkinDefaults(EditorStyles.numberField);
ResetStyleToSkinDefaults(EditorStyles.objectField);
ResetStyleToSkinDefaults(EditorStyles.popup);
ResetStyleToSkinDefaults(EditorStyles.helpBox);
ResetStyleToSkinDefaults(EditorStyles.foldout);
ResetStyleToSkinDefaults(EditorStyles.label);
ResetStyleToSkinDefaults(EditorStyles.boldLabel);
ResetStyleToSkinDefaults(EditorStyles.toggle);
ResetStyleToSkinDefaults(EditorStyles.miniButton);
ResetStyleToSkinDefaults(EditorStyles.miniButtonLeft);
ResetStyleToSkinDefaults(EditorStyles.miniButtonMid);
ResetStyleToSkinDefaults(EditorStyles.miniButtonRight);
ResetStyleToSkinDefaults(EditorStyles.miniLabel);
// Reset text colors from GUI.skin which has the correct skin-specific defaults
// GUI.skin styles have the proper text colors for light/dark themes
// Use EditorGUIUtility.isProSkin to determine the correct text color
// Pro skin (dark theme) uses light text, Personal skin (light theme) uses dark text
skinTextColor = EditorGUIUtility.isProSkin
? new Color(0.82f, 0.82f, 0.82f, 1f) // Light gray for dark theme
: new Color(0.09f, 0.09f, 0.09f, 1f); // Dark gray for light theme
SetStyleTextColor(EditorStyles.label, skinTextColor);
SetStyleTextColor(EditorStyles.boldLabel, skinTextColor);
SetStyleTextColor(EditorStyles.toggle, skinTextColor);
SetStyleTextColor(EditorStyles.foldout, skinTextColor);
SetStyleTextColor(EditorStyles.miniLabel, skinTextColor);
// For input fields and buttons, also use the skin text color
SetStyleTextColor(EditorStyles.textField, skinTextColor);
SetStyleTextColor(EditorStyles.numberField, skinTextColor);
SetStyleTextColor(EditorStyles.miniButton, skinTextColor);
SetStyleTextColor(EditorStyles.miniButtonLeft, skinTextColor);
SetStyleTextColor(EditorStyles.miniButtonMid, skinTextColor);
SetStyleTextColor(EditorStyles.miniButtonRight, skinTextColor);
}
private static void SetStyleTextColor(GUIStyle style, Color color)
{
if (style == null)
{
return;
}
style.normal.textColor = color;
style.hover.textColor = color;
style.active.textColor = color;
style.focused.textColor = color;
style.onNormal.textColor = color;
style.onHover.textColor = color;
style.onActive.textColor = color;
style.onFocused.textColor = color;
}
private static void ResetStyleBackgrounds(GUIStyle style)
{
if (style == null)
{
return;
}
// Clear any custom backgrounds - Unity will use its internal defaults
style.normal.background = null;
style.focused.background = null;
style.active.background = null;
style.hover.background = null;
style.onNormal.background = null;
style.onFocused.background = null;
style.onActive.background = null;
style.onHover.background = null;
}
private static void ResetStyleToSkinDefaults(GUIStyle style)
{
if (style == null)
{
return;
}
// Clear any custom backgrounds - Unity will use its internal defaults
style.normal.background = null;
style.focused.background = null;
style.active.background = null;
style.hover.background = null;
style.onNormal.background = null;
style.onFocused.background = null;
style.onActive.background = null;
style.onHover.background = null;
// Don't reset text colors - we'll copy from the skin's defaults below
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
// Restore EditorStyles to their previous state (with WGroup theming if applicable)
RestoreFullStyleState(EditorStyles.textField, _savedTextField);
RestoreFullStyleState(EditorStyles.numberField, _savedNumberField);
RestoreFullStyleState(EditorStyles.objectField, _savedObjectField);
RestoreFullStyleState(EditorStyles.popup, _savedPopup);
RestoreFullStyleState(EditorStyles.helpBox, _savedHelpBox);
RestoreFullStyleState(EditorStyles.foldout, _savedFoldout);
RestoreFullStyleState(EditorStyles.label, _savedLabel);
RestoreFullStyleState(EditorStyles.toggle, _savedToggle);
RestoreFullStyleState(EditorStyles.miniButton, _savedMiniButton);
RestoreFullStyleState(EditorStyles.miniButtonLeft, _savedMiniButtonLeft);
RestoreFullStyleState(EditorStyles.miniButtonMid, _savedMiniButtonMid);
RestoreFullStyleState(EditorStyles.miniButtonRight, _savedMiniButtonRight);
// Restore WGroup context
_currentThemeState = _previousThemeState;
_isInsideWGroupPropertyDraw = _previousIsInsideWGroupPropertyDraw;
// Restore GUI colors
GUI.contentColor = _previousContentColor;
GUI.color = _previousColor;
GUI.backgroundColor = _previousBackgroundColor;
}
}
///
/// Temporarily restores WGroup GUI colors within an ExitWGroupTheming scope.
/// Used for drawing elements like foldout labels that need WGroup theming
/// because they render against the WGroup background.
///
private sealed class RestoreWGroupThemingScope : IDisposable
{
private readonly Color _savedContentColor;
private readonly Color _savedColor;
private readonly Color _savedBackgroundColor;
// Saved foldout style text colors - the foldout icon uses these
private readonly Color _savedFoldoutNormalTextColor;
private readonly Color _savedFoldoutOnNormalTextColor;
private readonly Color _savedFoldoutHoverTextColor;
private readonly Color _savedFoldoutOnHoverTextColor;
private readonly Color _savedFoldoutActiveTextColor;
private readonly Color _savedFoldoutOnActiveTextColor;
private readonly Color _savedFoldoutFocusedTextColor;
private readonly Color _savedFoldoutOnFocusedTextColor;
private bool _disposed;
internal RestoreWGroupThemingScope(WGroupSavedColors colors)
{
// Save current (non-themed) colors
_savedContentColor = GUI.contentColor;
_savedColor = GUI.color;
_savedBackgroundColor = GUI.backgroundColor;
// Save current foldout style text colors
GUIStyle foldout = EditorStyles.foldout;
_savedFoldoutNormalTextColor = foldout.normal.textColor;
_savedFoldoutOnNormalTextColor = foldout.onNormal.textColor;
_savedFoldoutHoverTextColor = foldout.hover.textColor;
_savedFoldoutOnHoverTextColor = foldout.onHover.textColor;
_savedFoldoutActiveTextColor = foldout.active.textColor;
_savedFoldoutOnActiveTextColor = foldout.onActive.textColor;
_savedFoldoutFocusedTextColor = foldout.focused.textColor;
_savedFoldoutOnFocusedTextColor = foldout.onFocused.textColor;
WGroupThemingDiagnostics.LogRestoreTheming(
"ENTER (saving non-themed)",
_savedContentColor,
_savedColor,
_savedBackgroundColor
);
// Restore WGroup themed colors
GUI.contentColor = colors.ContentColor;
GUI.color = colors.Color;
GUI.backgroundColor = colors.BackgroundColor;
// Apply WGroup text color to foldout style for icon coloring
Color textColor = colors.ContentColor;
foldout.normal.textColor = textColor;
foldout.onNormal.textColor = textColor;
foldout.hover.textColor = textColor;
foldout.onHover.textColor = textColor;
foldout.active.textColor = textColor;
foldout.onActive.textColor = textColor;
foldout.focused.textColor = textColor;
foldout.onFocused.textColor = textColor;
WGroupThemingDiagnostics.LogFoldoutStyleColors("after set", foldout);
WGroupThemingDiagnostics.LogRestoreTheming(
"ENTER (restored WGroup colors)",
GUI.contentColor,
GUI.color,
GUI.backgroundColor
);
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
WGroupThemingDiagnostics.LogRestoreTheming(
"EXIT (before restore)",
GUI.contentColor,
GUI.color,
GUI.backgroundColor
);
// Restore non-themed colors
GUI.contentColor = _savedContentColor;
GUI.color = _savedColor;
GUI.backgroundColor = _savedBackgroundColor;
// Restore foldout style text colors
GUIStyle foldout = EditorStyles.foldout;
foldout.normal.textColor = _savedFoldoutNormalTextColor;
foldout.onNormal.textColor = _savedFoldoutOnNormalTextColor;
foldout.hover.textColor = _savedFoldoutHoverTextColor;
foldout.onHover.textColor = _savedFoldoutOnHoverTextColor;
foldout.active.textColor = _savedFoldoutActiveTextColor;
foldout.onActive.textColor = _savedFoldoutOnActiveTextColor;
foldout.focused.textColor = _savedFoldoutFocusedTextColor;
foldout.onFocused.textColor = _savedFoldoutOnFocusedTextColor;
WGroupThemingDiagnostics.LogRestoreTheming(
"EXIT (after restore)",
GUI.contentColor,
GUI.color,
GUI.backgroundColor
);
}
}
private sealed class WGroupPropertyContextScope : IDisposable
{
private readonly bool _previousValue;
private bool _disposed;
internal WGroupPropertyContextScope()
{
_previousValue = _isInsideWGroupPropertyDraw;
_isInsideWGroupPropertyDraw = true;
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
_isInsideWGroupPropertyDraw = _previousValue;
}
}
internal static IDisposable PushContentPadding(float horizontalPadding)
{
return new WidthPaddingScope(horizontalPadding);
}
internal static IDisposable PushContentPadding(
float horizontalPadding,
float leftPadding,
float rightPadding
)
{
return new WidthPaddingScope(horizontalPadding, leftPadding, rightPadding);
}
internal static Rect ApplyCurrentPadding(Rect rect)
{
float leftPadding = _totalLeftPadding;
float rightPadding = _totalRightPadding;
if (leftPadding <= 0f && rightPadding <= 0f)
{
return rect;
}
Rect adjusted = rect;
adjusted.xMin += leftPadding;
adjusted.xMax -= rightPadding;
if (adjusted.width < 0f || float.IsNaN(adjusted.width))
{
adjusted.width = 0f;
}
return adjusted;
}
internal static float CalculateHorizontalPadding(GUIStyle containerStyle)
{
return CalculateHorizontalPadding(containerStyle, out _, out _);
}
internal static float CalculateHorizontalPadding(
GUIStyle containerStyle,
out float leftPadding,
out float rightPadding
)
{
leftPadding = 0f;
rightPadding = 0f;
if (containerStyle == null)
{
return 0f;
}
RectOffset padding = containerStyle.padding;
if (padding == null)
{
return 0f;
}
leftPadding = Mathf.Max(0f, padding.left);
rightPadding = Mathf.Max(0f, padding.right);
int total = padding.left + padding.right;
return Mathf.Max(0f, total);
}
}
#endif
}