This commit is contained in:
陈思海 2025-02-20 10:48:57 +08:00
commit d04ec57ba7
24 changed files with 972 additions and 0 deletions

8
Editor.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7811143a39f50c944bfc16e166522b84
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: b2530939212d0e04b9969f32af76be2c
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 32d329a1ac6add4408e5effdee7a429d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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<string, string> cachedManifestDependencies;
private static ListRequest listRequest;
[Serializable]
public class ManifestDependencies
{
public Dictionary<string, string> dependencies = new Dictionary<string, string>();
}
public static void ValidatePackageStates(List<RepositoryPackageData> 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<ManifestDependencies>(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<string> GetInstalledPackagesFromPM()
{
var packages = new List<string>();
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);
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 491930ef63864219a4944a44ed820823
timeCreated: 1740016880

View File

@ -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<RepoItem> 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<List<RepositoryPackageData>> callback)
{
EditorCoroutineUtility.StartCoroutineOwnerless(FetchDataRoutine(callback));
}
private static IEnumerator FetchDataRoutine(Action<List<RepositoryPackageData>> 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<RepoApiResponse>(request.downloadHandler.text);
if (!repoResponse.ok || repoResponse.data == null)
{
Debug.LogError("Invalid repository response data");
callback?.Invoke(null);
yield break;
}
var results = new List<RepositoryPackageData>();
var pendingRequests = new Queue<RepoItem>(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<PackageJsonResponse>(
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;
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 311855e386934def95705701938ac3f5
timeCreated: 1739878350

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6c43bb4a9eaf8b147bb5e9145abda9a1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,6 @@
<engine:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:engine="UnityEngine.UIElements" xmlns:editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
<engine:TwoPaneSplitView fixed-pane-index="0" fixed-pane-initial-dimension="250" tabindex="0">
<engine:VisualElement name="LeftContainer" />
<engine:VisualElement name="RightContainer" style="flex-grow: 1; border-left-width: 1px; border-left-color: rgb(0, 0, 0);" />
</engine:TwoPaneSplitView>
</engine:UXML>

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 432e048f6b943cf408f1ab3c0ef1310c
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f48ee37e4be54c94bd6a2fb80024d3d8
timeCreated: 1739951411

View File

@ -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<RepositoryPackageData> RepositoryPackageDatas = new List<RepositoryPackageData>();
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<FrameworkPackageManagerWindow>().Show();
GetWindow<FrameworkPackageManagerWindow>().Refresh();
}
private void CreateGUI()
{
VisualTreeAsset visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(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<VisualElement>("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<VisualElement>("RightContainer");
packageInfoView = new PackageInfoView();
rightContainer.Add(packageInfoView);
packageListView.itemsSource = RepositoryPackageDatas;
}
private void PackageListViewOnOnSelectionChangedEvent(IEnumerable<RepositoryPackageData> obj)
{
packageInfoView.RefreshView(obj.First());
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 29f884ddb4ba425fbb082406fe4da3cf
timeCreated: 1739944505

View File

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

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ef4c9b64d076487cae6a6e58ab137fb4
timeCreated: 1739950003

View File

@ -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)
{
// 设置选中时的背景颜色为 #4D4D4DRGB: 77,77,77未选中时透明
m_MainItem.style.backgroundColor = isSelected
? new Color(77f / 255f, 77f / 255f, 77f / 255f)
: Color.clear;
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 45ecac0747e241e188e33b298d93dbe9
timeCreated: 1739947858

View File

@ -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<ClickEvent>(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()
{
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 76b5dc77258547288eeaed68ca86a596
timeCreated: 1739957408

View File

@ -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<IEnumerable<RepositoryPackageData>> OnSelectionChangedEvent;
private void OnSelectionChangedInternal(IEnumerable<object> selectedItems)
{
// 转换对象类型为实际数据类型
var packages = selectedItems.Cast<RepositoryPackageData>();
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>();
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<object> 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<RepositoryPackageData> itemsSource ? itemsSource.ElementAtOrDefault<RepositoryPackageData>(index) : (RepositoryPackageData)null;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 81fd4dd929c8461db6c870bef1127deb
timeCreated: 1739952274

13
package.json Normal file
View File

@ -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"
}
}

7
package.json.meta Normal file
View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4aaa00a70a6fc2044bd0b9145d855ed7
PackageManifestImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: