#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