From d04ec57ba7a8e9a157cda064a66eb922e3201ed7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=80=9D=E6=B5=B7?= <10001@qq.com> Date: Thu, 20 Feb 2025 10:48:57 +0800 Subject: [PATCH] init --- Editor.meta | 8 + Editor/AlicizaX.PackageManager.Editor.asmdef | 18 ++ ...AlicizaX.PackageManager.Editor.asmdef.meta | 7 + Editor/PackageManager.meta | 8 + .../PackageManager/PackageManagerCheckTool.cs | 144 ++++++++++++ .../PackageManagerCheckTool.cs.meta | 3 + .../PackageManager/RepositoryDataFetcher.cs | 204 +++++++++++++++++ .../RepositoryDataFetcher.cs.meta | 3 + Editor/PackageManager/Res.meta | 8 + .../Res/AlicizaXPackageManagerWindow.uxml | 6 + .../AlicizaXPackageManagerWindow.uxml.meta | 10 + Editor/PackageManager/Window.meta | 3 + .../Window/FrameworkPackageManagerWindow.cs | 80 +++++++ .../FrameworkPackageManagerWindow.cs.meta | 3 + .../Window/PackageBottomStateBar.cs | 44 ++++ .../Window/PackageBottomStateBar.cs.meta | 3 + .../Window/PackageInfoListItem.cs | 116 ++++++++++ .../Window/PackageInfoListItem.cs.meta | 3 + .../PackageManager/Window/PackageInfoView.cs | 206 ++++++++++++++++++ .../Window/PackageInfoView.cs.meta | 3 + .../PackageManager/Window/PackageListView.cs | 69 ++++++ .../Window/PackageListView.cs.meta | 3 + package.json | 13 ++ package.json.meta | 7 + 24 files changed, 972 insertions(+) create mode 100644 Editor.meta create mode 100644 Editor/AlicizaX.PackageManager.Editor.asmdef create mode 100644 Editor/AlicizaX.PackageManager.Editor.asmdef.meta create mode 100644 Editor/PackageManager.meta create mode 100644 Editor/PackageManager/PackageManagerCheckTool.cs create mode 100644 Editor/PackageManager/PackageManagerCheckTool.cs.meta create mode 100644 Editor/PackageManager/RepositoryDataFetcher.cs create mode 100644 Editor/PackageManager/RepositoryDataFetcher.cs.meta create mode 100644 Editor/PackageManager/Res.meta create mode 100644 Editor/PackageManager/Res/AlicizaXPackageManagerWindow.uxml create mode 100644 Editor/PackageManager/Res/AlicizaXPackageManagerWindow.uxml.meta create mode 100644 Editor/PackageManager/Window.meta create mode 100644 Editor/PackageManager/Window/FrameworkPackageManagerWindow.cs create mode 100644 Editor/PackageManager/Window/FrameworkPackageManagerWindow.cs.meta create mode 100644 Editor/PackageManager/Window/PackageBottomStateBar.cs create mode 100644 Editor/PackageManager/Window/PackageBottomStateBar.cs.meta create mode 100644 Editor/PackageManager/Window/PackageInfoListItem.cs create mode 100644 Editor/PackageManager/Window/PackageInfoListItem.cs.meta create mode 100644 Editor/PackageManager/Window/PackageInfoView.cs create mode 100644 Editor/PackageManager/Window/PackageInfoView.cs.meta create mode 100644 Editor/PackageManager/Window/PackageListView.cs create mode 100644 Editor/PackageManager/Window/PackageListView.cs.meta create mode 100644 package.json create mode 100644 package.json.meta diff --git a/Editor.meta b/Editor.meta new file mode 100644 index 0000000..03576bc --- /dev/null +++ b/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7811143a39f50c944bfc16e166522b84 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/AlicizaX.PackageManager.Editor.asmdef b/Editor/AlicizaX.PackageManager.Editor.asmdef new file mode 100644 index 0000000..65fa8c9 --- /dev/null +++ b/Editor/AlicizaX.PackageManager.Editor.asmdef @@ -0,0 +1,18 @@ +{ + "name": "AlicizaX.PackageManager.Editor", + "rootNamespace": "AlicizaX.PackageManager.Editor", + "references": [ + "GUID:478a2357cc57436488a56e564b08d223" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": false, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Editor/AlicizaX.PackageManager.Editor.asmdef.meta b/Editor/AlicizaX.PackageManager.Editor.asmdef.meta new file mode 100644 index 0000000..e55d6cd --- /dev/null +++ b/Editor/AlicizaX.PackageManager.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b2530939212d0e04b9969f32af76be2c +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/PackageManager.meta b/Editor/PackageManager.meta new file mode 100644 index 0000000..6a1a4c4 --- /dev/null +++ b/Editor/PackageManager.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 32d329a1ac6add4408e5effdee7a429d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/PackageManager/PackageManagerCheckTool.cs b/Editor/PackageManager/PackageManagerCheckTool.cs new file mode 100644 index 0000000..9c59a71 --- /dev/null +++ b/Editor/PackageManager/PackageManagerCheckTool.cs @@ -0,0 +1,144 @@ +using Newtonsoft.Json; + +namespace AlicizaX.PackageManager.Editor +{ + using System; + using System.Collections.Generic; + using System.IO; + using UnityEngine; + using UnityEditor; + using UnityEditor.PackageManager; + using UnityEditor.PackageManager.Requests; + using Newtonsoft.Json.Linq; + + public static class PackageManagerCheckTool + { + private static Dictionary cachedManifestDependencies; + private static ListRequest listRequest; + + [Serializable] + public class ManifestDependencies + { + public Dictionary dependencies = new Dictionary(); + } + + public static void ValidatePackageStates(List packages) + { + var manifest = ReadManifestFile(); + var installedPackages = GetInstalledPackagesFromPM(); + + foreach (var package in packages) + { + // 检查是否在manifest中存在 + if (manifest.dependencies.TryGetValue(package.name, out var gitUrl)) + { + // 检查PackageCache目录 + var cachePath = FindInPackageCache(package.name); + if (!string.IsNullOrEmpty(cachePath)) + { + var localTime = Directory.GetLastWriteTimeUtc(cachePath); + if (DateTime.TryParse(package.updatedAt, out var remoteTime)) + { + package.PackageState = remoteTime > localTime ? PackageState.Update : PackageState.Install; + } + else + { + package.PackageState = PackageState.Install; + } + } + else + { + package.PackageState = PackageState.Install; + } + } + else + { + // 检查Packages目录 + var packagePath = Path.GetFullPath($"Packages/{package.name}"); + package.PackageState = Directory.Exists(packagePath) ? PackageState.InstallLocal : PackageState.UnInstall; + } + } + } + + private static string FindInPackageCache(string packageName) + { + var cacheRoot = Path.GetFullPath("Library/PackageCache"); + if (!Directory.Exists(cacheRoot)) return null; + + foreach (var dir in Directory.GetDirectories(cacheRoot)) + { + if (dir.Contains(packageName + "@")) + { + return dir; + } + } + + return null; + } + + private static ManifestDependencies ReadManifestFile() + { + var manifestPath = Path.GetFullPath("Packages/manifest.json"); + var json = File.ReadAllText(manifestPath); + return JsonConvert.DeserializeObject(json); + } + + public static void InstallPackage(string gitUrl) + { + Client.Add(gitUrl); + } + + public static void UpdatePackage(string packageName) + { + Client.Add($"git:{packageName}"); + } + + public static void UninstallPackage(string packageName) + { + Client.Remove(packageName); + } + + private static List GetInstalledPackagesFromPM() + { + var packages = new List(); + listRequest = Client.List(); + + EditorApplication.CallbackFunction checkProgress = null; + checkProgress = () => + { + if (listRequest.IsCompleted) + { + if (listRequest.Status == StatusCode.Success) + { + foreach (var package in listRequest.Result) + { + packages.Add(package.name); + } + } + + EditorApplication.update -= checkProgress; + } + }; + + EditorApplication.update += checkProgress; + return packages; + } + + // 辅助方法:从Git URL提取包名 + public static string ExtractPackageNameFromUrl(string gitUrl) + { + try + { + Uri uri = new Uri(gitUrl); + string path = uri.AbsolutePath; + int startIndex = path.LastIndexOf('/') + 1; + string name = path.Substring(startIndex); + return name.Replace(".git", "").Replace("_", ".").ToLower(); + } + catch + { + return Path.GetFileNameWithoutExtension(gitUrl); + } + } + } +} diff --git a/Editor/PackageManager/PackageManagerCheckTool.cs.meta b/Editor/PackageManager/PackageManagerCheckTool.cs.meta new file mode 100644 index 0000000..bee6f74 --- /dev/null +++ b/Editor/PackageManager/PackageManagerCheckTool.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 491930ef63864219a4944a44ed820823 +timeCreated: 1740016880 \ No newline at end of file diff --git a/Editor/PackageManager/RepositoryDataFetcher.cs b/Editor/PackageManager/RepositoryDataFetcher.cs new file mode 100644 index 0000000..5a15fd1 --- /dev/null +++ b/Editor/PackageManager/RepositoryDataFetcher.cs @@ -0,0 +1,204 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Networking; +using Newtonsoft.Json; +using Unity.EditorCoroutines.Editor; +using UnityEditor; + + +namespace AlicizaX.PackageManager.Editor +{ + public enum PackageState + { + UnInstall, + InstallLocal, + Install, + Update + } + + [Serializable] + public class RepositoryPackageData + { + // Repository info + public string name; + public string description; + public string cloneUrl; + public string htmlUrl; + public string updatedAt; + + // Package info + public string displayName; + public string version; + public string unityVersion; + public string category; + public string packageDescription; + public string authorName; + public PackageState PackageState; + } + + [Serializable] + public class RepoApiResponse + { + public bool ok; + public List data; + } + + [Serializable] + public class RepoItem + { + public string name; + public string description; + public string clone_url; + public string html_url; + public string updated_at; + public Owner owner; + + [Serializable] + public class Owner + { + public string login; + } + } + + [Serializable] + public class PackageJsonResponse + { + public string name; + public string displayName; + public string category; + public string description; + public string version; + public string unity; + public Author author; + + [Serializable] + public class Author + { + public string name; + } + } + + public static class RepositoryDataFetcher + { + private const string BaseApiUrl = "http://101.34.252.46:3000/api/v1/repos/search?q=unity&topic=false"; + private const string PackageJsonPath = "/raw/branch/main/package.json"; + + + + + public static void FetchRepositoryData(Action> callback) + { + EditorCoroutineUtility.StartCoroutineOwnerless(FetchDataRoutine(callback)); + } + + private static IEnumerator FetchDataRoutine(Action> callback) + { + // 第一阶段:获取仓库列表 + using (var request = CreateWebRequest(BaseApiUrl)) + { + yield return request.SendWebRequest(); + + if (!HandleRequestError(request, "Repository request failed")) + { + callback?.Invoke(null); + yield break; + } + + var repoResponse = JsonConvert.DeserializeObject(request.downloadHandler.text); + if (!repoResponse.ok || repoResponse.data == null) + { + Debug.LogError("Invalid repository response data"); + callback?.Invoke(null); + yield break; + } + + var results = new List(); + var pendingRequests = new Queue(repoResponse.data); + + // 第二阶段:逐个获取package.json信息 + while (pendingRequests.Count > 0) + { + var repo = pendingRequests.Dequeue(); + var packageData = CreateBaseRepositoryData(repo); + + var packageUrl = repo.html_url + PackageJsonPath; + using (var packageRequest = CreateWebRequest(packageUrl)) + { + yield return packageRequest.SendWebRequest(); + + if (HandleRequestError(packageRequest, $"Package request failed for {repo.name}")) + { + try + { + var packageInfo = JsonConvert.DeserializeObject( + packageRequest.downloadHandler.text); + + UpdateWithPackageInfo(ref packageData, packageInfo); + } + catch (Exception e) + { + Debug.LogError($"JSON parse error for {repo.name}: {e.Message}"); + } + } + + results.Add(packageData); + } + + // 防止同时发起太多请求,每帧处理一个 + yield return null; + } + + callback?.Invoke(results); + } + } + + private static UnityWebRequest CreateWebRequest(string url) + { + var request = new UnityWebRequest(url, "GET"); + request.downloadHandler = new DownloadHandlerBuffer(); + request.SetRequestHeader("accept", "application/json"); + return request; + } + + private static bool HandleRequestError(UnityWebRequest request, string errorMessage) + { + if (request.result != UnityWebRequest.Result.Success) + { + Debug.LogError($"{errorMessage}: {request.error}"); + return false; + } + + return true; + } + + private static RepositoryPackageData CreateBaseRepositoryData(RepoItem repo) + { + return new RepositoryPackageData + { + name = repo.name, + description = repo.description, + cloneUrl = repo.clone_url, + htmlUrl = repo.html_url, + updatedAt = repo.updated_at + }; + } + + private static void UpdateWithPackageInfo(ref RepositoryPackageData data, PackageJsonResponse package) + { + if (package == null) return; + + data.displayName = package.displayName; + data.version = package.version; + data.unityVersion = package.unity; + data.category = package.category; + data.packageDescription = package.description; + + if (package.author != null) + { + data.authorName = package.author.name; + } + } + } +} diff --git a/Editor/PackageManager/RepositoryDataFetcher.cs.meta b/Editor/PackageManager/RepositoryDataFetcher.cs.meta new file mode 100644 index 0000000..f1d6965 --- /dev/null +++ b/Editor/PackageManager/RepositoryDataFetcher.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 311855e386934def95705701938ac3f5 +timeCreated: 1739878350 \ No newline at end of file diff --git a/Editor/PackageManager/Res.meta b/Editor/PackageManager/Res.meta new file mode 100644 index 0000000..a22d4c2 --- /dev/null +++ b/Editor/PackageManager/Res.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6c43bb4a9eaf8b147bb5e9145abda9a1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/PackageManager/Res/AlicizaXPackageManagerWindow.uxml b/Editor/PackageManager/Res/AlicizaXPackageManagerWindow.uxml new file mode 100644 index 0000000..46c026b --- /dev/null +++ b/Editor/PackageManager/Res/AlicizaXPackageManagerWindow.uxml @@ -0,0 +1,6 @@ + + + + + + diff --git a/Editor/PackageManager/Res/AlicizaXPackageManagerWindow.uxml.meta b/Editor/PackageManager/Res/AlicizaXPackageManagerWindow.uxml.meta new file mode 100644 index 0000000..437616d --- /dev/null +++ b/Editor/PackageManager/Res/AlicizaXPackageManagerWindow.uxml.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 432e048f6b943cf408f1ab3c0ef1310c +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0} diff --git a/Editor/PackageManager/Window.meta b/Editor/PackageManager/Window.meta new file mode 100644 index 0000000..c6fa9f8 --- /dev/null +++ b/Editor/PackageManager/Window.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f48ee37e4be54c94bd6a2fb80024d3d8 +timeCreated: 1739951411 \ No newline at end of file diff --git a/Editor/PackageManager/Window/FrameworkPackageManagerWindow.cs b/Editor/PackageManager/Window/FrameworkPackageManagerWindow.cs new file mode 100644 index 0000000..b5bdb1e --- /dev/null +++ b/Editor/PackageManager/Window/FrameworkPackageManagerWindow.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEngine; +using UnityEngine.UIElements; + +namespace AlicizaX.PackageManager.Editor +{ + [EditorWindowTitle(title = "Package Manager", icon = "Package Manager")] + internal class FrameworkPackageManagerWindow : EditorWindow + { + private const string VisualAssetPath = "Packages/com.alicizax.unity.packagemanager/Editor/PackageManager/Res/AlicizaXPackageManagerWindow.uxml"; + public List RepositoryPackageDatas = new List(); + + + public void RefreshPackageData(Action callBack) + { + RepositoryDataFetcher.FetchRepositoryData((datas) => + { + PackageManagerCheckTool.ValidatePackageStates(datas); + RepositoryPackageDatas = datas; + callBack?.Invoke(); + }); + } + + [MenuItem("Tools/AlicizaX/Package Manager")] + internal static void Open() + { + EditorWindow.GetWindow().Show(); + GetWindow().Refresh(); + } + + private void CreateGUI() + { + VisualTreeAsset visualTree = AssetDatabase.LoadAssetAtPath(VisualAssetPath); + visualTree.CloneTree(rootVisualElement); + CreateLeftList(); + } + + private void Refresh() + { + RefreshPackageData(() => + { + packageListView.itemsSource = RepositoryPackageDatas; + packageListView.Rebuild(); + }); + } + + private PackageListView packageListView; + private PackageInfoView packageInfoView; + + private void CreateLeftList() + { + var leftContainer = rootVisualElement.Q("LeftContainer"); + leftContainer.style.backgroundColor = new Color(0.1686275f, 0.1606275f, 0.1686275f); + + packageListView = new PackageListView(); + leftContainer.Add(packageListView); + packageListView.OnSelectionChangedEvent += PackageListViewOnOnSelectionChangedEvent; + + + leftContainer.Add(new PackageBottomStateBar()); + + var rightContainer = rootVisualElement.Q("RightContainer"); + + packageInfoView = new PackageInfoView(); + rightContainer.Add(packageInfoView); + + + packageListView.itemsSource = RepositoryPackageDatas; + } + + private void PackageListViewOnOnSelectionChangedEvent(IEnumerable obj) + { + packageInfoView.RefreshView(obj.First()); + } + } +} diff --git a/Editor/PackageManager/Window/FrameworkPackageManagerWindow.cs.meta b/Editor/PackageManager/Window/FrameworkPackageManagerWindow.cs.meta new file mode 100644 index 0000000..5e3b856 --- /dev/null +++ b/Editor/PackageManager/Window/FrameworkPackageManagerWindow.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 29f884ddb4ba425fbb082406fe4da3cf +timeCreated: 1739944505 \ No newline at end of file diff --git a/Editor/PackageManager/Window/PackageBottomStateBar.cs b/Editor/PackageManager/Window/PackageBottomStateBar.cs new file mode 100644 index 0000000..69f371d --- /dev/null +++ b/Editor/PackageManager/Window/PackageBottomStateBar.cs @@ -0,0 +1,44 @@ +using UnityEditor; +using UnityEngine; +using UnityEngine.UIElements; + +namespace AlicizaX.PackageManager.Editor +{ + internal class PackageBottomStateBar : VisualElement + { + private readonly Label stateLabel; + private readonly Button btnRefresh; + + public PackageBottomStateBar() + { + style.flexDirection = FlexDirection.Row; + style.flexShrink = 1; + style.justifyContent = Justify.FlexStart; + style.height = Length.Percent(5); + style.backgroundColor = new Color(0.2509804f, 0.2509804f, 0.2509804f); + style.borderTopWidth = 1; + + stateLabel = new Label(); + stateLabel.name = "stateLabel"; + stateLabel.text = "Last update Feb 19,13:52"; + stateLabel.style.alignSelf = Align.Center; + stateLabel.style.marginLeft = new Length(2, LengthUnit.Pixel); + stateLabel.style.flexGrow = 1; // 让 Label 占据剩余空间 + Add(stateLabel); + + btnRefresh = new Button(); + btnRefresh.name = "btnRefresh"; + btnRefresh.style.maxHeight = 30; + + Image icon = new Image(); + icon.image = EditorGUIUtility.IconContent("d_Refresh").image; + btnRefresh.Add(icon); + Add(btnRefresh); + } + + private void OnBtnRefreshClick() + { + // RepositoryDataFetcher.RefreshPackageData(null); + } + } +} diff --git a/Editor/PackageManager/Window/PackageBottomStateBar.cs.meta b/Editor/PackageManager/Window/PackageBottomStateBar.cs.meta new file mode 100644 index 0000000..4cab78d --- /dev/null +++ b/Editor/PackageManager/Window/PackageBottomStateBar.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ef4c9b64d076487cae6a6e58ab137fb4 +timeCreated: 1739950003 \ No newline at end of file diff --git a/Editor/PackageManager/Window/PackageInfoListItem.cs b/Editor/PackageManager/Window/PackageInfoListItem.cs new file mode 100644 index 0000000..36cc25f --- /dev/null +++ b/Editor/PackageManager/Window/PackageInfoListItem.cs @@ -0,0 +1,116 @@ +using UnityEditor; +using UnityEngine; +using UnityEngine.UIElements; + +namespace AlicizaX.PackageManager.Editor +{ + internal class PackageInfoListItem : VisualElement + { + private RepositoryPackageData _repositoryPackageData; + + private VisualElement m_MainItem; + private Label m_NameLabel; + private Label m_VersionLabel; + private VisualElement m_StateContainer; + private Image m_StateIcon; + private VisualElement m_LeftContainer; + private VisualElement m_RightContainer; + + public PackageInfoListItem() + { + style.backgroundColor = new Color(0.2196078f, 0.2196078f, 0.2196078f); + BuildMainItem(); + } + + public void RefreshDataSource(RepositoryPackageData packageData) + { + _repositoryPackageData = packageData; + m_NameLabel.text = packageData.displayName; + + if (packageData.PackageState == PackageState.Update) + { + m_StateIcon.image = EditorGUIUtility.IconContent("Update-Available").image; + } + else if (packageData.PackageState == PackageState.Install || packageData.PackageState == PackageState.InstallLocal) + { + m_StateIcon.image = EditorGUIUtility.IconContent("Installed").image; + } + else if (packageData.PackageState == PackageState.UnInstall) + { + m_StateIcon.image = EditorGUIUtility.IconContent("d_CreateAddNew").image; + } + + m_VersionLabel.text = packageData.PackageState == PackageState.InstallLocal ? "[Local]" : packageData.version; + } + + private void BuildMainItem() + { + style.flexShrink = 1; + style.borderBottomWidth = 1; + var borderColor = new Color(0.1372549f, 0.1372549f, 0.1372549f); + style.borderBottomColor = borderColor; + style.borderTopColor = borderColor; + style.borderLeftColor = borderColor; + style.borderRightColor = borderColor; + + m_MainItem = new VisualElement(); + m_MainItem.name = "MainItem"; + m_MainItem.style.height = Length.Percent(100); + m_MainItem.style.flexDirection = FlexDirection.Row; + m_MainItem.style.alignItems = Align.Center; + + Add(m_MainItem); + + m_LeftContainer = new VisualElement(); + m_LeftContainer.name = "leftContainer"; + + m_MainItem.Add(m_LeftContainer); + + m_NameLabel = new Label(); + m_NameLabel.name = "packageName"; + m_LeftContainer.Add((VisualElement)m_NameLabel); + m_LeftContainer.style.marginLeft = new Length(20, LengthUnit.Pixel); + m_LeftContainer.style.justifyContent = Justify.Center; + m_LeftContainer.style.flexGrow = 1; // 让 Label 占据剩余空间 + + m_VersionLabel = new Label(); + m_VersionLabel.name = "versionLabel"; + m_MainItem.Add((VisualElement)m_VersionLabel); + + m_RightContainer = new VisualElement() + { + name = "rightContainer", + }; + m_RightContainer.AddToClassList("right"); + m_MainItem.Add(m_RightContainer); + + + m_StateContainer = new VisualElement() + { + name = "statesContainer" + }; + m_MainItem.Add(m_StateContainer); + + m_StateContainer.style.justifyContent = Justify.Center; + m_StateContainer.style.marginRight = new Length(10, LengthUnit.Pixel); + + m_StateIcon = new Image() + { + name = "stateIcon", + }; + m_StateIcon.AddToClassList("status"); + m_StateContainer.Add(m_StateIcon); + } + + public void SetSelected(bool isSelected) + { + if (m_MainItem != null) + { + // 设置选中时的背景颜色为 #4D4D4D(RGB: 77,77,77),未选中时透明 + m_MainItem.style.backgroundColor = isSelected + ? new Color(77f / 255f, 77f / 255f, 77f / 255f) + : Color.clear; + } + } + } +} diff --git a/Editor/PackageManager/Window/PackageInfoListItem.cs.meta b/Editor/PackageManager/Window/PackageInfoListItem.cs.meta new file mode 100644 index 0000000..3ba9939 --- /dev/null +++ b/Editor/PackageManager/Window/PackageInfoListItem.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 45ecac0747e241e188e33b298d93dbe9 +timeCreated: 1739947858 \ No newline at end of file diff --git a/Editor/PackageManager/Window/PackageInfoView.cs b/Editor/PackageManager/Window/PackageInfoView.cs new file mode 100644 index 0000000..13795fc --- /dev/null +++ b/Editor/PackageManager/Window/PackageInfoView.cs @@ -0,0 +1,206 @@ +using UnityEngine; +using UnityEngine.UIElements; + +namespace AlicizaX.PackageManager.Editor +{ + internal class PackageInfoView : VisualElement + { + private VisualElement _mainContainer; + private VisualElement _headContainer; + private Label _displayName; + private Label _versionWithDate; + private Label _authorLabel; + private Label _packageNameLabel; + private Label _projectDescription; + private Label _btnLinkHtml; + + + private VisualElement packageBtnContainer; + + + private Button _btnUpdate; + private Button _btnInstall; + private Button _btnRemove; + + + private RepositoryPackageData _selectRepositoryPackageData; + + public PackageInfoView() + { + style.flexShrink = 1; + style.height = Length.Percent(100); + + _mainContainer = new VisualElement() + { + name = "MainContainer" + }; + _mainContainer.style.flexShrink = 1; + _mainContainer.style.height = Length.Percent(100); + + + _headContainer = new VisualElement() + { + name = "HeadContainer" + }; + _headContainer.style.flexShrink = 1; + _headContainer.style.height = Length.Percent(35); + _headContainer.style.paddingLeft = 10; + _headContainer.style.paddingTop = 10; + + _mainContainer.Add(_headContainer); + + + _displayName = new Label() + { + name = "DisplayName" + }; + _displayName.style.fontSize = 19; + _displayName.style.unityFontStyleAndWeight = FontStyle.Bold; + + _versionWithDate = new Label() + { + name = "VersionWithDate" + }; + _versionWithDate.style.fontSize = 13; + _versionWithDate.style.unityFontStyleAndWeight = FontStyle.Bold; + _versionWithDate.style.paddingTop = 5; + + + _authorLabel = new Label() + { + name = "AuthorLabel" + }; + _authorLabel.style.fontSize = 12; + _authorLabel.style.paddingTop = 5; + _authorLabel.style.unityFontStyleAndWeight = FontStyle.Italic; + + _packageNameLabel = new Label() + { + name = "PackageName" + }; + _packageNameLabel.style.fontSize = 12; + _packageNameLabel.style.paddingTop = 5; + _packageNameLabel.style.unityFontStyleAndWeight = FontStyle.Italic; + + _projectDescription = new Label() + { + name = "ProjectDescription" + }; + _projectDescription.style.fontSize = 12; + _projectDescription.style.paddingTop = 5; + + + _btnLinkHtml = new Label() + { + name = "BtnLink" + }; + + _btnLinkHtml.text = "Source Code"; + _btnLinkHtml.style.fontSize = 12; + _btnLinkHtml.style.paddingTop = 5; + _btnLinkHtml.style.marginLeft = 0; + _btnLinkHtml.style.paddingLeft = 0; + _btnLinkHtml.style.flexGrow = 0; + _btnLinkHtml.style.minWidth = new Length(20, LengthUnit.Pixel); + _btnLinkHtml.style.maxWidth = new Length(300, LengthUnit.Pixel); + _btnLinkHtml.style.color = new Color(0.2980392f, 0.498f, 1f); + _btnLinkHtml.style.backgroundColor = Color.clear; + _btnLinkHtml.RegisterCallback(OnBtnLinkClick); + + packageBtnContainer = new VisualElement() + { + name = "PackageBtnContainer" + }; + + packageBtnContainer.style.flexShrink = 1; + packageBtnContainer.style.height = Length.Percent(100); + packageBtnContainer.style.flexDirection = FlexDirection.Row; + packageBtnContainer.style.paddingTop = 10; + + + _headContainer.Add(_displayName); + _headContainer.Add(_versionWithDate); + _headContainer.Add(_authorLabel); + _headContainer.Add(_packageNameLabel); + _headContainer.Add(_projectDescription); + _headContainer.Add(_btnLinkHtml); + _headContainer.Add(packageBtnContainer); + + _btnUpdate = new Button(OnBtnUpdate) + { + name = "BtnUpdate" + }; + _btnUpdate.text = "Update"; + _btnUpdate.style.width = new Length(90, LengthUnit.Pixel); + _btnUpdate.style.height = new Length(24, LengthUnit.Pixel); + _btnUpdate.style.marginLeft = 0; + + _btnInstall = new Button(OnBtnInstall) + { + name = "BtnInstall" + }; + _btnInstall.text = "Install"; + _btnInstall.style.width = new Length(90, LengthUnit.Pixel); + _btnInstall.style.height = new Length(24, LengthUnit.Pixel); + _btnInstall.style.marginLeft = 0; + + _btnRemove = new Button(OnBtnRemove) + { + name = "BtnRemove" + }; + _btnRemove.text = "Remove"; + _btnRemove.style.width = new Length(90, LengthUnit.Pixel); + _btnRemove.style.height = new Length(24, LengthUnit.Pixel); + _btnRemove.style.marginLeft = 0; + + + + packageBtnContainer.Add(_btnInstall); + packageBtnContainer.Add(_btnUpdate); + packageBtnContainer.Add(_btnRemove); + + Add(_mainContainer); + RefreshView(_selectRepositoryPackageData); + } + + public void RefreshView(RepositoryPackageData repositoryPackageData) + { + if (repositoryPackageData == null) + { + _mainContainer.visible = false; + return; + } + + _selectRepositoryPackageData = repositoryPackageData; + + _mainContainer.visible = true; + _displayName.text = repositoryPackageData.displayName; + _versionWithDate.text = $"{repositoryPackageData.version}· {repositoryPackageData.updatedAt}"; + _authorLabel.text = $"Author: {repositoryPackageData.authorName}"; + _packageNameLabel.text = repositoryPackageData.name; + _projectDescription.text = string.IsNullOrEmpty(repositoryPackageData.description) ? "Description:TODO" : repositoryPackageData.description; + + + _btnInstall.style.display = repositoryPackageData.PackageState == PackageState.UnInstall ? DisplayStyle.Flex : DisplayStyle.None; + _btnUpdate.style.display = repositoryPackageData.PackageState == PackageState.Update ? DisplayStyle.Flex : DisplayStyle.None; + _btnRemove.style.display = repositoryPackageData.PackageState == PackageState.Update || repositoryPackageData.PackageState == PackageState.Install ? DisplayStyle.Flex : DisplayStyle.None; + } + + private void OnBtnLinkClick(ClickEvent e) + { + Application.OpenURL(_selectRepositoryPackageData.htmlUrl); + } + + private void OnBtnUpdate() + { + } + + private void OnBtnInstall() + { + } + + private void OnBtnRemove() + { + } + } +} diff --git a/Editor/PackageManager/Window/PackageInfoView.cs.meta b/Editor/PackageManager/Window/PackageInfoView.cs.meta new file mode 100644 index 0000000..a291bae --- /dev/null +++ b/Editor/PackageManager/Window/PackageInfoView.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 76b5dc77258547288eeaed68ca86a596 +timeCreated: 1739957408 \ No newline at end of file diff --git a/Editor/PackageManager/Window/PackageListView.cs b/Editor/PackageManager/Window/PackageListView.cs new file mode 100644 index 0000000..57a2d5f --- /dev/null +++ b/Editor/PackageManager/Window/PackageListView.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine.UIElements; + +namespace AlicizaX.PackageManager.Editor +{ + internal class PackageListView : ListView + { + public event Action> OnSelectionChangedEvent; + + private void OnSelectionChangedInternal(IEnumerable selectedItems) + { + // 转换对象类型为实际数据类型 + var packages = selectedItems.Cast(); + OnSelectionChangedEvent?.Invoke(packages); + + // 刷新列表项显示 + RefreshItems(); + } + + public PackageListView() + { + style.paddingTop = new Length(2, LengthUnit.Pixel); + virtualizationMethod = CollectionVirtualizationMethod.FixedHeight; + fixedItemHeight = 25f; + horizontalScrollingEnabled = false; + ScrollView scrollView = this.Q(); + scrollView.horizontalScrollerVisibility = ScrollerVisibility.Hidden; + style.flexShrink = 1; + style.height = Length.Percent(95); + makeItem = MakeItem; + bindItem = BindItem; + selectionChanged += OnSelectionChanged; + selectionChanged += OnSelectionChangedInternal; + selectionType = SelectionType.Single; + } + + private void OnSelectionChanged(IEnumerable selectedItems) + { + RefreshItems(); + } + + private VisualElement MakeItem() + { + return new PackageInfoListItem(); + } + + private void BindItem(VisualElement item, int index) + { + if (!(item is PackageInfoListItem packageItem)) + { + return; + } + + RepositoryPackageData packageData = GetVisualStateByIndex(index); + packageItem.RefreshDataSource(packageData); + + // 绑定数据后更新选中状态 + bool isSelected = selectedIndices.Contains(index); + packageItem.SetSelected(isSelected); + } + + private RepositoryPackageData GetVisualStateByIndex(int index) + { + return this.itemsSource is List itemsSource ? itemsSource.ElementAtOrDefault(index) : (RepositoryPackageData)null; + } + } +} diff --git a/Editor/PackageManager/Window/PackageListView.cs.meta b/Editor/PackageManager/Window/PackageListView.cs.meta new file mode 100644 index 0000000..f84c406 --- /dev/null +++ b/Editor/PackageManager/Window/PackageListView.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 81fd4dd929c8461db6c870bef1127deb +timeCreated: 1739952274 \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..81e400d --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "com.alicizax.unity.packagemanager", + "displayName": "Aliciza X PackageManager", + "version": "1.0.0", + "description": "A Unity Framework", + "author": { + "name": "Yuliuren", + "email": "yuliuren00@gmail.com" + }, + "dependencies": { + "com.unity.editorcoroutines": "1.0.0" + } +} \ No newline at end of file diff --git a/package.json.meta b/package.json.meta new file mode 100644 index 0000000..465ac13 --- /dev/null +++ b/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4aaa00a70a6fc2044bd0b9145d855ed7 +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: