using System.Collections.Generic; using UnityEditor; using UnityEngine; namespace Amanotes { /// /// Draws the "Local Packages" group in the Packages tab. /// Shows packages from the project's LocalPackages/ folder with enable/disable toggles. /// internal static class LocalPackagesTab { private static List packages; private static bool needsRefresh = true; private static bool expanded = true; private static string localPackagesRoot; /// Called when a local package is enabled/disabled so the packages tab can refresh. internal static System.Action onPackageChanged; const float kPad = 4f; const float kToggleW = 40f; const float kVerW = 48f; const float kRowH = 24f; const float kInfoH = 16f; const float kRowGap = 6f; internal static void Refresh() => needsRefresh = true; internal static bool HasLocalPackages() { EnsureData(); return packages != null && packages.Count > 0; } /// /// Returns the set of package names that are local packages, /// so DevModeTab can exclude them from its list. /// internal static HashSet GetLocalPackageNames() { EnsureData(); var set = new HashSet(); if (packages == null) return set; for (int i = 0; i < packages.Count; i++) set.Add(packages[i].packageName); return set; } private static void EnsureData() { if (!needsRefresh && packages != null) return; needsRefresh = false; packages = LocalPackagesManager.DetectLocalPackages(); localPackagesRoot = LocalPackagesManager.FindLocalPackagesRoot(); } internal static void OnGUI() { EnsureData(); if (packages == null || packages.Count == 0) return; int enabledCount = 0; for (int i = 0; i < packages.Count; i++) if (packages[i].enabled) enabledCount++; expanded = EditorGUILayout.Foldout(expanded, $"Local Packages ({enabledCount}/{packages.Count})", true, sFoldout); if (!expanded) return; for (int i = 0; i < packages.Count; i++) DrawRow(packages[i]); // Separator after local packages section GUILayout.Space(4); var sep = GUILayoutUtility.GetRect(0, 2, GUILayout.ExpandWidth(true)); EditorGUI.DrawRect(sep, new Color(0.25f, 0.5f, 0.25f, 0.5f)); GUILayout.Space(4); } private static void DrawRow(LocalPackagesManager.LocalPackageInfo pkg) { float totalW = EditorGUIUtility.currentViewWidth; float nameW = totalW - kPad * 3 - kToggleW - kVerW - kPad; if (nameW < 40) nameW = 40; EditorGUILayout.BeginHorizontal(GUILayout.Height(kRowH)); GUILayout.Space(kPad + 10); // indent under foldout // Package name var displayName = !string.IsNullOrEmpty(pkg.displayName) ? pkg.displayName : pkg.folderName; var nameRect = GUILayoutUtility.GetRect(nameW, kRowH, GUILayout.Width(nameW), GUILayout.Height(kRowH)); if (Event.current.type == EventType.Repaint) { var style = pkg.enabled ? sName : sNameDisabled; style.Draw(nameRect, new GUIContent(displayName), false, false, false, false); } GUILayout.FlexibleSpace(); // Version var verRect = GUILayoutUtility.GetRect(kVerW - 2, kRowH, GUILayout.Width(kVerW - 2), GUILayout.Height(kRowH)); if (Event.current.type == EventType.Repaint) sDim.Draw(verRect, new GUIContent(pkg.version), false, false, false, false); // Enable/Disable toggle button var color = pkg.enabled ? new Color(0.4f, 0.9f, 0.4f, 1f) : new Color(0.9f, 0.4f, 0.4f, 0.7f); var label = pkg.enabled ? "ON" : "OFF"; var oldBg = GUI.backgroundColor; GUI.backgroundColor = color; if (GUILayout.Button(label, sToggleBtn, GUILayout.Width(kToggleW), GUILayout.Height(kRowH - 2))) { if (pkg.enabled) { if (EditorUtility.DisplayDialog("Disable Package", $"Disable {pkg.displayName}?\n\nThis will rename package.json to .bak and remove it from manifest.json.", "Disable", "Cancel")) { LocalPackagesManager.DisablePackage(pkg); needsRefresh = true; onPackageChanged?.Invoke(); } } else { LocalPackagesManager.EnablePackage(pkg); needsRefresh = true; onPackageChanged?.Invoke(); } } GUI.backgroundColor = oldBg; GUILayout.Space(kPad); EditorGUILayout.EndHorizontal(); // Info line: folder path EditorGUILayout.BeginHorizontal(GUILayout.Height(kInfoH)); GUILayout.Space(24); var pathDisplay = pkg.folderName; if (Event.current.type == EventType.Repaint) { float avail = totalW - 28; var pathRect = GUILayoutUtility.GetRect(avail, kInfoH, GUILayout.Height(kInfoH)); sPath.Draw(pathRect, new GUIContent($"LocalPackages/{pathDisplay}"), false, false, false, false); } else { GUILayoutUtility.GetRect(totalW - 28, kInfoH, GUILayout.Height(kInfoH)); } EditorGUILayout.EndHorizontal(); GUILayout.Space(kRowGap); var sep = GUILayoutUtility.GetRect(0, 1, GUILayout.ExpandWidth(true)); EditorGUI.DrawRect(sep, new Color(0.15f, 0.15f, 0.15f, 1f)); GUILayout.Space(kRowGap); } // ── Styles ──────────────────────────────────────────────────────── private static GUIStyle _sFoldout, _sName, _sNameDisabled, _sDim, _sPath, _sToggleBtn; private static GUIStyle sFoldout => _sFoldout ??= new GUIStyle(EditorStyles.foldout) { fontStyle = FontStyle.Bold }; private static GUIStyle sName => _sName ??= new GUIStyle(EditorStyles.label) { clipping = TextClipping.Clip, wordWrap = false, normal = { textColor = Color.white }, hover = { textColor = Color.white } }; private static GUIStyle sNameDisabled => _sNameDisabled ??= new GUIStyle(EditorStyles.label) { clipping = TextClipping.Clip, wordWrap = false, normal = { textColor = new Color(0.5f, 0.5f, 0.5f) }, hover = { textColor = new Color(0.5f, 0.5f, 0.5f) } }; private static GUIStyle sDim => _sDim ??= new GUIStyle(EditorStyles.miniLabel) { alignment = TextAnchor.MiddleRight, normal = { textColor = new Color(0.5f, 0.5f, 0.5f) }, hover = { textColor = new Color(0.5f, 0.5f, 0.5f) } }; private static GUIStyle sPath => _sPath ??= new GUIStyle(EditorStyles.miniLabel) { wordWrap = false, normal = { textColor = new Color(0.5f, 0.5f, 0.5f) }, hover = { textColor = new Color(0.5f, 0.5f, 0.5f) } }; private static GUIStyle sToggleBtn { get { if (_sToggleBtn != null) return _sToggleBtn; _sToggleBtn = new GUIStyle(GUI.skin.button) { fontStyle = FontStyle.Bold, fontSize = 10, alignment = TextAnchor.MiddleCenter, normal = { textColor = new Color(1f, 1f, 1f, 0.5f) }, hover = { textColor = new Color(1f, 1f, 1f, 0.5f) }, active = { textColor = new Color(1f, 1f, 1f, 0.5f) }, padding = new RectOffset(2, 2, 2, 2), margin = new RectOffset(2, 0, 1, 1) }; return _sToggleBtn; } } } }