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

using System.Collections.Generic;
using System.Linq;
using Semver;
using UnityEngine;
using UnityEngine.Experimental.UIElements;
namespace UnityEditor.PackageManager.UI
{
#if !UNITY_2018_3_OR_NEWER
internal class PackageDetailsFactory : UxmlFactory<PackageDetails>
{
protected override PackageDetails DoCreate(IUxmlAttributes bag, CreationContext cc)
{
return new PackageDetails();
}
}
#endif
internal class PackageDetails : VisualElement
{
#if UNITY_2018_3_OR_NEWER
internal new class UxmlFactory : UxmlFactory<PackageDetails> { }
#endif
private readonly VisualElement root;
private Package package;
private const string emptyDescriptionClass = "empty";
private List<VersionItem> VersionItems;
internal PopupField<VersionItem> VersionPopup;
private PackageInfo DisplayPackage;
private PackageInfo SelectedPackage
{
get { return VersionPopup.value.Version != null ? VersionPopup.value.Version : null; }
}
internal enum PackageAction
{
Add,
Remove,
Update,
Downgrade,
Enable,
Disable,
UpToDate,
Current,
Local,
Git,
Embedded
}
private static readonly VersionItem EmptyVersion = new VersionItem {Version = null};
internal static readonly string[] PackageActionVerbs = { "Install", "Remove", "Update to", "Update to", "Enable", "Disable", "Up to date", "Current", "Local", "Git", "Embedded" };
internal static readonly string[] PackageActionInProgressVerbs = { "Installing", "Removing", "Updating to", "Updating to", "Enabling", "Disabling", "Up to date", "Current", "Local", "Git", "Embedded" };
public PackageDetails()
{
UIUtils.SetElementDisplay(this, false);
root = Resources.GetTemplate("PackageDetails.uxml");
Add(root);
foreach (var extension in PackageManagerExtensions.Extensions)
CustomContainer.Add(extension.CreateExtensionUI());
root.StretchToParentSize();
SetUpdateVisibility(false);
RemoveButton.visible = false;
UpdateBuiltIn.visible = false;
UpdateButton.clickable.clicked += UpdateClick;
UpdateBuiltIn.clickable.clicked += UpdateClick;
RemoveButton.clickable.clicked += RemoveClick;
ViewDocButton.clickable.clicked += ViewDocClick;
ViewChangelogButton.clickable.clicked += ViewChangelogClick;
ViewLicenses.clickable.clicked += ViewLicensesClick;
VersionItems = new List<VersionItem> {EmptyVersion};
VersionPopup = new PopupField<VersionItem>(VersionItems, 0);
VersionPopup.SetLabelCallback(VersionSelectionSetLabel);
VersionPopup.AddToClassList("popup");
VersionPopup.OnValueChanged(VersionSelectionChanged);
if (VersionItems.Count == 1)
VersionPopup.SetEnabled(false);
UpdateDropdownContainer.Add(VersionPopup);
VersionPopup.StretchToParentSize();
// Fix button on dark skin but overlapping edge pixel perfectly
if (EditorGUIUtility.isProSkin)
{
VersionPopup.style.positionLeft = -1;
UpdateDropdownContainer.style.sliceLeft = 4;
}
}
private string VersionSelectionSetLabel(VersionItem item)
{
return item.Label;
}
private void VersionSelectionChanged(ChangeEvent<VersionItem> e)
{
RefreshAddButton();
}
private void SetUpdateVisibility(bool value)
{
if (UpdateContainer != null)
UIUtils.SetElementDisplay(UpdateContainer, value);
}
internal void SetDisplayPackage(PackageInfo packageInfo)
{
DisplayPackage = packageInfo;
var detailVisible = true;
Error error = null;
if (package == null || DisplayPackage == null)
{
detailVisible = false;
UIUtils.SetElementDisplay(DocumentationContainer, false);
UIUtils.SetElementDisplay(CustomContainer, false);
UIUtils.SetElementDisplay(UpdateBuiltIn, false);
foreach (var extension in PackageManagerExtensions.Extensions)
extension.OnPackageSelectionChange(null);
}
else
{
SetUpdateVisibility(true);
UIUtils.SetElementDisplay(ViewDocButton, true);
RemoveButton.visible = true;
if (string.IsNullOrEmpty(DisplayPackage.Description))
{
DetailDesc.text = "There is no description for this package.";
DetailDesc.AddToClassList(emptyDescriptionClass);
}
else
{
DetailDesc.text = DisplayPackage.Description;
DetailDesc.RemoveFromClassList(emptyDescriptionClass);
}
root.Q<Label>("detailTitle").text = DisplayPackage.DisplayName;
DetailVersion.text = "Version " + DisplayPackage.VersionWithoutTag;
if (DisplayPackage.IsInDevelopment || DisplayPackage.HasVersionTag(PackageTag.preview))
UIUtils.SetElementDisplay(GetTag(PackageTag.verified), false);
else
{
var unityVersionParts = Application.unityVersion.Split('.');
var unityVersion = string.Format("{0}.{1}", unityVersionParts[0], unityVersionParts[1]);
VerifyLabel.text = unityVersion + " verified";
UIUtils.SetElementDisplay(GetTag(PackageTag.verified), DisplayPackage.IsVerified);
}
UIUtils.SetElementDisplay(GetTag(PackageTag.inDevelopment), DisplayPackage.IsInDevelopment);
UIUtils.SetElementDisplay(GetTag(PackageTag.local), DisplayPackage.IsLocal);
UIUtils.SetElementDisplay(GetTag(PackageTag.preview), DisplayPackage.IsPreview);
UIUtils.SetElementDisplay(DocumentationContainer, DisplayPackage.Origin != PackageSource.BuiltIn);
UIUtils.SetElementDisplay(ChangelogContainer, DisplayPackage.HasChangelog(DisplayPackage));
root.Q<Label>("detailName").text = DisplayPackage.Name;
root.Q<ScrollView>("detailView").scrollOffset = new Vector2(0, 0);
DetailModuleReference.text = "";
var isBuiltIn = DisplayPackage.IsBuiltIn;
if (isBuiltIn)
DetailModuleReference.text = DisplayPackage.BuiltInDescription;
DetailAuthor.text = "";
if (!string.IsNullOrEmpty(DisplayPackage.Author))
DetailAuthor.text = string.Format("Author: {0}", DisplayPackage.Author);
UIUtils.SetElementDisplay(DetailDesc, !isBuiltIn);
UIUtils.SetElementDisplay(DetailVersion, !isBuiltIn);
UIUtils.SetElementDisplayNonEmpty(DetailModuleReference);
UIUtils.SetElementDisplayNonEmpty(DetailAuthor);
if (DisplayPackage.Errors.Count > 0)
error = DisplayPackage.Errors.First();
RefreshAddButton();
RefreshRemoveButton();
UIUtils.SetElementDisplay(CustomContainer, true);
package.AddSignal.OnOperation += OnAddOperation;
package.RemoveSignal.OnOperation += OnRemoveOperation;
foreach (var extension in PackageManagerExtensions.Extensions)
extension.OnPackageSelectionChange(DisplayPackage.Info);
}
// Set visibility
root.Q<VisualElement>("detail").visible = detailVisible;
if (null == error)
error = PackageCollection.Instance.GetPackageError(package);
if (error != null)
SetError(error);
else
DetailError.ClearError();
}
private void ResetVersionItems(PackageInfo displayPackage)
{
VersionItems.Clear();
VersionPopup.SetEnabled(true);
if (displayPackage == null)
return;
//
// Get key versions -- Latest, Verified, LatestPatch, Current.
var keyVersions = new List<PackageInfo>();
if (package.LatestRelease != null) keyVersions.Add(package.LatestRelease);
if (package.Current != null) keyVersions.Add(package.Current);
if (package.Verified != null && package.Verified != package.Current) keyVersions.Add(package.Verified);
if (package.LatestPatch != null && package.IsAfterCurrentVersion(package.LatestPatch)) keyVersions.Add(package.LatestPatch);
if (package.Current == null && package.LatestRelease == null && package.Latest != null) keyVersions.Add(package.Latest);
if (Package.ShouldProposeLatestVersions && package.Latest != package.LatestRelease && package.Latest != null) keyVersions.Add(package.Latest);
keyVersions.Add(package.LatestUpdate); // Make sure LatestUpdate is always in the list.
foreach (var version in keyVersions.OrderBy(package => package.Version).Reverse())
{
var item = new VersionItem {Version = version};
VersionItems.Add(item);
if (version == package.LatestUpdate)
VersionPopup.value = item;
}
//
// Add all versions
foreach (var version in package.Versions.Reverse())
{
var item = new VersionItem {Version = version};
item.MenuName = "All Versions/";
VersionItems.Add(item);
}
if (VersionItems.Count == 0)
{
VersionItems.Add(EmptyVersion);
VersionPopup.value = EmptyVersion;
VersionPopup.SetEnabled(false);
}
}
public void SetPackage(Package package)
{
if (this.package != null)
{
if (this.package.AddSignal.Operation != null)
{
this.package.AddSignal.Operation.OnOperationError -= OnAddOperationError;
this.package.AddSignal.Operation.OnOperationSuccess -= OnAddOperationSuccess;
}
this.package.AddSignal.ResetEvents();
if (this.package.RemoveSignal.Operation != null)
{
this.package.RemoveSignal.Operation.OnOperationSuccess -= OnRemoveOperationSuccess;
this.package.RemoveSignal.Operation.OnOperationError -= OnRemoveOperationError;
}
this.package.RemoveSignal.ResetEvents();
}
UIUtils.SetElementDisplay(this, true);
this.package = package;
var displayPackage = package != null ? package.VersionToDisplay : null;
ResetVersionItems(displayPackage);
SetDisplayPackage(displayPackage);
}
private void SetError(Error error)
{
DetailError.AdjustSize(DetailView.verticalScroller.visible);
DetailError.SetError(error);
DetailError.OnCloseError = () =>
{
PackageCollection.Instance.RemovePackageErrors(package);
PackageCollection.Instance.UpdatePackageCollection();
};
}
private void OnAddOperation(IAddOperation operation)
{
operation.OnOperationError += OnAddOperationError;
operation.OnOperationSuccess += OnAddOperationSuccess;
}
private void OnAddOperationError(Error error)
{
if (package != null && package.AddSignal.Operation != null)
{
package.AddSignal.Operation.OnOperationSuccess -= OnAddOperationSuccess;
package.AddSignal.Operation.OnOperationError -= OnAddOperationError;
package.AddSignal.Operation = null;
}
PackageCollection.Instance.AddPackageError(package, error);
SetError(error);
if (package != null)
ResetVersionItems(package.VersionToDisplay);
PackageCollection.Instance.UpdatePackageCollection();
}
private void OnAddOperationSuccess(PackageInfo packageInfo)
{
if (package != null && package.AddSignal.Operation != null)
{
package.AddSignal.Operation.OnOperationSuccess -= OnAddOperationSuccess;
package.AddSignal.Operation.OnOperationError -= OnAddOperationError;
package.AddSignal.Operation = null;
}
foreach (var extension in PackageManagerExtensions.Extensions)
extension.OnPackageAddedOrUpdated(packageInfo.Info);
}
private void OnRemoveOperation(IRemoveOperation operation)
{
// Make sure we are not already registered
operation.OnOperationError -= OnRemoveOperationError;
operation.OnOperationSuccess -= OnRemoveOperationSuccess;
operation.OnOperationError += OnRemoveOperationError;
operation.OnOperationSuccess += OnRemoveOperationSuccess;
}
private void OnRemoveOperationError(Error error)
{
if (package != null && package.RemoveSignal.Operation != null)
{
package.RemoveSignal.Operation.OnOperationSuccess -= OnRemoveOperationSuccess;
package.RemoveSignal.Operation.OnOperationError -= OnRemoveOperationError;
package.RemoveSignal.Operation = null;
}
PackageCollection.Instance.AddPackageError(package, error);
SetError(error);
PackageCollection.Instance.UpdatePackageCollection();
}
private void OnRemoveOperationSuccess(PackageInfo packageInfo)
{
if (package != null && package.RemoveSignal.Operation != null)
{
package.RemoveSignal.Operation.OnOperationSuccess -= OnRemoveOperationSuccess;
package.RemoveSignal.Operation.OnOperationError -= OnRemoveOperationError;
package.RemoveSignal.Operation = null;
}
foreach (var extension in PackageManagerExtensions.Extensions)
extension.OnPackageRemoved(packageInfo.Info);
}
private void RefreshAddButton()
{
if (package.Current != null && package.Current.IsInDevelopment)
{
UIUtils.SetElementDisplay(UpdateBuiltIn, false);
UIUtils.SetElementDisplay(UpdateCombo, false);
UIUtils.SetElementDisplay(UpdateButton, false);
return;
}
var targetVersion = SelectedPackage;
if (targetVersion == null)
return;
var enableButton = !Package.AddRemoveOperationInProgress;
var enableVersionButton = true;
var action = PackageAction.Update;
var inprogress = false;
var isBuiltIn = package.IsBuiltIn;
SemVersion version = null;
if (package.AddSignal.Operation != null)
{
if (isBuiltIn)
{
action = PackageAction.Enable;
inprogress = true;
enableButton = false;
}
else
{
var addOperationVersion = package.AddSignal.Operation.PackageInfo.Version;
if (package.Current == null)
{
action = PackageAction.Add;
inprogress = true;
}
else
{
action = addOperationVersion.CompareByPrecedence(package.Current.Version) >= 0
? PackageAction.Update : PackageAction.Downgrade;
inprogress = true;
}
enableButton = false;
enableVersionButton = false;
}
}
else
{
if (package.Current != null)
{
// Installed
if (package.Current.IsVersionLocked)
{
if (package.Current.Origin == PackageSource.Embedded)
action = PackageAction.Embedded;
else if (package.Current.Origin == PackageSource.Git)
action = PackageAction.Git;
enableButton = false;
enableVersionButton = false;
}
else
{
if (targetVersion.IsCurrent)
{
if (targetVersion == package.LatestUpdate)
action = PackageAction.UpToDate;
else
action = PackageAction.Current;
enableButton = false;
}
else
{
action = targetVersion.Version.CompareByPrecedence(package.Current.Version) >= 0
? PackageAction.Update : PackageAction.Downgrade;
}
}
}
else
{
// Not Installed
if (package.Versions.Any())
{
if (isBuiltIn)
action = PackageAction.Enable;
else
action = PackageAction.Add;
}
}
}
if (package.RemoveSignal.Operation != null)
enableButton = false;
if (EditorApplication.isCompiling)
{
enableButton = false;
enableVersionButton = false;
EditorApplication.update -= CheckCompilationStatus;
EditorApplication.update += CheckCompilationStatus;
}
var button = isBuiltIn ? UpdateBuiltIn : UpdateButton;
button.SetEnabled(enableButton);
VersionPopup.SetEnabled(enableVersionButton);
button.text = GetButtonText(action, inprogress, version);
var visibleFlag = !(package.Current != null && package.Current.IsVersionLocked);
UIUtils.SetElementDisplay(UpdateBuiltIn, isBuiltIn && visibleFlag);
UIUtils.SetElementDisplay(UpdateCombo, !isBuiltIn && visibleFlag);
UIUtils.SetElementDisplay(UpdateButton, !isBuiltIn && visibleFlag);
}
private void RefreshRemoveButton()
{
var visibleFlag = false;
var current = package.Current;
// Show only if there is a current package installed
if (current != null)
{
visibleFlag = current.CanBeRemoved && !package.IsPackageManagerUI;
var action = current.IsBuiltIn ? PackageAction.Disable : PackageAction.Remove;
var inprogress = package.RemoveSignal.Operation != null;
var enableButton = visibleFlag && !EditorApplication.isCompiling && !inprogress && !Package.AddRemoveOperationInProgress;
if (EditorApplication.isCompiling)
{
EditorApplication.update -= CheckCompilationStatus;
EditorApplication.update += CheckCompilationStatus;
}
RemoveButton.SetEnabled(enableButton);
RemoveButton.text = GetButtonText(action, inprogress);
}
UIUtils.SetElementDisplay(RemoveButton, visibleFlag);
}
private void CheckCompilationStatus()
{
if (EditorApplication.isCompiling)
return;
RefreshAddButton();
RefreshRemoveButton();
EditorApplication.update -= CheckCompilationStatus;
}
private static string GetButtonText(PackageAction action, bool inProgress = false, SemVersion version = null)
{
return version == null ?
string.Format("{0}", inProgress ? PackageActionInProgressVerbs[(int) action] : PackageActionVerbs[(int) action]) :
string.Format("{0} {1}", inProgress ? PackageActionInProgressVerbs[(int) action] : PackageActionVerbs[(int) action], version);
}
private void UpdateClick()
{
if (package.IsPackageManagerUI)
{
// Let's not allow updating of the UI if there are build errrors, as for now, that will prevent the UI from reloading properly.
if (EditorUtility.scriptCompilationFailed)
{
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");
return;
}
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"))
return;
if (package.AddSignal.Operation != null)
{
package.AddSignal.Operation.OnOperationSuccess -= OnAddOperationSuccess;
package.AddSignal.Operation.OnOperationError -= OnAddOperationError;
package.AddSignal.ResetEvents();
package.AddSignal.Operation = null;
}
DetailError.ClearError();
EditorApplication.update += CloseAndUpdate;
return;
}
DetailError.ClearError();
package.Add(SelectedPackage);
RefreshAddButton();
RefreshRemoveButton();
}
private void CloseAndUpdate()
{
EditorApplication.update -= CloseAndUpdate;
package.Add(SelectedPackage);
var windows = UnityEngine.Resources.FindObjectsOfTypeAll<PackageManagerWindow>();
if (windows.Length > 0)
{
windows[0].Close();
}
}
private void RemoveClick()
{
DetailError.ClearError();
package.Remove();
RefreshRemoveButton();
RefreshAddButton();
}
private void ViewDocClick()
{
Application.OpenURL(DisplayPackage.GetDocumentationUrl());
}
private void ViewChangelogClick()
{
Application.OpenURL(DisplayPackage.GetChangelogUrl());
}
private void ViewLicensesClick()
{
Application.OpenURL(DisplayPackage.GetLicensesUrl());
}
private Label DetailDesc { get { return root.Q<Label>("detailDesc"); } }
internal Button UpdateButton { get { return root.Q<Button>("update"); } }
private Button RemoveButton { get { return root.Q<Button>("remove"); } }
private Button ViewDocButton { get { return root.Q<Button>("viewDocumentation"); } }
private VisualElement DocumentationContainer { get { return root.Q<VisualElement>("documentationContainer"); } }
private Button ViewChangelogButton { get { return root.Q<Button>("viewChangelog"); } }
private VisualElement ChangelogContainer { get { return root.Q<VisualElement>("changeLogContainer"); } }
private VisualElement ViewLicensesContainer { get { return root.Q<VisualElement>("viewLicensesContainer"); } }
private Button ViewLicenses { get { return root.Q<Button>("viewLicenses"); } }
private VisualElement UpdateContainer { get { return root.Q<VisualElement>("updateContainer"); } }
private Alert DetailError { get { return root.Q<Alert>("detailError"); } }
private ScrollView DetailView { get { return root.Q<ScrollView>("detailView"); } }
private Label DetailModuleReference { get { return root.Q<Label>("detailModuleReference"); } }
private Label DetailVersion { get { return root.Q<Label>("detailVersion"); }}
private Label DetailAuthor { get { return root.Q<Label>("detailAuthor"); }}
private Label VerifyLabel { get { return root.Q<Label>("tagVerify"); } }
private VisualElement CustomContainer { get { return root.Q<VisualElement>("detailCustomContainer"); } }
internal VisualElement GetTag(PackageTag tag) {return root.Q<VisualElement>("tag-" + tag); }
private VisualElement UpdateDropdownContainer { get { return root.Q<VisualElement>("updateDropdownContainer"); } }
internal VisualElement UpdateCombo { get { return root.Q<VisualElement>("updateCombo"); } }
internal Button UpdateBuiltIn { get { return root.Q<Button>("updateBuiltIn"); } }
}
}