Files
T
2026-09-21 12:15:57 +03:00

836 lines
34 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using UnityEditor.AddressableAssets.Build.Layout;
using UnityEditor.AddressableAssets.GUI;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
namespace UnityEditor.AddressableAssets.BuildReportVisualizer
{
internal abstract class ContentView : IAddressableView, IBuildReportConsumer
{
protected BuildLayout m_Report;
internal ToolbarSearchField m_SearchField;
internal string m_SearchValue;
protected MultiColumnTreeView m_TreeView = null;
public MultiColumnTreeView ContentTreeView
{
get { return m_TreeView; }
}
protected IList<IAddressablesBuildReportItem> m_TreeItems = null;
public IList<IAddressablesBuildReportItem> TreeItems
{
get { return m_TreeItems; }
}
// Async search support
private CancellationTokenSource m_SearchCancellationTokenSource;
private CancellationTokenSource m_DebounceCancellationTokenSource;
private string m_PendingSearchValue = null;
private string m_LastSearchValue = null;
private const int k_SearchDebounceDelayMs = 500;
// Loading and no results containers that replace the tree view
protected VisualElement m_LoadingContainer = null;
protected VisualElement m_NoResultsContainer = null;
protected Label m_NoResultsLabel = null;
protected VisualElement m_ContentViewElement = null;
// Lazy icon loader
private IconLazyLoad m_IconLazyLoader = new IconLazyLoad();
protected object m_CachedFullTree = null;
public struct TreeDataReportItem
{
public int Id;
public IAddressablesBuildReportItem ReportItem;
public TreeDataReportItem(int id, IAddressablesBuildReportItem reportItem)
{
Id = id;
ReportItem = reportItem;
}
}
protected Dictionary<Hash128, TreeDataReportItem> m_DataHashtoReportItem = null;
public Dictionary<Hash128, TreeDataReportItem> DataHashtoReportItem
{
get { return m_DataHashtoReportItem; }
}
public Action<IEnumerable<object>> ItemsSelected;
internal abstract ContentViewColumnData[] ColumnDataForView { get; }
public abstract void Consume(BuildLayout buildReport);
public abstract void CreateGUI(VisualElement rootVisualElement);
public virtual void ClearGUI()
{
// Cancel any pending searches to prevent them from completing after switching views
m_SearchCancellationTokenSource?.Cancel();
m_SearchCancellationTokenSource = null;
m_DebounceCancellationTokenSource?.Cancel();
m_DebounceCancellationTokenSource = null;
m_PendingSearchValue = null;
// Unregister search field callback to prevent multiple views from responding to the same search
UnregisterSearchCallback();
m_IconLazyLoader?.ClearWorkQueue();
m_NoResultsContainer?.RemoveFromHierarchy();
m_LoadingContainer?.RemoveFromHierarchy();
m_TreeView?.RemoveFromHierarchy();
}
/// <summary>
/// Register search field callback - must be implemented by subclasses
/// </summary>
protected virtual void RegisterSearchCallback()
{
if (m_SearchField != null)
{
m_SearchField.RegisterValueChangedCallback(OnSearchValueChanged);
m_SearchValue = m_SearchField.value;
}
}
/// <summary>
/// Unregister search field callback - must be implemented by subclasses
/// </summary>
protected virtual void UnregisterSearchCallback()
{
if (m_SearchField != null)
m_SearchField.UnregisterValueChangedCallback(OnSearchValueChanged);
}
/// <summary>
/// Callback for search field value changes. Schedules a debounced search.
/// </summary>
/// <param name="evt">The change event containing the new search value</param>
protected void OnSearchValueChanged(ChangeEvent<string> evt)
{
if (m_TreeItems == null)
return;
ScheduleDebouncedSearch(evt.newValue);
}
internal BuildReportHelperConsumer m_HelperConsumer;
DetailsView m_DetailsView;
VisualTreeAsset m_TreeViewItem;
VisualTreeAsset m_TreeViewNavigableItem;
internal ContentView(BuildReportHelperConsumer helperConsumer, DetailsView detailsView)
{
m_HelperConsumer = helperConsumer;
m_DetailsView = detailsView;
m_TreeViewItem = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(BuildReportUtility.TreeViewItemFilePath);
m_TreeViewNavigableItem = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(BuildReportUtility.TreeViewNavigableItemFilePath);
}
public abstract IList<IAddressablesBuildReportItem> CreateTreeViewItems(BuildLayout report);
// Expresses bundle data as a flat list of TreeViewItemData objects.
protected IList<TreeViewItemData<IAddressablesBuildReportItem>> CreateTreeRootsFlatList(IList<IAddressablesBuildReportItem> items, Dictionary<Hash128, TreeDataReportItem> dataHashToReportItem)
{
int id = 0;
var roots = new List<TreeViewItemData<IAddressablesBuildReportItem>>(items.Count);
foreach (IAddressablesBuildReportItem item in items)
{
dataHashToReportItem.Add(BuildReportUtility.ComputeDataHash(item.Name, ""), new TreeDataReportItem(id, item));
roots.Add(new TreeViewItemData<IAddressablesBuildReportItem>(id++, item));
}
return roots;
}
internal static void SetCallbacksForColumns(Columns columns, ContentViewColumnData[] columnNameToWidth)
{
foreach (ContentViewColumnData data in columnNameToWidth)
{
Column col = columns[data.Name];
col.makeCell = () => new Label();
col.bindCell = data.BindCellCallback;
col.makeHeader = () => new Label();
col.bindHeader = data.BindHeaderCallback;
}
}
private IOrderedEnumerable<IAddressablesBuildReportItem> OrderByType(string columnName, Type t)
{
if (t == typeof(int))
return m_TreeItems.OrderBy(item => int.Parse(item.GetSortContent(columnName)));
if (t == typeof(ulong))
return m_TreeItems.OrderBy(item => ulong.Parse(item.GetSortContent(columnName)));
return null;
}
readonly Dictionary<string, Type> m_NumericColumnNames = new Dictionary<string, Type>()
{
{BuildReportUtility.AssetsContentViewColSizePlusRefs, typeof(ulong)},
{BuildReportUtility.AssetsContentViewColSizeUncompressed, typeof(ulong)},
{BuildReportUtility.AssetsContentViewColBundleSize, typeof(ulong)},
{BuildReportUtility.AssetsContentViewColRefsBy, typeof(int)},
{BuildReportUtility.AssetsContentViewColRefsTo, typeof(int)},
{BuildReportUtility.BundlesContentViewColSizePlusRefs, typeof(ulong)},
{BuildReportUtility.BundlesContentViewColSizeUncompressed, typeof(ulong)},
{BuildReportUtility.BundlesContentViewBundleSize, typeof(ulong)},
{BuildReportUtility.BundlesContentViewColRefsBy, typeof(int)},
{BuildReportUtility.BundlesContentViewColRefsTo, typeof(int)},
{BuildReportUtility.GroupsContentViewColSizePlusRefs, typeof(ulong)},
{BuildReportUtility.GroupsContentViewColSizeUncompressed, typeof(ulong)},
{BuildReportUtility.GroupsContentViewColBundleSize, typeof(ulong)},
{BuildReportUtility.GroupsContentViewColRefsBy, typeof(int)},
{BuildReportUtility.GroupsContentViewColRefsTo, typeof(int)},
{BuildReportUtility.LabelsContentViewColSizePlusRefs, typeof(ulong)},
{BuildReportUtility.LabelsContentViewColSizeUncompressed, typeof(ulong)},
{BuildReportUtility.LabelsContentViewColSizeBundle, typeof(ulong)},
{BuildReportUtility.LabelsContentViewColRefsBy, typeof(int)},
{BuildReportUtility.LabelsContentViewColRefsTo, typeof(int)},
{BuildReportUtility.DuplicatedAssetsContentViewSpaceSaved, typeof(ulong)},
{BuildReportUtility.DuplicatedAssetsContentViewDuplicationCount, typeof(int)},
{BuildReportUtility.DuplicatedAssetsContentViewColSize, typeof(ulong)}
};
public void CreateTreeViewHeader(VisualElement element, string colName, bool isAssetColumn)
{
(element as Label).text = ContentTreeView.columns[colName].title;
if (isAssetColumn)
element.AddToClassList(BuildReportUtility.TreeViewAssetHeader);
else
element.AddToClassList(BuildReportUtility.TreeViewHeader);
}
public void CreateTreeViewCell(VisualElement element, int index, string colName, bool isNameColumn, Type type)
{
IAddressablesBuildReportItem itemData = null;
if (type == typeof(AssetsContentView))
itemData = ContentTreeView.GetItemDataForIndex<AssetsViewBuildReportItem>(index);
if (type == typeof(BundlesContentView))
itemData = ContentTreeView.GetItemDataForIndex<BundlesViewBuildReportItem>(index);
if (type == typeof(LabelsContentView))
itemData = ContentTreeView.GetItemDataForIndex<LabelsViewBuildReportItem>(index);
if (type == typeof(GroupsContentView))
itemData = ContentTreeView.GetItemDataForIndex<GroupsViewBuildReportItem>(index);
if (type == typeof(DuplicatedAssetsContentView))
itemData = ContentTreeView.GetItemDataForIndex<DuplicatedAssetsViewBuildReportItem>(index);
if (isNameColumn)
{
ShowEntryIcon(element, itemData, m_TreeViewItem, colName);
element.AddToClassList(BuildReportUtility.TreeViewIconElement);
}
else
{
(element as Label).text = itemData.GetCellContent(colName);
element.AddToClassList(BuildReportUtility.TreeViewElement);
}
}
protected bool EntryAppearsInSearch(IAddressablesBuildReportItem item, string searchValue)
{
if (string.IsNullOrEmpty(searchValue))
return true;
if (item.Name.Contains(searchValue, StringComparison.OrdinalIgnoreCase))
return true;
return false;
}
/// <summary>
/// Schedules a debounced search to avoid excessive searching while the user is typing
/// </summary>
/// <param name="searchValue">The search string to filter items by</param>
internal async void ScheduleDebouncedSearch(string searchValue)
{
// Cancel any pending debounce
m_DebounceCancellationTokenSource?.Cancel();
m_DebounceCancellationTokenSource = new CancellationTokenSource();
m_PendingSearchValue = searchValue;
try
{
// Wait for the debounce delay
await Task.Delay(k_SearchDebounceDelayMs, m_DebounceCancellationTokenSource.Token);
// After delay, perform the search
PerformDebouncedSearchAsync();
}
catch (TaskCanceledException)
{
// Debounce was cancelled by a new keystroke, this is expected
}
}
private async void PerformDebouncedSearchAsync()
{
if (m_PendingSearchValue == null)
return;
string searchToExecute = m_PendingSearchValue;
m_PendingSearchValue = null;
// Cancel any existing search
m_SearchCancellationTokenSource?.Cancel();
m_SearchCancellationTokenSource = new CancellationTokenSource();
try
{
await PerformSearchAsync(searchToExecute, m_SearchCancellationTokenSource.Token);
}
catch (OperationCanceledException)
{
// Search was cancelled, this is expected
}
}
protected abstract Task PerformSearchAsync(string searchValue, CancellationToken cancellationToken);
internal async Task PerformSearchAsyncImpl<T>(string searchValue, CancellationToken cancellationToken) where T : IAddressablesBuildReportItem
{
ShowLoadingIndicator();
//We're searching for the same thing again, so just show the tree as-is
if (m_LastSearchValue == searchValue)
{
ShowTree();
return;
}
m_SearchValue = searchValue;
// Clear the icon work queue - we'll only load icons for filtered results
m_IconLazyLoader.ClearWorkQueue();
// If no search value, show cached full tree
if (string.IsNullOrEmpty(searchValue))
{
//delay one frame to allow UI to update
EditorApplication.delayCall += () =>
{
// Check if search was cancelled while waiting for delayCall
if (cancellationToken.IsCancellationRequested)
{
HideLoadingIndicator();
return;
}
if (m_CachedFullTree is List<TreeViewItemData<T>> cachedTree)
{
m_TreeView.SetRootItems(cachedTree);
m_TreeView.Rebuild();
}
ShowTree();
};
return;
}
// Filter cached tree instead of rebuilding
var allResults = new List<TreeViewItemData<T>>();
await Task.Run(() =>
{
if (m_CachedFullTree is List<TreeViewItemData<T>> cachedTree)
{
//foreach (var rootItem in cachedTree)
Parallel.ForEach(cachedTree, (rootItem, loopState) =>
{
if (cancellationToken.IsCancellationRequested)
loopState.Break();
var filteredItem = FilterTreeItem(rootItem, searchValue);
if (filteredItem.HasValue)
{
lock (allResults)
{
allResults.Add(filteredItem.Value);
}
}
});
}
}, cancellationToken);
// Final update with all results
if (!cancellationToken.IsCancellationRequested)
{
m_TreeView.SetRootItems(allResults);
m_TreeView.Rebuild();
// Check if search returned no results
if (allResults.Count == 0 && !string.IsNullOrEmpty(searchValue))
{
ShowNoResultsMessage(searchValue);
}
else
{
ShowTree();
}
}
m_LastSearchValue = searchValue;
}
private TreeViewItemData<T>? FilterTreeItem<T>(TreeViewItemData<T> item, string searchValue) where T : IAddressablesBuildReportItem
{
bool itemMatches = EntryAppearsInSearch(item.data, searchValue);
var filteredChildren = new List<TreeViewItemData<T>>();
// Recursively filter children
if (item.hasChildren)
{
foreach (var child in item.children)
{
var filteredChild = FilterTreeItem<T>(child, searchValue);
if (filteredChild.HasValue)
{
filteredChildren.Add(filteredChild.Value);
}
}
}
// Include this item if it matches OR has matching children
if (itemMatches || filteredChildren.Count > 0)
{
return new TreeViewItemData<T>(item.id, item.data, filteredChildren);
}
return null;
}
/// <summary>
/// Shows the loading indicator UI and hides the tree view and no results message
/// </summary>
internal void ShowLoadingIndicator()
{
if (m_ContentViewElement == null)
return;
HideTree();
HideNoResultsMessage();
// Remove ALL loading containers from the DOM to prevent duplicates (handles race conditions from multiple views)
var existingContainers = m_ContentViewElement.Query<VisualElement>(name: "LoadingContainer").ToList();
foreach (var container in existingContainers)
container.RemoveFromHierarchy();
// Add this view's loading container
if (m_LoadingContainer != null)
{
m_ContentViewElement.Add(m_LoadingContainer);
// Reset rotation for the spinner
var spinner = m_LoadingContainer.Q<VisualElement>("LoadingSpinner");
if (spinner != null)
spinner.style.rotate = new Rotate(Angle.Degrees(0));
}
}
/// <summary>
/// Shows the tree view and hides the loading indicator and no results message
/// </summary>
internal void ShowTree()
{
if (m_TreeView == null)
return; // should probably warn/error
HideLoadingIndicator();
HideNoResultsMessage();
// Remove ALL MultiColumnTreeView elements from ContentView (handles multiple views' searches completing)
var existingTreeViews = m_ContentViewElement.Query<MultiColumnTreeView>().ToList();
foreach (var treeView in existingTreeViews)
treeView.RemoveFromHierarchy();
// Add this view's tree view
m_ContentViewElement.Add(m_TreeView);
}
/// <summary>
/// Hides the tree view by removing it from the content view
/// </summary>
internal void HideTree()
{
// Remove ALL tree views from ContentView (handles race conditions)
if (m_ContentViewElement != null)
{
var existingTreeViews = m_ContentViewElement.Query<MultiColumnTreeView>().ToList();
foreach (var treeView in existingTreeViews)
treeView.RemoveFromHierarchy();
}
}
/// <summary>
/// Hides the loading indicator by removing it from the content view
/// </summary>
protected void HideLoadingIndicator()
{
if (m_ContentViewElement == null)
return;
// Remove ALL loading containers from ContentView (handles race conditions)
var existingContainers = m_ContentViewElement.Query<VisualElement>(name: "LoadingContainer").ToList();
foreach (var container in existingContainers)
container.RemoveFromHierarchy();
}
/// <summary>
/// Shows the "no results" message UI and hides the tree view and loading indicator
/// </summary>
/// <param name="searchValue">The search string that returned no results</param>
internal void ShowNoResultsMessage(string searchValue)
{
if (m_ContentViewElement == null)
return;
HideTree();
HideLoadingIndicator();
// Remove ALL no results containers from the DOM to prevent duplicates (handles race conditions from multiple views)
var existingContainers = m_ContentViewElement.Query<VisualElement>(name: "NoResultsContainer").ToList();
foreach (var container in existingContainers)
container.RemoveFromHierarchy();
// Update the label text with the search string
if (m_NoResultsLabel != null && !string.IsNullOrEmpty(searchValue))
{
m_NoResultsLabel.text = $"No results for \"{searchValue}\"";
}
// Add this view's no results container
if (m_NoResultsContainer != null)
{
m_ContentViewElement.Add(m_NoResultsContainer);
}
}
/// <summary>
/// Hides the "no results" message by removing it from the content view
/// </summary>
protected void HideNoResultsMessage()
{
if (m_ContentViewElement == null)
return;
// Remove ALL no results containers from ContentView (handles race conditions)
var existingContainers = m_ContentViewElement.Query<VisualElement>(name: "NoResultsContainer").ToList();
foreach (var container in existingContainers)
container.RemoveFromHierarchy();
}
// Helper class for building trees with incremental UI updates
protected class TreeBuilder<T> where T : IAddressablesBuildReportItem
{
private List<TreeViewItemData<T>> m_Results = new List<TreeViewItemData<T>>();
public void Add(TreeViewItemData<T> item)
{
m_Results.Add(item);
}
public List<TreeViewItemData<T>> GetResults()
{
lock (m_Results)
{
return m_Results;
}
}
}
/// <summary>
/// Loads and displays a tree view by building the tree data and optionally performing finalization steps
/// </summary>
/// <typeparam name="T">The type of build report items in the tree</typeparam>
/// <param name="buildTreeAction">Action to build the tree data by populating the provided list</param>
/// <param name="finalizeBeforeDisplayAction">Optional action to finalize the tree data before displaying it</param>
protected void LoadTree<T>(Action<List<TreeViewItemData<T>>> buildTreeAction, Action<List<TreeViewItemData<T>>> finalizeBeforeDisplayAction = null) where T : IAddressablesBuildReportItem
{
// Clear the icon work queue when building a new tree
m_IconLazyLoader.ClearWorkQueue();
var treeViewItemResults = new List<TreeViewItemData<T>>();
buildTreeAction(treeViewItemResults);
m_CachedFullTree = treeViewItemResults;
// Allow views to update additional state before displaying
finalizeBeforeDisplayAction?.Invoke(treeViewItemResults);
m_TreeView.SetRootItems(treeViewItemResults);
m_TreeView.Rebuild();
ShowTree();
}
/// <summary>
/// Creates the loading indicator UI elements including spinner and text
/// </summary>
/// <param name="rootVisualElement">The root visual element to query for the content view</param>
protected void CreateLoadingIndicator(VisualElement rootVisualElement)
{
if(m_ContentViewElement != null)
m_ContentViewElement.Clear();
m_ContentViewElement = rootVisualElement.Q<VisualElement>(BuildReportUtility.ContentView);
if (m_ContentViewElement == null)
return;
// Always clear and add the current tree view, even if containers already exist
// This handles the case where CreateGUI() is called multiple times with new tree views
m_ContentViewElement.Clear();
//if (m_TreeView != null && m_TreeView.parent != m_ContentViewElement)
// m_ContentViewElement.Add(m_TreeView);
if (m_LoadingContainer == null)
{
// Create loading container - centered in the content view
m_LoadingContainer = new VisualElement();
m_LoadingContainer.name = "LoadingContainer";
m_LoadingContainer.style.flexGrow = 1;
m_LoadingContainer.style.alignItems = Align.Center;
m_LoadingContainer.style.justifyContent = Justify.Center;
// Create inner container for spinner and text (side by side)
var innerContainer = new VisualElement();
innerContainer.style.flexDirection = FlexDirection.Row;
innerContainer.style.alignItems = Align.Center;
// Create spinner
var spinner = new VisualElement();
spinner.name = "LoadingSpinner";
spinner.style.width = 32;
spinner.style.height = 32;
spinner.style.marginRight = 10;
var spinnerIcon = new Image();
spinnerIcon.image = EditorGUIUtility.IconContent("Loading").image as Texture2D;
spinnerIcon.style.width = 32;
spinnerIcon.style.height = 32;
spinner.Add(spinnerIcon);
// Create loading text
var loadingText = new Label("Searching...");
loadingText.style.color = Color.white;
loadingText.style.unityFontStyleAndWeight = FontStyle.Bold;
loadingText.style.fontSize = 14;
innerContainer.Add(spinner);
innerContainer.Add(loadingText);
m_LoadingContainer.Add(innerContainer);
// Add rotation animation for spinner
spinner.schedule.Execute(() =>
{
if (m_LoadingContainer != null && m_LoadingContainer.parent != null)
{
var spinnerAngle = spinner.resolvedStyle.rotate;
spinner.style.rotate = new Rotate(Angle.Degrees(spinnerAngle.angle.value + 10f));
}
}).Every(50);
}
if (m_NoResultsContainer == null)
{
// Create "No results" container - centered in the content view
m_NoResultsContainer = new VisualElement();
m_NoResultsContainer.name = "NoResultsContainer";
m_NoResultsContainer.style.flexGrow = 1;
m_NoResultsContainer.style.alignItems = Align.Center;
m_NoResultsContainer.style.justifyContent = Justify.Center;
m_NoResultsLabel = new Label("No results found");
m_NoResultsLabel.style.color = Color.white;
m_NoResultsLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
m_NoResultsLabel.style.fontSize = 14;
m_NoResultsContainer.Add(m_NoResultsLabel);
}
}
public void ShowEntryIcon(VisualElement element, IAddressablesBuildReportItem itemData, VisualTreeAsset baseItem, string colName)
{
(element as Label).text = string.Empty;
element.Clear();
VisualElement treeItem = baseItem.Clone(element);
var icon = treeItem.Q<Image>(BuildReportUtility.TreeViewItemIcon);
var name = treeItem.Q<TextElement>(BuildReportUtility.TreeViewItemName);
name.text = itemData.GetCellContent(colName);
if (itemData is IAddressablesBuildReportAsset asset)
{
string path = asset.ExplicitAsset == null ? asset.DataFromOtherAsset.AssetPath : asset.ExplicitAsset.AssetPath;
// Use lazy loading for icons to improve performance
if (!string.IsNullOrEmpty(path))
{
m_IconLazyLoader.LoadIconLazy(icon, path);
}
else
{
icon.AddToClassList(BuildReportUtility.TreeViewItemNoIcon);
}
if (asset.DataFromOtherAsset != null)
name.AddToClassList(BuildReportUtility.TreeViewImplicitAsset);
if (asset is DuplicatedAssetsViewBuildReportDuplicatedAsset)
name.AddToClassList(BuildReportUtility.TreeViewDuplicatedAsset);
}
else if (itemData is LabelsViewBuildReportLabel label)
icon.image = EditorGUIUtility.IconContent("FilterByLabel").image as Texture2D;
else if (itemData is GroupsViewBuildReportGroup group ||
itemData is BundlesViewBuildReportIndirectlyReferencedBundles ||
itemData is GroupsViewBuildReportIndirectlyReferencedBundles)
icon.image = EditorGUIUtility.IconContent("d_FolderOpened Icon").image as Texture2D;
else if (itemData is IAddressablesBuildReportBundle)
icon.image = EditorGUIUtility.IconContent("Package Manager").image as Texture2D;
else
icon.AddToClassList(BuildReportUtility.TreeViewItemNoIcon);
name.AddManipulator(new ContextualMenuManipulator((ContextualMenuPopulateEvent evt) =>
{
evt.menu.AppendAction("Search in this window", (e) =>
{
string newSearchValue = name.text;
m_SearchField.Q<TextField>().value = newSearchValue;
});
}));
}
public ContentView UseCachedView(VisualElement rootVisualElement)
{
m_ContentViewElement = rootVisualElement.Q<VisualElement>(BuildReportUtility.ContentView);
if (m_ContentViewElement == null)
return this;
// Clear ContentView to remove any containers from other views
m_ContentViewElement.Clear();
HideNoResultsMessage();
HideLoadingIndicator();
RegisterSearchCallback();
// Add this view's tree view
if (m_TreeView != null && m_TreeView.parent != m_ContentViewElement)
m_ContentViewElement.Add(m_TreeView);
return this;
}
internal List<IAddressablesBuildReportItem> SortByColumnDescription(SortColumnDescription col)
{
IOrderedEnumerable<IAddressablesBuildReportItem> sortedTreeRootEnumerable;
if (m_NumericColumnNames.ContainsKey(col.columnName))
{
Type t = m_NumericColumnNames[col.columnName];
sortedTreeRootEnumerable = OrderByType(col.columnName, t);
}
else
{
sortedTreeRootEnumerable = m_TreeItems.OrderBy(item => item.GetSortContent(col.columnName));
}
List<IAddressablesBuildReportItem> finalTreeRoots = new List<IAddressablesBuildReportItem>(m_TreeItems.Count);
foreach (var item in sortedTreeRootEnumerable)
finalTreeRoots.Add(item);
if (col.direction == SortDirection.Ascending)
finalTreeRoots.Reverse();
return finalTreeRoots;
}
}
internal struct ContentViewColumnData
{
public string Name;
public string Title;
public Action<VisualElement, int> BindCellCallback;
public Action<VisualElement> BindHeaderCallback;
public ContentViewColumnData(string name, ContentView view, bool isNameColumn, string title = "N/a")
{
Name = name;
Title = title;
BindCellCallback = ((element, index) =>
{
view.CreateTreeViewCell(element, index, name, isNameColumn, view.GetType());
});
BindHeaderCallback = ((element) =>
{
view.CreateTreeViewHeader(element, name, isNameColumn);
});
}
}
/// <summary>
/// Nested interface that can be either a bundle or asset.
/// </summary>
public interface IAddressablesBuildReportItem
{
/// <summary>
/// The name of the build report item
/// </summary>
public string Name { get; }
/// <summary>
/// Create the UI element for a build report item
/// </summary>
/// <param name="rootVisualElement">The visual element container</param>
void CreateGUI(VisualElement rootVisualElement);
/// <summary>
/// Get content for a cell given a column name
/// </summary>
/// <param name="colName">The name of the column</param>
/// <returns>The display string for the cell</returns>
string GetCellContent(string colName);
/// <summary>
/// Get the sortable content
/// </summary>
/// <param name="colName">The name of the column</param>
/// <returns>The display string for the cell</returns>
string GetSortContent(string colName);
}
/// <summary>
/// Interface for an AssetBundle build report item
/// </summary>
public interface IAddressablesBuildReportBundle
{
/// <summary>
/// The AssetBundle data
/// </summary>
public BuildLayout.Bundle Bundle { get; }
}
/// <summary>
/// Interface for Asset build report item
/// </summary>
public interface IAddressablesBuildReportAsset
{
/// <summary>
/// The data to set if the asset is an explicit asset (Addressable)
/// </summary>
public BuildLayout.ExplicitAsset ExplicitAsset { get; }
/// <summary>
/// The data to set if the asset is an implicit asset (non-Addressable, but pulled into an Asset Bundle)
/// </summary>
public BuildLayout.DataFromOtherAsset DataFromOtherAsset { get; }
/// <summary>
/// The data for the AssetBundle the asset belongs to
/// </summary>
public List<BuildLayout.Bundle> Bundles { get; }
/// <summary>
/// The total size of this asset plus its dependencies
/// </summary>
public ulong SizeWDependencies { get; }
}
}