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