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