You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

626 lines
25 KiB

  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using Semver;
  4. using UnityEngine;
  5. using UnityEngine.Experimental.UIElements;
  6. namespace UnityEditor.PackageManager.UI
  7. {
  8. #if !UNITY_2018_3_OR_NEWER
  9. internal class PackageDetailsFactory : UxmlFactory<PackageDetails>
  10. {
  11. protected override PackageDetails DoCreate(IUxmlAttributes bag, CreationContext cc)
  12. {
  13. return new PackageDetails();
  14. }
  15. }
  16. #endif
  17. internal class PackageDetails : VisualElement
  18. {
  19. #if UNITY_2018_3_OR_NEWER
  20. internal new class UxmlFactory : UxmlFactory<PackageDetails> { }
  21. #endif
  22. private readonly VisualElement root;
  23. private Package package;
  24. private const string emptyDescriptionClass = "empty";
  25. private List<VersionItem> VersionItems;
  26. internal PopupField<VersionItem> VersionPopup;
  27. private PackageInfo DisplayPackage;
  28. private PackageInfo SelectedPackage
  29. {
  30. get { return VersionPopup.value.Version != null ? VersionPopup.value.Version : null; }
  31. }
  32. internal enum PackageAction
  33. {
  34. Add,
  35. Remove,
  36. Update,
  37. Downgrade,
  38. Enable,
  39. Disable,
  40. UpToDate,
  41. Current,
  42. Local,
  43. Git,
  44. Embedded
  45. }
  46. private static readonly VersionItem EmptyVersion = new VersionItem {Version = null};
  47. internal static readonly string[] PackageActionVerbs = { "Install", "Remove", "Update to", "Update to", "Enable", "Disable", "Up to date", "Current", "Local", "Git", "Embedded" };
  48. internal static readonly string[] PackageActionInProgressVerbs = { "Installing", "Removing", "Updating to", "Updating to", "Enabling", "Disabling", "Up to date", "Current", "Local", "Git", "Embedded" };
  49. public PackageDetails()
  50. {
  51. UIUtils.SetElementDisplay(this, false);
  52. root = Resources.GetTemplate("PackageDetails.uxml");
  53. Add(root);
  54. foreach (var extension in PackageManagerExtensions.Extensions)
  55. CustomContainer.Add(extension.CreateExtensionUI());
  56. root.StretchToParentSize();
  57. SetUpdateVisibility(false);
  58. RemoveButton.visible = false;
  59. UpdateBuiltIn.visible = false;
  60. UpdateButton.clickable.clicked += UpdateClick;
  61. UpdateBuiltIn.clickable.clicked += UpdateClick;
  62. RemoveButton.clickable.clicked += RemoveClick;
  63. ViewDocButton.clickable.clicked += ViewDocClick;
  64. ViewChangelogButton.clickable.clicked += ViewChangelogClick;
  65. ViewLicenses.clickable.clicked += ViewLicensesClick;
  66. VersionItems = new List<VersionItem> {EmptyVersion};
  67. VersionPopup = new PopupField<VersionItem>(VersionItems, 0);
  68. VersionPopup.SetLabelCallback(VersionSelectionSetLabel);
  69. VersionPopup.AddToClassList("popup");
  70. VersionPopup.OnValueChanged(VersionSelectionChanged);
  71. if (VersionItems.Count == 1)
  72. VersionPopup.SetEnabled(false);
  73. UpdateDropdownContainer.Add(VersionPopup);
  74. VersionPopup.StretchToParentSize();
  75. // Fix button on dark skin but overlapping edge pixel perfectly
  76. if (EditorGUIUtility.isProSkin)
  77. {
  78. VersionPopup.style.positionLeft = -1;
  79. UpdateDropdownContainer.style.sliceLeft = 4;
  80. }
  81. }
  82. private string VersionSelectionSetLabel(VersionItem item)
  83. {
  84. return item.Label;
  85. }
  86. private void VersionSelectionChanged(ChangeEvent<VersionItem> e)
  87. {
  88. RefreshAddButton();
  89. }
  90. private void SetUpdateVisibility(bool value)
  91. {
  92. if (UpdateContainer != null)
  93. UIUtils.SetElementDisplay(UpdateContainer, value);
  94. }
  95. internal void SetDisplayPackage(PackageInfo packageInfo)
  96. {
  97. DisplayPackage = packageInfo;
  98. var detailVisible = true;
  99. Error error = null;
  100. if (package == null || DisplayPackage == null)
  101. {
  102. detailVisible = false;
  103. UIUtils.SetElementDisplay(DocumentationContainer, false);
  104. UIUtils.SetElementDisplay(CustomContainer, false);
  105. UIUtils.SetElementDisplay(UpdateBuiltIn, false);
  106. foreach (var extension in PackageManagerExtensions.Extensions)
  107. extension.OnPackageSelectionChange(null);
  108. }
  109. else
  110. {
  111. SetUpdateVisibility(true);
  112. UIUtils.SetElementDisplay(ViewDocButton, true);
  113. RemoveButton.visible = true;
  114. if (string.IsNullOrEmpty(DisplayPackage.Description))
  115. {
  116. DetailDesc.text = "There is no description for this package.";
  117. DetailDesc.AddToClassList(emptyDescriptionClass);
  118. }
  119. else
  120. {
  121. DetailDesc.text = DisplayPackage.Description;
  122. DetailDesc.RemoveFromClassList(emptyDescriptionClass);
  123. }
  124. root.Q<Label>("detailTitle").text = DisplayPackage.DisplayName;
  125. DetailVersion.text = "Version " + DisplayPackage.VersionWithoutTag;
  126. if (DisplayPackage.IsInDevelopment || DisplayPackage.HasVersionTag(PackageTag.preview))
  127. UIUtils.SetElementDisplay(GetTag(PackageTag.verified), false);
  128. else
  129. {
  130. var unityVersionParts = Application.unityVersion.Split('.');
  131. var unityVersion = string.Format("{0}.{1}", unityVersionParts[0], unityVersionParts[1]);
  132. VerifyLabel.text = unityVersion + " verified";
  133. UIUtils.SetElementDisplay(GetTag(PackageTag.verified), DisplayPackage.IsVerified);
  134. }
  135. UIUtils.SetElementDisplay(GetTag(PackageTag.inDevelopment), DisplayPackage.IsInDevelopment);
  136. UIUtils.SetElementDisplay(GetTag(PackageTag.local), DisplayPackage.IsLocal);
  137. UIUtils.SetElementDisplay(GetTag(PackageTag.preview), DisplayPackage.IsPreview);
  138. UIUtils.SetElementDisplay(DocumentationContainer, DisplayPackage.Origin != PackageSource.BuiltIn);
  139. UIUtils.SetElementDisplay(ChangelogContainer, DisplayPackage.HasChangelog(DisplayPackage));
  140. root.Q<Label>("detailName").text = DisplayPackage.Name;
  141. root.Q<ScrollView>("detailView").scrollOffset = new Vector2(0, 0);
  142. DetailModuleReference.text = "";
  143. var isBuiltIn = DisplayPackage.IsBuiltIn;
  144. if (isBuiltIn)
  145. DetailModuleReference.text = DisplayPackage.BuiltInDescription;
  146. DetailAuthor.text = "";
  147. if (!string.IsNullOrEmpty(DisplayPackage.Author))
  148. DetailAuthor.text = string.Format("Author: {0}", DisplayPackage.Author);
  149. UIUtils.SetElementDisplay(DetailDesc, !isBuiltIn);
  150. UIUtils.SetElementDisplay(DetailVersion, !isBuiltIn);
  151. UIUtils.SetElementDisplayNonEmpty(DetailModuleReference);
  152. UIUtils.SetElementDisplayNonEmpty(DetailAuthor);
  153. if (DisplayPackage.Errors.Count > 0)
  154. error = DisplayPackage.Errors.First();
  155. RefreshAddButton();
  156. RefreshRemoveButton();
  157. UIUtils.SetElementDisplay(CustomContainer, true);
  158. package.AddSignal.OnOperation += OnAddOperation;
  159. package.RemoveSignal.OnOperation += OnRemoveOperation;
  160. foreach (var extension in PackageManagerExtensions.Extensions)
  161. extension.OnPackageSelectionChange(DisplayPackage.Info);
  162. }
  163. // Set visibility
  164. root.Q<VisualElement>("detail").visible = detailVisible;
  165. if (null == error)
  166. error = PackageCollection.Instance.GetPackageError(package);
  167. if (error != null)
  168. SetError(error);
  169. else
  170. DetailError.ClearError();
  171. }
  172. private void ResetVersionItems(PackageInfo displayPackage)
  173. {
  174. VersionItems.Clear();
  175. VersionPopup.SetEnabled(true);
  176. if (displayPackage == null)
  177. return;
  178. //
  179. // Get key versions -- Latest, Verified, LatestPatch, Current.
  180. var keyVersions = new List<PackageInfo>();
  181. if (package.LatestRelease != null) keyVersions.Add(package.LatestRelease);
  182. if (package.Current != null) keyVersions.Add(package.Current);
  183. if (package.Verified != null && package.Verified != package.Current) keyVersions.Add(package.Verified);
  184. if (package.LatestPatch != null && package.IsAfterCurrentVersion(package.LatestPatch)) keyVersions.Add(package.LatestPatch);
  185. if (package.Current == null && package.LatestRelease == null && package.Latest != null) keyVersions.Add(package.Latest);
  186. if (Package.ShouldProposeLatestVersions && package.Latest != package.LatestRelease && package.Latest != null) keyVersions.Add(package.Latest);
  187. keyVersions.Add(package.LatestUpdate); // Make sure LatestUpdate is always in the list.
  188. foreach (var version in keyVersions.OrderBy(package => package.Version).Reverse())
  189. {
  190. var item = new VersionItem {Version = version};
  191. VersionItems.Add(item);
  192. if (version == package.LatestUpdate)
  193. VersionPopup.value = item;
  194. }
  195. //
  196. // Add all versions
  197. foreach (var version in package.Versions.Reverse())
  198. {
  199. var item = new VersionItem {Version = version};
  200. item.MenuName = "All Versions/";
  201. VersionItems.Add(item);
  202. }
  203. if (VersionItems.Count == 0)
  204. {
  205. VersionItems.Add(EmptyVersion);
  206. VersionPopup.value = EmptyVersion;
  207. VersionPopup.SetEnabled(false);
  208. }
  209. }
  210. public void SetPackage(Package package)
  211. {
  212. if (this.package != null)
  213. {
  214. if (this.package.AddSignal.Operation != null)
  215. {
  216. this.package.AddSignal.Operation.OnOperationError -= OnAddOperationError;
  217. this.package.AddSignal.Operation.OnOperationSuccess -= OnAddOperationSuccess;
  218. }
  219. this.package.AddSignal.ResetEvents();
  220. if (this.package.RemoveSignal.Operation != null)
  221. {
  222. this.package.RemoveSignal.Operation.OnOperationSuccess -= OnRemoveOperationSuccess;
  223. this.package.RemoveSignal.Operation.OnOperationError -= OnRemoveOperationError;
  224. }
  225. this.package.RemoveSignal.ResetEvents();
  226. }
  227. UIUtils.SetElementDisplay(this, true);
  228. this.package = package;
  229. var displayPackage = package != null ? package.VersionToDisplay : null;
  230. ResetVersionItems(displayPackage);
  231. SetDisplayPackage(displayPackage);
  232. }
  233. private void SetError(Error error)
  234. {
  235. DetailError.AdjustSize(DetailView.verticalScroller.visible);
  236. DetailError.SetError(error);
  237. DetailError.OnCloseError = () =>
  238. {
  239. PackageCollection.Instance.RemovePackageErrors(package);
  240. PackageCollection.Instance.UpdatePackageCollection();
  241. };
  242. }
  243. private void OnAddOperation(IAddOperation operation)
  244. {
  245. operation.OnOperationError += OnAddOperationError;
  246. operation.OnOperationSuccess += OnAddOperationSuccess;
  247. }
  248. private void OnAddOperationError(Error error)
  249. {
  250. if (package != null && package.AddSignal.Operation != null)
  251. {
  252. package.AddSignal.Operation.OnOperationSuccess -= OnAddOperationSuccess;
  253. package.AddSignal.Operation.OnOperationError -= OnAddOperationError;
  254. package.AddSignal.Operation = null;
  255. }
  256. PackageCollection.Instance.AddPackageError(package, error);
  257. SetError(error);
  258. if (package != null)
  259. ResetVersionItems(package.VersionToDisplay);
  260. PackageCollection.Instance.UpdatePackageCollection();
  261. }
  262. private void OnAddOperationSuccess(PackageInfo packageInfo)
  263. {
  264. if (package != null && package.AddSignal.Operation != null)
  265. {
  266. package.AddSignal.Operation.OnOperationSuccess -= OnAddOperationSuccess;
  267. package.AddSignal.Operation.OnOperationError -= OnAddOperationError;
  268. package.AddSignal.Operation = null;
  269. }
  270. foreach (var extension in PackageManagerExtensions.Extensions)
  271. extension.OnPackageAddedOrUpdated(packageInfo.Info);
  272. }
  273. private void OnRemoveOperation(IRemoveOperation operation)
  274. {
  275. // Make sure we are not already registered
  276. operation.OnOperationError -= OnRemoveOperationError;
  277. operation.OnOperationSuccess -= OnRemoveOperationSuccess;
  278. operation.OnOperationError += OnRemoveOperationError;
  279. operation.OnOperationSuccess += OnRemoveOperationSuccess;
  280. }
  281. private void OnRemoveOperationError(Error error)
  282. {
  283. if (package != null && package.RemoveSignal.Operation != null)
  284. {
  285. package.RemoveSignal.Operation.OnOperationSuccess -= OnRemoveOperationSuccess;
  286. package.RemoveSignal.Operation.OnOperationError -= OnRemoveOperationError;
  287. package.RemoveSignal.Operation = null;
  288. }
  289. PackageCollection.Instance.AddPackageError(package, error);
  290. SetError(error);
  291. PackageCollection.Instance.UpdatePackageCollection();
  292. }
  293. private void OnRemoveOperationSuccess(PackageInfo packageInfo)
  294. {
  295. if (package != null && package.RemoveSignal.Operation != null)
  296. {
  297. package.RemoveSignal.Operation.OnOperationSuccess -= OnRemoveOperationSuccess;
  298. package.RemoveSignal.Operation.OnOperationError -= OnRemoveOperationError;
  299. package.RemoveSignal.Operation = null;
  300. }
  301. foreach (var extension in PackageManagerExtensions.Extensions)
  302. extension.OnPackageRemoved(packageInfo.Info);
  303. }
  304. private void RefreshAddButton()
  305. {
  306. if (package.Current != null && package.Current.IsInDevelopment)
  307. {
  308. UIUtils.SetElementDisplay(UpdateBuiltIn, false);
  309. UIUtils.SetElementDisplay(UpdateCombo, false);
  310. UIUtils.SetElementDisplay(UpdateButton, false);
  311. return;
  312. }
  313. var targetVersion = SelectedPackage;
  314. if (targetVersion == null)
  315. return;
  316. var enableButton = !Package.AddRemoveOperationInProgress;
  317. var enableVersionButton = true;
  318. var action = PackageAction.Update;
  319. var inprogress = false;
  320. var isBuiltIn = package.IsBuiltIn;
  321. SemVersion version = null;
  322. if (package.AddSignal.Operation != null)
  323. {
  324. if (isBuiltIn)
  325. {
  326. action = PackageAction.Enable;
  327. inprogress = true;
  328. enableButton = false;
  329. }
  330. else
  331. {
  332. var addOperationVersion = package.AddSignal.Operation.PackageInfo.Version;
  333. if (package.Current == null)
  334. {
  335. action = PackageAction.Add;
  336. inprogress = true;
  337. }
  338. else
  339. {
  340. action = addOperationVersion.CompareByPrecedence(package.Current.Version) >= 0
  341. ? PackageAction.Update : PackageAction.Downgrade;
  342. inprogress = true;
  343. }
  344. enableButton = false;
  345. enableVersionButton = false;
  346. }
  347. }
  348. else
  349. {
  350. if (package.Current != null)
  351. {
  352. // Installed
  353. if (package.Current.IsVersionLocked)
  354. {
  355. if (package.Current.Origin == PackageSource.Embedded)
  356. action = PackageAction.Embedded;
  357. else if (package.Current.Origin == PackageSource.Git)
  358. action = PackageAction.Git;
  359. enableButton = false;
  360. enableVersionButton = false;
  361. }
  362. else
  363. {
  364. if (targetVersion.IsCurrent)
  365. {
  366. if (targetVersion == package.LatestUpdate)
  367. action = PackageAction.UpToDate;
  368. else
  369. action = PackageAction.Current;
  370. enableButton = false;
  371. }
  372. else
  373. {
  374. action = targetVersion.Version.CompareByPrecedence(package.Current.Version) >= 0
  375. ? PackageAction.Update : PackageAction.Downgrade;
  376. }
  377. }
  378. }
  379. else
  380. {
  381. // Not Installed
  382. if (package.Versions.Any())
  383. {
  384. if (isBuiltIn)
  385. action = PackageAction.Enable;
  386. else
  387. action = PackageAction.Add;
  388. }
  389. }
  390. }
  391. if (package.RemoveSignal.Operation != null)
  392. enableButton = false;
  393. if (EditorApplication.isCompiling)
  394. {
  395. enableButton = false;
  396. enableVersionButton = false;
  397. EditorApplication.update -= CheckCompilationStatus;
  398. EditorApplication.update += CheckCompilationStatus;
  399. }
  400. var button = isBuiltIn ? UpdateBuiltIn : UpdateButton;
  401. button.SetEnabled(enableButton);
  402. VersionPopup.SetEnabled(enableVersionButton);
  403. button.text = GetButtonText(action, inprogress, version);
  404. var visibleFlag = !(package.Current != null && package.Current.IsVersionLocked);
  405. UIUtils.SetElementDisplay(UpdateBuiltIn, isBuiltIn && visibleFlag);
  406. UIUtils.SetElementDisplay(UpdateCombo, !isBuiltIn && visibleFlag);
  407. UIUtils.SetElementDisplay(UpdateButton, !isBuiltIn && visibleFlag);
  408. }
  409. private void RefreshRemoveButton()
  410. {
  411. var visibleFlag = false;
  412. var current = package.Current;
  413. // Show only if there is a current package installed
  414. if (current != null)
  415. {
  416. visibleFlag = current.CanBeRemoved && !package.IsPackageManagerUI;
  417. var action = current.IsBuiltIn ? PackageAction.Disable : PackageAction.Remove;
  418. var inprogress = package.RemoveSignal.Operation != null;
  419. var enableButton = visibleFlag && !EditorApplication.isCompiling && !inprogress && !Package.AddRemoveOperationInProgress;
  420. if (EditorApplication.isCompiling)
  421. {
  422. EditorApplication.update -= CheckCompilationStatus;
  423. EditorApplication.update += CheckCompilationStatus;
  424. }
  425. RemoveButton.SetEnabled(enableButton);
  426. RemoveButton.text = GetButtonText(action, inprogress);
  427. }
  428. UIUtils.SetElementDisplay(RemoveButton, visibleFlag);
  429. }
  430. private void CheckCompilationStatus()
  431. {
  432. if (EditorApplication.isCompiling)
  433. return;
  434. RefreshAddButton();
  435. RefreshRemoveButton();
  436. EditorApplication.update -= CheckCompilationStatus;
  437. }
  438. private static string GetButtonText(PackageAction action, bool inProgress = false, SemVersion version = null)
  439. {
  440. return version == null ?
  441. string.Format("{0}", inProgress ? PackageActionInProgressVerbs[(int) action] : PackageActionVerbs[(int) action]) :
  442. string.Format("{0} {1}", inProgress ? PackageActionInProgressVerbs[(int) action] : PackageActionVerbs[(int) action], version);
  443. }
  444. private void UpdateClick()
  445. {
  446. if (package.IsPackageManagerUI)
  447. {
  448. // Let's not allow updating of the UI if there are build errrors, as for now, that will prevent the UI from reloading properly.
  449. if (EditorUtility.scriptCompilationFailed)
  450. {
  451. EditorUtility.DisplayDialog("Unity Package Manager", "The Package Manager UI cannot be updated while there are script compilation errors in your project. Please fix the errors and try again.", "Ok");
  452. return;
  453. }
  454. if (!EditorUtility.DisplayDialog("Unity Package Manager", "Updating this package will close the Package Manager window. You will have to re-open it after the update is done. Do you want to continue?", "Yes", "No"))
  455. return;
  456. if (package.AddSignal.Operation != null)
  457. {
  458. package.AddSignal.Operation.OnOperationSuccess -= OnAddOperationSuccess;
  459. package.AddSignal.Operation.OnOperationError -= OnAddOperationError;
  460. package.AddSignal.ResetEvents();
  461. package.AddSignal.Operation = null;
  462. }
  463. DetailError.ClearError();
  464. EditorApplication.update += CloseAndUpdate;
  465. return;
  466. }
  467. DetailError.ClearError();
  468. package.Add(SelectedPackage);
  469. RefreshAddButton();
  470. RefreshRemoveButton();
  471. }
  472. private void CloseAndUpdate()
  473. {
  474. EditorApplication.update -= CloseAndUpdate;
  475. package.Add(SelectedPackage);
  476. var windows = UnityEngine.Resources.FindObjectsOfTypeAll<PackageManagerWindow>();
  477. if (windows.Length > 0)
  478. {
  479. windows[0].Close();
  480. }
  481. }
  482. private void RemoveClick()
  483. {
  484. DetailError.ClearError();
  485. package.Remove();
  486. RefreshRemoveButton();
  487. RefreshAddButton();
  488. }
  489. private void ViewDocClick()
  490. {
  491. Application.OpenURL(DisplayPackage.GetDocumentationUrl());
  492. }
  493. private void ViewChangelogClick()
  494. {
  495. Application.OpenURL(DisplayPackage.GetChangelogUrl());
  496. }
  497. private void ViewLicensesClick()
  498. {
  499. Application.OpenURL(DisplayPackage.GetLicensesUrl());
  500. }
  501. private Label DetailDesc { get { return root.Q<Label>("detailDesc"); } }
  502. internal Button UpdateButton { get { return root.Q<Button>("update"); } }
  503. private Button RemoveButton { get { return root.Q<Button>("remove"); } }
  504. private Button ViewDocButton { get { return root.Q<Button>("viewDocumentation"); } }
  505. private VisualElement DocumentationContainer { get { return root.Q<VisualElement>("documentationContainer"); } }
  506. private Button ViewChangelogButton { get { return root.Q<Button>("viewChangelog"); } }
  507. private VisualElement ChangelogContainer { get { return root.Q<VisualElement>("changeLogContainer"); } }
  508. private VisualElement ViewLicensesContainer { get { return root.Q<VisualElement>("viewLicensesContainer"); } }
  509. private Button ViewLicenses { get { return root.Q<Button>("viewLicenses"); } }
  510. private VisualElement UpdateContainer { get { return root.Q<VisualElement>("updateContainer"); } }
  511. private Alert DetailError { get { return root.Q<Alert>("detailError"); } }
  512. private ScrollView DetailView { get { return root.Q<ScrollView>("detailView"); } }
  513. private Label DetailModuleReference { get { return root.Q<Label>("detailModuleReference"); } }
  514. private Label DetailVersion { get { return root.Q<Label>("detailVersion"); }}
  515. private Label DetailAuthor { get { return root.Q<Label>("detailAuthor"); }}
  516. private Label VerifyLabel { get { return root.Q<Label>("tagVerify"); } }
  517. private VisualElement CustomContainer { get { return root.Q<VisualElement>("detailCustomContainer"); } }
  518. internal VisualElement GetTag(PackageTag tag) {return root.Q<VisualElement>("tag-" + tag); }
  519. private VisualElement UpdateDropdownContainer { get { return root.Q<VisualElement>("updateDropdownContainer"); } }
  520. internal VisualElement UpdateCombo { get { return root.Q<VisualElement>("updateCombo"); } }
  521. internal Button UpdateBuiltIn { get { return root.Q<Button>("updateBuiltIn"); } }
  522. }
  523. }