using System.Collections.Generic; using System.Text.RegularExpressions; using UnityEditor; using UnityEngine; namespace Amanotes { internal class FlagTab { private Core.AmaDevSettings settings; private Core.LocalSymbolOverrides localOverrides; private bool needsRefresh = true; private Vector2 scrollPos; private bool symbolsExpanded = true; private bool platformExpanded; private bool loggerTagsExpanded = true; private readonly List cscSymbols = new List(); private readonly List psSymbols = new List(); private readonly Color rowBg = new Color(0.22f, 0.22f, 0.22f, 1f); private readonly Color headerBg = new Color(0.19f, 0.19f, 0.19f, 1f); private readonly Color separatorColor = new Color(0.1f, 0.1f, 0.1f, 1f); private readonly Color dotColor = new Color(1f, 0.6f, 0.2f, 1f); private readonly Color psColor = new Color(0.5f, 0.7f, 1f, 0.7f); private GUIStyle _sRowLabel, _sHeader, _sSmallBtn, _sDimLabel, _sDot, _sBadge; private GUIStyle sRowLabel => _sRowLabel ??= new GUIStyle(EditorStyles.label) { alignment = TextAnchor.MiddleLeft, padding = new RectOffset(4, 0, 0, 0) }; private GUIStyle sDimLabel => _sDimLabel ??= new GUIStyle(EditorStyles.label) { alignment = TextAnchor.MiddleLeft, padding = new RectOffset(4, 0, 0, 0), normal = { textColor = new Color(0.55f, 0.55f, 0.55f) } }; private GUIStyle sHeader { get { if (_sHeader != null) return _sHeader; _sHeader = new GUIStyle(EditorStyles.foldout) { fontSize = 14 }; _sHeader.normal.textColor = _sHeader.onNormal.textColor = _sHeader.focused.textColor = _sHeader.onFocused.textColor = _sHeader.active.textColor = _sHeader.onActive.textColor = _sHeader.hover.textColor = _sHeader.onHover.textColor = Color.white; return _sHeader; } } private GUIStyle sSmallBtn { get { if (_sSmallBtn != null) return _sSmallBtn; _sSmallBtn = new GUIStyle(GUI.skin.button) { fontSize = 9, alignment = TextAnchor.MiddleCenter, padding = new RectOffset(4, 4, 0, 0), margin = new RectOffset(0, 0, 0, 0), overflow = new RectOffset(0, 0, 0, 0) }; return _sSmallBtn; } } private GUIStyle sBadge => _sBadge ??= new GUIStyle(EditorStyles.miniLabel) { fontSize = 8, alignment = TextAnchor.MiddleCenter, normal = { textColor = psColor } }; internal void Refresh() => needsRefresh = true; private void EnsureData() { if (!needsRefresh && settings != null) return; needsRefresh = false; settings = Core.AmaDevSettings.Instance; localOverrides = Core.LocalSymbolOverrides.Load(); if (settings != null) settings.ReadSymbols(); } internal void OnGUI() { EnsureData(); if (settings == null) { EditorGUILayout.HelpBox("No AmaDevSettings asset found.", MessageType.Info); if (GUILayout.Button("Create AmaDevSettings", GUILayout.Height(30))) { settings = Core.AmaDevSettings.GetOrCreate(); settings.ReadSymbols(); needsRefresh = true; } return; } if (EditorApplication.isCompiling) { EditorGUILayout.HelpBox("Compiling...", MessageType.Info); return; } if (EditorApplication.isPlayingOrWillChangePlaymode) { EditorGUILayout.HelpBox("Disabled in play mode.", MessageType.Info); return; } scrollPos = EditorGUILayout.BeginScrollView(scrollPos); cscSymbols.Clear(); psSymbols.Clear(); for (int i = 0; i < settings.symbolDefine.symbols.Count; i++) { var sym = settings.symbolDefine.symbols[i]; if (sym.source == Core.AmaDevSettings.SymbolSource.PlayerSettings) psSymbols.Add(sym); else cscSymbols.Add(sym); } DrawSymbolsList(cscSymbols, "Symbols", ref symbolsExpanded, true); if (settings.logV2.enabled && settings.logV2.tags.Count > 0) { GUILayout.Space(4); DrawLoggerTagsReadOnly(); } if (psSymbols.Count > 0) { GUILayout.Space(4); DrawSymbolsList(psSymbols, "Platform Defines", ref platformExpanded, false); } EditorGUILayout.EndScrollView(); GUILayout.FlexibleSpace(); DrawFooter(); } private void DrawSymbolsList(List symbols, string label, ref bool expanded, bool allowAdd) { bool isLogger = !allowAdd; var headerRect = GUILayoutUtility.GetRect(0, 26f, GUILayout.ExpandWidth(true)); EditorGUI.DrawRect(headerRect, headerBg); EditorGUI.DrawRect(new Rect(headerRect.x, headerRect.y, headerRect.width, 1), separatorColor); const float btnW = 36f; const float btnH = 16f; float bx = headerRect.xMax - 6; if (isLogger) { bx -= btnW; if (GUI.Button(new Rect(bx, headerRect.y + (26f - btnH) * 0.5f, btnW, btnH), "None", sSmallBtn)) SetAll(symbols, false); bx -= btnW + 2; if (GUI.Button(new Rect(bx, headerRect.y + (26f - btnH) * 0.5f, btnW, btnH), "All", sSmallBtn)) SetAll(symbols, true); } if (allowAdd) { const float addW = 20f; bx -= addW; if (GUI.Button(new Rect(bx, headerRect.y + (26f - btnH) * 0.5f, addW, btnH), "+", sSmallBtn)) AddSymbolDialog(); } var foldW = bx - headerRect.x - 8; expanded = EditorGUI.Foldout(new Rect(headerRect.x + 4, headerRect.y, foldW, headerRect.height), expanded, $"{label} ({symbols.Count})", true, sHeader); if (!expanded) return; for (int i = 0; i < symbols.Count; i++) DrawSymbolRow(symbols[i], allowAdd); } private void DrawSymbolRow(Core.AmaDevSettings.SymbolInfo symbol, bool allowRemove) { const float rowH = 24f; const float toggleW = 36f; const float tagH = 14f; const float pad = 4f; const float dotW = 12f; const float closeW = 14f; bool isPS = symbol.source == Core.AmaDevSettings.SymbolSource.PlayerSettings; bool hasOverride = localOverrides.HasOverride(symbol.name); bool effectiveEnabled = hasOverride ? localOverrides.GetOverrideEnabled(symbol.name) : symbol.enabled; var rect = GUILayoutUtility.GetRect(0, rowH, GUILayout.ExpandWidth(true)); if (Event.current.type == EventType.Repaint) { EditorGUI.DrawRect(rect, rowBg); EditorGUI.DrawRect(new Rect(rect.x, rect.yMax - 1, rect.width, 1), new Color(0.18f, 0.18f, 0.18f, 0.5f)); } float x = rect.x + pad; if (allowRemove && !isPS) { var oldCloseColor = GUI.color; GUI.color = new Color(1f, 1f, 1f, 0.3f); if (GUI.Button(new Rect(x, rect.y, closeW, rowH), "\u00D7", EditorStyles.label)) { settings.symbolDefine.RemoveSymbol(symbol.name); localOverrides.RemoveOverride(symbol.name); EditorUtility.SetDirty(settings); return; } GUI.color = oldCloseColor; x += closeW; } else { x += closeW; } if (hasOverride) { var dotRect = new Rect(x, rect.y + (rowH - dotW) * 0.5f, dotW, dotW); if (Event.current.type == EventType.Repaint) { var oldColor = GUI.color; GUI.color = dotColor; GUI.Label(dotRect, new GUIContent("\u25CF", "Local override. Click to remove.")); GUI.color = oldColor; } if (GUI.Button(dotRect, GUIContent.none, GUIStyle.none)) { localOverrides.RemoveOverride(symbol.name); needsRefresh = true; } x += dotW; } else { x += dotW; } float rx = rect.xMax - pad - toggleW; if (isPS) { var badgeRect = new Rect(rx, rect.y + (rowH - tagH) * 0.5f, toggleW, tagH); if (Event.current.type == EventType.Repaint) sBadge.Draw(badgeRect, new GUIContent("PS"), false, false, false, false); } else { var btnRect = new Rect(rx, rect.y + (rowH - tagH - 2) * 0.5f, toggleW, tagH + 2); var oldBg = GUI.backgroundColor; GUI.backgroundColor = effectiveEnabled ? new Color(0.4f, 0.9f, 0.4f, 1f) : new Color(0.5f, 0.5f, 0.5f, 0.5f); string btnLabel = effectiveEnabled ? "ON" : "OFF"; string tooltip = Event.current.alt ? "Alt+Click: override locally" : ""; if (GUI.Button(btnRect, new GUIContent(btnLabel, tooltip), sSmallBtn)) { if (Event.current.alt) { localOverrides.SetOverride(symbol.name, !effectiveEnabled); needsRefresh = true; } else { if (hasOverride) { localOverrides.SetOverride(symbol.name, !effectiveEnabled); needsRefresh = true; } else { symbol.enabled = !symbol.enabled; EditorUtility.SetDirty(settings); } } } GUI.backgroundColor = oldBg; } float nameW = rx - x - pad; var nameRect = new Rect(x, rect.y, nameW, rowH); if (Event.current.type == EventType.Repaint) { var style = effectiveEnabled ? sRowLabel : sDimLabel; style.Draw(nameRect, new GUIContent(symbol.name), false, false, false, false); } } private void DrawLoggerTagsReadOnly() { var tags = settings.logV2.tags; var headerRect = GUILayoutUtility.GetRect(0, 26f, GUILayout.ExpandWidth(true)); EditorGUI.DrawRect(headerRect, headerBg); EditorGUI.DrawRect(new Rect(headerRect.x, headerRect.y, headerRect.width, 1), separatorColor); loggerTagsExpanded = EditorGUI.Foldout( new Rect(headerRect.x + 4, headerRect.y, headerRect.width - 8, headerRect.height), loggerTagsExpanded, $"Logger Tags ({tags.Count})", true, sHeader); if (!loggerTagsExpanded) return; for (int i = 0; i < tags.Count; i++) { const float rowH = 24f; const float toggleW = 36f; const float tagH = 14f; const float pad = 4f; var tag = tags[i]; var rect = GUILayoutUtility.GetRect(0, rowH, GUILayout.ExpandWidth(true)); if (Event.current.type == EventType.Repaint) { EditorGUI.DrawRect(rect, rowBg); EditorGUI.DrawRect(new Rect(rect.x, rect.yMax - 1, rect.width, 1), new Color(0.18f, 0.18f, 0.18f, 0.5f)); } float rx = rect.xMax - pad - toggleW; var btnRect = new Rect(rx, rect.y + (rowH - tagH - 2) * 0.5f, toggleW, tagH + 2); var oldBg = GUI.backgroundColor; GUI.backgroundColor = tag.enabled ? new Color(0.4f, 0.9f, 0.4f, 1f) : new Color(0.5f, 0.5f, 0.5f, 0.5f); if (GUI.Button(btnRect, tag.enabled ? "ON" : "OFF", sSmallBtn)) { tag.enabled = !tag.enabled; EditorUtility.SetDirty(settings); } GUI.backgroundColor = oldBg; float nameW = rx - rect.x - pad * 2 - 4; var nameRect = new Rect(rect.x + pad + 4, rect.y, nameW, rowH); if (Event.current.type == EventType.Repaint) (tag.enabled ? sRowLabel : sDimLabel).Draw(nameRect, new GUIContent(tag.name), false, false, false, false); } } private void DrawFooter() { var sep = GUILayoutUtility.GetRect(0, 1, GUILayout.ExpandWidth(true)); EditorGUI.DrawRect(sep, new Color(0.3f, 0.3f, 0.3f, 1f)); GUILayout.BeginHorizontal(); if (GUILayout.Button("Refresh", GUILayout.Height(26))) needsRefresh = true; var oldBg = GUI.backgroundColor; GUI.backgroundColor = new Color(0.3f, 0.7f, 1f, 1f); if (GUILayout.Button("Apply", GUILayout.Height(26))) { settings.Apply(); needsRefresh = true; } GUI.backgroundColor = oldBg; GUILayout.EndHorizontal(); GUILayout.Space(2); } private void SetAll(List symbols, bool enabled) { for (int i = 0; i < symbols.Count; i++) symbols[i].enabled = enabled; EditorUtility.SetDirty(settings); } private void AddSymbolDialog() { var name = EditorInputDialog.Show("Add Symbol", "Enter symbol name (e.g. MY_FEATURE):", ""); if (string.IsNullOrEmpty(name)) return; name = name.Trim().ToUpperInvariant(); if (!IsValidSymbolName(name)) { EditorUtility.DisplayDialog("Invalid Symbol", "Symbol must be 2+ chars: uppercase letters, digits, underscores. No leading digit.", "OK"); return; } settings.symbolDefine.AddNewSymbol(name); EditorUtility.SetDirty(settings); needsRefresh = true; } private static bool IsValidSymbolName(string name) { if (name.Length < 2) return false; return Regex.IsMatch(name, @"^[A-Z_][A-Z0-9_]+$"); } } }