// Copyright © Gamesmiths Guild. #if TOOLS using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Gamesmiths.Forge.Godot.Resources.Statescript; using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers; using Godot; namespace Gamesmiths.Forge.Godot.Editor.Statescript; public partial class StatescriptGraphNode { private readonly Dictionary _baseDropdownStyles = []; private StyleBoxFlat? _basePanelStyle; private StyleBoxFlat? _baseSelectedPanelStyle; private static bool ResolverReferencesVariable(StatescriptResolverResource? resolver, string variableName) { if (resolver is null || string.IsNullOrEmpty(variableName)) { return false; } var visited = new HashSet(); return ResolverReferencesVariableRecursive(resolver, variableName, visited); } private static bool ResolverReferencesVariableRecursive( StatescriptResolverResource resolver, string variableName, HashSet visited) { nint instanceId = (nint)resolver.GetInstanceId(); if (!visited.Add(instanceId)) { return false; } if (resolver is VariableResolverResource variableResolver && string.Equals(variableResolver.VariableName, variableName, StringComparison.Ordinal)) { return true; } foreach (PropertyInfo property in resolver.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)) { if (!property.CanRead || !typeof(StatescriptResolverResource).IsAssignableFrom(property.PropertyType)) { continue; } if (property.GetValue(resolver) is StatescriptResolverResource nestedResolver && ResolverReferencesVariableRecursive(nestedResolver, variableName, visited)) { return true; } } return false; } private static bool ResolverReferencesSharedVariable( StatescriptResolverResource? resolver, string sharedVariableSetPath, string variableName) { if (resolver is null || string.IsNullOrEmpty(sharedVariableSetPath) || string.IsNullOrEmpty(variableName)) { return false; } var visited = new HashSet(); return ResolverReferencesSharedVariableRecursive(resolver, sharedVariableSetPath, variableName, visited); } private static bool ResolverReferencesSharedVariableRecursive( StatescriptResolverResource resolver, string sharedVariableSetPath, string variableName, HashSet visited) { nint instanceId = (nint)resolver.GetInstanceId(); if (!visited.Add(instanceId)) { return false; } if (resolver is SharedVariableResolverResource sharedVariableResolver && string.Equals(sharedVariableResolver.SharedVariableSetPath, sharedVariableSetPath, StringComparison.Ordinal) && string.Equals(sharedVariableResolver.VariableName, variableName, StringComparison.Ordinal)) { return true; } foreach (PropertyInfo property in resolver.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)) { if (!property.CanRead || !typeof(StatescriptResolverResource).IsAssignableFrom(property.PropertyType)) { continue; } if (property.GetValue(resolver) is StatescriptResolverResource nestedResolver && ResolverReferencesSharedVariableRecursive( nestedResolver, sharedVariableSetPath, variableName, visited)) { return true; } } return false; } private bool ReferencesVariable(string variableName) { if (NodeResource is null) { return false; } return NodeResource.PropertyBindings.Any(binding => ResolverReferencesVariable(binding.Resolver, variableName)); } private bool ReferencesSharedVariable(string? sharedVariableSetPath, string? variableName) { if (NodeResource is null || string.IsNullOrEmpty(sharedVariableSetPath) || string.IsNullOrEmpty(variableName)) { return false; } return NodeResource.PropertyBindings.Any(binding => ResolverReferencesSharedVariable(binding.Resolver, sharedVariableSetPath, variableName)); } private void ApplyHighlightBorder() { EnsureBasePanelStylesStored(); if (_basePanelStyle is null || _baseSelectedPanelStyle is null) { return; } if (_isHighlighted) { StyleBoxFlat baseStyle = _basePanelStyle; var highlightStyle = (StyleBoxFlat)baseStyle.Duplicate(); highlightStyle.BorderColor = _highlightColor; highlightStyle.BorderWidthTop = 2; highlightStyle.BorderWidthBottom = 2; highlightStyle.BorderWidthLeft = 2; highlightStyle.BorderWidthRight = 2; highlightStyle.BgColor = baseStyle.BgColor.Lerp(_highlightColor, 0.15f); AddThemeStyleboxOverride("panel", highlightStyle); AddThemeStyleboxOverride("panel_selected", highlightStyle); } else { AddThemeStyleboxOverride("panel", (StyleBoxFlat)_basePanelStyle.Duplicate()); AddThemeStyleboxOverride("panel_selected", (StyleBoxFlat)_baseSelectedPanelStyle.Duplicate()); } } private void UpdateChildHighlights() { UpdateHighlightsRecursive(this); } private void UpdateHighlightsRecursive(Node parent) { foreach (Node child in parent.GetChildren()) { if (child is OptionButton optionButton) { HighlightOptionButtonIfMatches(optionButton); } else if (child is Label label) { HighlightLabelIfMatches(label); } else if (child is FoldableContainer foldable) { HighlightFoldableSummaryBadgeIfPresent(foldable); } else if (child is PanelContainer badge && badge.HasMeta("forge_inline_summary_badge_kind")) { HighlightSummaryBadgeIfMatches(badge); } UpdateHighlightsRecursive(child); } } private void HighlightFoldableSummaryBadgeIfPresent(FoldableContainer foldable) { if (!InlineConstantSummaryFormatter.TryGetSummaryBadgeForHighlighting(foldable, out PanelContainer? badge)) { return; } HighlightSummaryBadgeIfMatches(badge); } private void HighlightOptionButtonIfMatches(OptionButton dropdown) { EnsureBaseDropdownStyleStored(dropdown); if (!dropdown.HasMeta("is_variable_dropdown") && !dropdown.HasMeta("is_shared_variable_dropdown")) { return; } if (!_baseDropdownStyles.TryGetValue(dropdown, out StyleBoxFlat? baseStyle)) { return; } if (string.IsNullOrEmpty(_highlightedVariableName) && (string.IsNullOrEmpty(_highlightedSharedVariableSetPath) || string.IsNullOrEmpty(_highlightedSharedVariableName))) { dropdown.AddThemeStyleboxOverride("normal", (StyleBoxFlat)baseStyle.Duplicate()); return; } int selectedIdx = dropdown.Selected; if (selectedIdx < 0) { dropdown.AddThemeStyleboxOverride("normal", (StyleBoxFlat)baseStyle.Duplicate()); return; } string selectedText = dropdown.GetItemText(selectedIdx); bool isSharedVariableDropdown = dropdown.HasMeta("is_shared_variable_dropdown"); string dropdownSetPath = dropdown.HasMeta("shared_variable_set_path") ? dropdown.GetMeta("shared_variable_set_path").AsString() : string.Empty; bool isMatch = isSharedVariableDropdown ? !string.IsNullOrEmpty(_highlightedSharedVariableSetPath) && !string.IsNullOrEmpty(_highlightedSharedVariableName) && selectedText == _highlightedSharedVariableName && dropdownSetPath == _highlightedSharedVariableSetPath : selectedText == _highlightedVariableName; if (isMatch) { var highlightStyle = (StyleBoxFlat)baseStyle.Duplicate(); highlightStyle.BgColor = baseStyle.BgColor.Lerp(_highlightColor, 0.25f); dropdown.AddThemeStyleboxOverride("normal", highlightStyle); } else { dropdown.AddThemeStyleboxOverride("normal", (StyleBoxFlat)baseStyle.Duplicate()); } } private void EnsureBasePanelStylesStored() { if (_basePanelStyle is null && GetThemeStylebox("panel") is StyleBoxFlat panelStyle) { _basePanelStyle = (StyleBoxFlat)panelStyle.Duplicate(); } if (_baseSelectedPanelStyle is null && GetThemeStylebox("panel_selected") is StyleBoxFlat selectedPanelStyle) { _baseSelectedPanelStyle = (StyleBoxFlat)selectedPanelStyle.Duplicate(); } } private void EnsureBaseDropdownStyleStored(OptionButton dropdown) { if (_baseDropdownStyles.ContainsKey(dropdown)) { return; } if (dropdown.GetThemeStylebox("normal") is StyleBoxFlat baseStyle) { _baseDropdownStyles[dropdown] = (StyleBoxFlat)baseStyle.Duplicate(); } } private void HighlightLabelIfMatches(Label label) { if (string.IsNullOrEmpty(_highlightedVariableName)) { if (label.HasMeta("is_highlight_colored")) { label.RemoveThemeColorOverride("font_color"); label.RemoveMeta("is_highlight_colored"); } return; } if (label.Text == _highlightedVariableName) { label.AddThemeColorOverride("font_color", _highlightColor); label.SetMeta("is_highlight_colored", true); } else if (label.HasMeta("is_highlight_colored")) { label.RemoveThemeColorOverride("font_color"); label.RemoveMeta("is_highlight_colored"); } } private void HighlightSummaryBadgeIfMatches(PanelContainer badge) { if (badge.GetNodeOrNull