WuhuIslandTesting/Library/PackageCache/com.unity.addressables@1.21.12/Editor/BuildReportVisualizer/DetailsPanel/DetailsContentView.cs
2025-01-07 02:06:59 +01:00

545 lines
23 KiB
C#

#if UNITY_2022_2_OR_NEWER
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor.AddressableAssets.Build.Layout;
using UnityEditor.AddressableAssets.GUIElements;
using UnityEngine;
using UnityEngine.UIElements;
namespace UnityEditor.AddressableAssets.BuildReportVisualizer
{
class DetailsContentView
{
protected VisualTreeAsset m_DetailsContentDrillableListItem;
protected List<DetailsListItem> m_ContentItems;
protected Label m_ActiveContentsName;
protected ListView m_ContentItemsListView;
BuildReportWindow m_Window;
protected VisualElement m_ContentsPane;
protected Image m_ActiveContentIcon;
VisualElement m_Toolbar;
ContextualMenuManipulator contextMenuManipulator;
const float m_ItemHeight = 24f;
RibbonButton m_RefToButton;
RibbonButton m_RefByButton;
Button m_BackButton;
protected Dictionary<VisualElement, List<Action>> m_ButtonCallBackTracker = new Dictionary<VisualElement, List<Action>>();
public DetailsContentView(VisualElement root, BuildReportWindow window)
{
m_Window = window;
m_ContentsPane = root.Q<VisualElement>(BuildReportUtility.DetailsContentsList);
m_ActiveContentsName = root.Q<Label>(BuildReportUtility.BreadcrumbToolbarName);
m_ActiveContentIcon = root.Q<Image>(BuildReportUtility.BreadcrumbToolbarIcon);
m_Toolbar = root.Q<VisualElement>(BuildReportUtility.BreadcrumbToolbar);
var stylesheetPath = BuildReportUtility.GetDetailsViewStylesheetPath();
m_Toolbar.styleSheets.Add(AssetDatabase.LoadAssetAtPath<StyleSheet>(stylesheetPath));
m_ActiveContentsName.style.textOverflow = TextOverflow.Ellipsis;
m_ActiveContentsName.style.overflow = Overflow.Hidden;
m_ActiveContentsName.style.unityFontStyleAndWeight = FontStyle.Bold;
contextMenuManipulator = GenerateContextMenu();
m_ActiveContentsName.AddManipulator(contextMenuManipulator);
m_DetailsContentDrillableListItem = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(BuildReportUtility.DrillableListViewItemPath);
m_RefByButton = root.Q<RibbonButton>(BuildReportUtility.ReferencedByTab);
m_RefToButton = root.Q<RibbonButton>(BuildReportUtility.ReferencesToTab);
m_ContentItems = new List<DetailsListItem>();
m_ContentItemsListView = new ListView(m_ContentItems, m_ItemHeight, RefToMakeItem, RefToBindItem);
m_ContentItemsListView.showAlternatingRowBackgrounds = AlternatingRowBackground.All;
m_ContentItemsListView.style.marginLeft = new StyleLength(new Length(4f, LengthUnit.Pixel));
m_ContentItemsListView.visible = false;
m_BackButton = root.Q<Button>(BuildReportUtility.BreadcrumbToolbarBackButton);
BuildReportUtility.SetVisibility(m_BackButton, false);
m_BackButton.style.backgroundImage = new StyleBackground(BuildReportUtility.GetIcon(BuildReportUtility.GetBackIconPath()) as Texture2D);
m_BackButton.style.unityTextAlign = TextAnchor.MiddleCenter;
m_BackButton.clicked += () =>
{
DetailsStack.Pop();
};
m_ContentsPane.Add(m_ContentItemsListView);
DetailsStack.OnPop += (item) =>
{
DisplayContents(item);
RefreshBackButton();
};
DetailsStack.OnPush += (item) =>
{
RefreshBackButton();
};
}
public void DisplayContents(object item, DetailsViewTab tab)
{
RefreshBackButton();
DetailsContents contentsToDisplay = null;
if (item is DetailsContents)
contentsToDisplay = item as DetailsContents;
else if (DetailsUtility.IsBundle(item))
{
var bundle = DetailsUtility.GetBundle(item);
UpdateTabButtons(GetRefByCount(bundle), GetRefToCount(bundle));
contentsToDisplay = GetContents(bundle, tab);
}
else
{
var asset = DetailsUtility.GetAsset(item);
if (asset != null)
{
UpdateTabButtons(GetRefByCount(asset), GetRefToCount(asset));
contentsToDisplay = GetContents(asset, tab);
}
else
{
var otherAsset = DetailsUtility.GetOtherAssetData(item);
if (otherAsset != null)
{
UpdateTabButtons(GetRefByCount(otherAsset), GetRefToCount(otherAsset));
contentsToDisplay = GetContents(otherAsset, tab);
}
}
}
DisplayContents(contentsToDisplay);
}
public void ClearContents()
{
m_ContentItems.Clear();
m_ContentItemsListView.RefreshItems();
}
DetailsContents m_ActiveContents;
void DisplayContents(DetailsContents contents)
{
ClearContents();
m_ActiveContents = contents;
RefreshBackButton();
if (contents == null)
{
UpdateTabButtons(0,0);
m_ContentItemsListView.RefreshItems();
m_ContentItemsListView.visible = false;
m_ActiveContentsName.text = "";
m_ActiveContentIcon.image = null;
return;
}
m_ActiveContentsName.text = contents.Title;
m_ActiveContentIcon.image = BuildReportUtility.GetIcon(contents.AssetPath);
if (contents.AssetPath != BuildReportUtility.GetAssetBundleIconPath())
{
m_ActiveContentsName.tooltip = "Asset Path: " + contents.AssetPath;
m_ActiveContentsName.displayTooltipWhenElided = false;
}
else
{
m_ActiveContentsName.tooltip = null;
m_ActiveContentsName.displayTooltipWhenElided = true;
}
foreach (var item in contents.DrillableItems)
m_ContentItems.Add(item);
m_ContentItemsListView.RefreshItems();
m_ContentItemsListView.style.maxHeight = m_ContentItems.Count * m_ItemHeight;
if (!m_ContentItemsListView.visible)
m_ContentItemsListView.visible = true;
}
private ContextualMenuManipulator GenerateContextMenu()
{
var manip = new ContextualMenuManipulator((ContextualMenuPopulateEvent evt) =>
{
evt.menu.AppendAction("Search in this window", (e) =>
{
string newSearchValue = m_ActiveContentsName.text;
string isInBundleString = "(in this Bundle)";
if (newSearchValue.EndsWith(isInBundleString))
newSearchValue = newSearchValue.Substring(0, newSearchValue.Length - isInBundleString.Length - 1);
m_Window.m_ActiveContentView.m_SearchField.Q<TextField>().value = newSearchValue;
});
});
return manip;
}
private List<BuildLayout.ExplicitAsset> GetReferencingAssetsFromBundle(BuildLayout.Bundle referencingBundle, BuildLayout.Bundle bundle)
{
var referencingAssets = new List<BuildLayout.ExplicitAsset>();
foreach (var bd in referencingBundle.BundleDependencies)
{
if (bd.DependencyBundle == bundle)
{
foreach (var assetDep in bd.AssetDependencies)
referencingAssets.Add(assetDep.rootAsset);
return referencingAssets;
}
}
return referencingAssets;
}
DetailsContents GetContents(BuildLayout.Bundle bundle, DetailsViewTab tab)
{
DetailsContents value = new DetailsContents(bundle.Name, BuildReportUtility.GetAssetBundleIconPath());
switch(tab)
{
case DetailsViewTab.ReferencedBy:
foreach (var referencingBundle in bundle.DependentBundles)
{
var referencingAssetList = GetReferencingAssetsFromBundle(referencingBundle, bundle);
if (referencingAssetList.Count > 0)
{
value.DrillableItems.Add(new DetailsListItem(referencingBundle.Name, BuildReportUtility.GetAssetBundleIconPath(), () => ShowAssetsThatLinkToBundle(DetailsViewTab.ReferencedBy,
referencingBundle,
GetReferencingAssetsFromBundle(referencingBundle, bundle)), BuildReportUtility.GetForwardIconPath()));
}
else
{
value.DrillableItems.Add(new DetailsListItem(referencingBundle.Name, BuildReportUtility.GetAssetBundleIconPath(), null, null));
}
}
if (value.DrillableItems.Count == 0)
value.DrillableItems.Add(new DetailsListItem($"No AssetBundles have this listed as a dependency", BuildReportUtility.GetAssetBundleIconPath(), null, null));
break;
case DetailsViewTab.ReferencesTo:
foreach (var file in bundle.Files)
{
Dictionary<string, int> addressToIndexMap = new Dictionary<string, int>();
int index = 0;
foreach (var asset in file.Assets)
{
Action drillDownEvent = null;
if (asset.ExternallyReferencedAssets.Count > 0)
drillDownEvent = () => ShowReferencesToForAsset(value, asset, true);
DetailsListItem drillableItem = new DetailsListItem(asset.AddressableName, asset.AssetPath, drillDownEvent, drillDownEvent == null ? null : BuildReportUtility.GetForwardIconPath());
addressToIndexMap.Add(asset.AddressableName, index++);
value.DrillableItems.Add(drillableItem);
}
foreach (var implicitAsset in file.OtherAssets)
value.DrillableItems.Add(new DetailsListItem(implicitAsset.AssetPath,
implicitAsset.AssetPath,
() =>
{
List<int> referencingItems = new List<int>();
foreach(var refAsset in implicitAsset.ReferencingAssets)
referencingItems.Add(addressToIndexMap[refAsset.AddressableName]);
m_ContentItemsListView.SetSelection(referencingItems);
},BuildReportUtility.GetHelpIconPath(), FontStyle.Italic));
}
break;
}
return value;
}
void ShowReferencesByForAsset(DetailsContents value, BuildLayout.ExplicitAsset asset, bool shouldCallDisplayContents)
{
if (shouldCallDisplayContents)
value = new DetailsContents(asset.AddressableName, asset.AssetPath);
foreach (var refAsset in asset.ReferencingAssets)
{
if (refAsset.Bundle == asset.Bundle)
value.DrillableItems.Add(new DetailsListItem(refAsset.AddressableName, refAsset.AssetPath, null, null));
else
{
value.DrillableItems.Add(new DetailsListItem(refAsset.Bundle.Name, BuildReportUtility.GetAssetBundleIconPath(), () => ShowAssetsThatLinkToBundle(DetailsViewTab.ReferencedBy,
refAsset.Bundle,
GetAssetsThatLinkFromBundleMap(asset.Bundle, refAsset)[refAsset.Bundle]), BuildReportUtility.GetForwardIconPath()));
}
}
if (shouldCallDisplayContents)
{
DetailsStack.Push(m_ActiveContents);
DisplayContents(value);
}
}
void ShowReferencesToForAsset(DetailsContents value, BuildLayout.ExplicitAsset asset, bool shouldCallDisplayContents)
{
if (shouldCallDisplayContents)
value = new DetailsContents(asset.AddressableName, asset.AssetPath);
foreach (var internalAsset in asset.InternalReferencedExplicitAssets)
value.DrillableItems.Add(new DetailsListItem(internalAsset.AddressableName, internalAsset.AssetPath, null, null));
foreach (var externalAsset in asset.ExternallyReferencedAssets)
value.DrillableItems.Add(new DetailsListItem(externalAsset.Bundle.Name, BuildReportUtility.GetAssetBundleIconPath(), () => ShowAssetsThatLinkToBundle(
DetailsViewTab.ReferencesTo,
externalAsset.Bundle,
GetAssetsThatLinkToBundleMap(asset.Bundle, asset)[externalAsset.Bundle]), BuildReportUtility.GetForwardIconPath()));
foreach (var implicitAsset in asset.InternalReferencedOtherAssets)
value.DrillableItems.Add(new DetailsListItem(implicitAsset.AssetPath, implicitAsset.AssetPath, null, null, FontStyle.Italic));
if (shouldCallDisplayContents)
{
DetailsStack.Push(m_ActiveContents);
DisplayContents(value);
}
}
Dictionary<BuildLayout.Bundle, List<BuildLayout.ExplicitAsset>> GetAssetsThatLinkToBundleMap(BuildLayout.Bundle bundle, BuildLayout.ExplicitAsset asset)
{
Dictionary<BuildLayout.Bundle, List<BuildLayout.ExplicitAsset>> bundlesLinkedToAsset = new Dictionary<BuildLayout.Bundle, List<BuildLayout.ExplicitAsset>>();
foreach(var bDep in bundle.BundleDependencies)
{
foreach (var depAsset in bDep.AssetDependencies)
{
if (depAsset.rootAsset == asset)
{
var depBundle = depAsset.dependencyAsset.Bundle;
if (!bundlesLinkedToAsset.ContainsKey(depBundle))
bundlesLinkedToAsset[depBundle] = new List<BuildLayout.ExplicitAsset>();
bundlesLinkedToAsset[depBundle].Add(depAsset.dependencyAsset);
}
}
}
return bundlesLinkedToAsset;
}
Dictionary<BuildLayout.Bundle, List<BuildLayout.ExplicitAsset>> GetAssetsThatLinkFromBundleMap(BuildLayout.Bundle bundle, BuildLayout.ExplicitAsset asset)
{
Dictionary<BuildLayout.Bundle, List<BuildLayout.ExplicitAsset>> bundlesLinkedToAsset = new Dictionary<BuildLayout.Bundle, List<BuildLayout.ExplicitAsset>>();
//oof - this sucks, surely there's another way
foreach(var bDep in bundle.DependentBundles)
{
foreach (var depFile in bDep.Files)
{
foreach (var depAsset in depFile.Assets)
{
if (depAsset == asset)
{
var depBundle = depAsset.Bundle;
if (!bundlesLinkedToAsset.ContainsKey(depBundle))
bundlesLinkedToAsset[depBundle] = new List<BuildLayout.ExplicitAsset>();
bundlesLinkedToAsset[depBundle].Add(depAsset);
}
}
}
}
return bundlesLinkedToAsset;
}
void ShowAssetsThatLinkToBundle(DetailsViewTab tab, BuildLayout.Bundle bundle, List<BuildLayout.ExplicitAsset> linkedAssets)
{
DetailsContents dc = new DetailsContents(bundle.Name, BuildReportUtility.GetAssetBundleIconPath());
foreach (var asset in linkedAssets)
{
Action onDrillDown = null;
if (tab == DetailsViewTab.ReferencesTo && asset.ExternallyReferencedAssets.Count > 0)
onDrillDown = () => ShowReferencesToForAsset(dc, asset, true);
if (tab == DetailsViewTab.ReferencedBy && asset.ReferencingAssets.Count > 0)
onDrillDown = () => ShowReferencesByForAsset(dc, asset, true);
dc.DrillableItems.Add(new DetailsListItem(asset.AddressableName, asset.AssetPath, onDrillDown, onDrillDown == null ? null : BuildReportUtility.GetForwardIconPath()));
}
if (bundle.AssetCount - linkedAssets.Count > 0)
dc.DrillableItems.Add(new DetailsListItem($"({bundle.AssetCount - linkedAssets.Count}) other assets", null));
DetailsStack.Push(m_ActiveContents);
DisplayContents(dc);
}
DetailsContents GetContents(BuildLayout.ExplicitAsset asset, DetailsViewTab tab)
{
DetailsContents value = new DetailsContents(asset.AddressableName, asset.AssetPath);
switch (tab)
{
case DetailsViewTab.ReferencedBy:
ShowReferencesByForAsset(value, asset, false);
break;
case DetailsViewTab.ReferencesTo:
ShowReferencesToForAsset(value, asset, false);
break;
}
return value;
}
DetailsContents GetContents(BuildLayout.DataFromOtherAsset asset, DetailsViewTab tab)
{
DetailsContents value = new DetailsContents($"{asset.AssetPath} (in this Bundle)", asset.AssetPath);
switch(tab)
{
case DetailsViewTab.ReferencedBy:
foreach (var refAsset in asset.ReferencingAssets)
value.DrillableItems.Add(new DetailsListItem(refAsset.AddressableName, refAsset.AssetPath, null, null));
break;
case DetailsViewTab.ReferencesTo:
//Do nothing
break;
}
return value;
}
VisualElement RefToMakeItem()
{
var vta = m_DetailsContentDrillableListItem.Clone();
m_ButtonCallBackTracker.Add(vta, new List<Action>());
Button button = vta.Q<Button>(BuildReportUtility.DrillableListViewButton);
BuildReportUtility.SetVisibility(button, false);
return vta;
}
void RefToBindItem(VisualElement e, int i)
{
var drillDownEvent = m_ContentItems[i].DrillDownEvent;
Image icon = e.Q<Image>(BuildReportUtility.DrillableListViewItemIcon);
icon.image = null;
if (!string.IsNullOrEmpty(m_ContentItems[i].ImagePath) && BuildReportUtility.GetIcon(m_ContentItems[i].ImagePath) is Texture iconTexture && iconTexture != null)
{
icon.image = iconTexture;
icon.RemoveFromClassList(BuildReportUtility.TreeViewItemNoIcon);
}
else
{
icon.AddToClassList(BuildReportUtility.TreeViewItemNoIcon);
}
Button button = e.Q<Button>(BuildReportUtility.DrillableListViewButton);
var label = e.Q<Label>(BuildReportUtility.DrillableListViewItemName);
string buttonIconPath = m_ContentItems[i].ButtonImagePath;
if(!string.IsNullOrEmpty(buttonIconPath))
{
if (buttonIconPath == BuildReportUtility.GetHelpIconPath())
button.tooltip = "Selects the asset(s) that pulled this non-addressable asset into the bundle";
else
button.tooltip = null;
button.style.backgroundImage = new StyleBackground(BuildReportUtility.GetIcon(buttonIconPath) as Texture2D);
button.style.maxHeight = button.style.maxWidth = new Length(16f, LengthUnit.Pixel);
button.text = "";
label.style.minWidth = new Length(88f, LengthUnit.Percent);
}
label.text = m_ContentItems[i].Text;
if (m_ContentItems[i].ImagePath != BuildReportUtility.GetAssetBundleIconPath())
label.tooltip = "Asset Path: " + m_ContentItems[i].ImagePath;
label.style.unityFontStyleAndWeight = m_ContentItems[i].StyleForText;
if (m_ContentItems[i].CanUseContextMenu)
{
label.AddManipulator(new ContextualMenuManipulator((ContextualMenuPopulateEvent evt) =>
{
evt.menu.AppendAction("Search in this window", (e) =>
{
string newSearchValue = label.text;
m_Window.m_ActiveContentView.m_SearchField.Q<TextField>().value = newSearchValue;
});
}));
}
if (drillDownEvent == null)
{
label.style.maxWidth = label.style.width = new Length(88f, LengthUnit.Percent);
BuildReportUtility.SetVisibility(button, false);
}
else
{
label.style.maxWidth = label.style.width = new Length(64f, LengthUnit.Percent);
BuildReportUtility.SetVisibility(button, true);
foreach (var callback in m_ButtonCallBackTracker[e])
button.clicked -= callback;
m_ButtonCallBackTracker[e].Clear();
button.clicked += drillDownEvent;
m_ButtonCallBackTracker[e].Add(drillDownEvent);
}
}
void RefreshBackButton()
{
if (DetailsStack.Count > 0)
BuildReportUtility.SetVisibility(m_BackButton, true);
else
BuildReportUtility.SetVisibility(m_BackButton, false);
}
void UpdateTabButtons(int refBy, int refTo)
{
if (refBy == 0 && refTo == 0)
{
m_RefByButton.text = "Referenced By";
m_RefToButton.text = "References To";
}
else
{
m_RefByButton.text = $"Referenced By ({refBy})";
m_RefToButton.text = $"References To ({refTo})";
}
}
int GetRefByCount(BuildLayout.Bundle bundle)
{
return bundle.DependentBundles.Count;
}
int GetRefByCount(BuildLayout.ExplicitAsset asset)
{
return asset.ReferencingAssets.Count;
}
int GetRefByCount(BuildLayout.DataFromOtherAsset asset)
{
return asset.ReferencingAssets.Count;
}
int GetRefToCount(BuildLayout.Bundle bundle)
{
int total = 0;
foreach (var file in bundle.Files)
total += file.Assets.Count + file.OtherAssets.Count;
return total;
}
int GetRefToCount(BuildLayout.ExplicitAsset asset)
{
return asset.InternalReferencedExplicitAssets.Count + asset.ExternallyReferencedAssets.Count + asset.InternalReferencedOtherAssets.Count;
}
int GetRefToCount(BuildLayout.DataFromOtherAsset asset)
{
return 0;
}
}
}
#endif