Added Unity project files
This commit is contained in:
552
Packages/dev.onevr.vrworldtoolkit/Editor/BuildReportTreeView.cs
Normal file
552
Packages/dev.onevr.vrworldtoolkit/Editor/BuildReportTreeView.cs
Normal file
@ -0,0 +1,552 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Build.Reporting;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRWorldToolkit.Editor
|
||||
{
|
||||
public class BuildReportTreeView : TreeView
|
||||
{
|
||||
private BuildReport report;
|
||||
public bool HasReport { get; private set; }
|
||||
public bool BuildSucceeded { get; private set; }
|
||||
|
||||
private enum TreeColumns
|
||||
{
|
||||
Type,
|
||||
Size,
|
||||
Name,
|
||||
Extension,
|
||||
Percentage,
|
||||
}
|
||||
|
||||
private readonly MultiColumnHeader header;
|
||||
|
||||
public BuildReportTreeView(TreeViewState state, MultiColumnHeader header, BuildReport report) : base(state, header)
|
||||
{
|
||||
this.header = header;
|
||||
showBorder = true;
|
||||
showAlternatingRowBackgrounds = true;
|
||||
this.header.sortingChanged += OnSortingChanged;
|
||||
|
||||
SetReport(report);
|
||||
}
|
||||
|
||||
private class BuildListAsset
|
||||
{
|
||||
public string AssetType { get; set; }
|
||||
public string FullPath { get; set; }
|
||||
public ulong Size { get; set; }
|
||||
public double Percentage { get; set; }
|
||||
|
||||
public BuildListAsset()
|
||||
{
|
||||
}
|
||||
|
||||
public BuildListAsset(Type assetType, string fullPath, ulong size)
|
||||
{
|
||||
AssetType = assetType.Name;
|
||||
FullPath = fullPath;
|
||||
Size = size;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class BuildReportItem : TreeViewItem
|
||||
{
|
||||
public Texture previewIcon { get; set; }
|
||||
public string assetType { get; set; }
|
||||
public string path { get; set; }
|
||||
public string extension { get; set; }
|
||||
public ulong size { get; set; }
|
||||
public double percentage { get; set; }
|
||||
|
||||
public BuildReportItem(int id, int depth, Texture previewIcon, string assetType, string displayName, string path, string extension, ulong size, double percentage) : base(id, depth, displayName)
|
||||
{
|
||||
this.previewIcon = previewIcon;
|
||||
this.assetType = assetType;
|
||||
this.displayName = displayName;
|
||||
this.path = path;
|
||||
this.extension = extension;
|
||||
this.size = size;
|
||||
this.percentage = percentage;
|
||||
}
|
||||
}
|
||||
|
||||
protected override TreeViewItem BuildRoot()
|
||||
{
|
||||
var root = new TreeViewItem {id = -1, depth = -1};
|
||||
|
||||
var packedAssets = report.packedAssets;
|
||||
|
||||
var bl = new List<BuildListAsset>();
|
||||
|
||||
for (var i = 0; i < packedAssets.Length; i++)
|
||||
{
|
||||
var packedAssetInfos = packedAssets[i].contents;
|
||||
for (int j = 0; j < packedAssetInfos.Length; j++)
|
||||
{
|
||||
var packedAssetInfo = packedAssetInfos[j];
|
||||
|
||||
var asset = new BuildListAsset(packedAssetInfo.type, packedAssetInfo.sourceAssetPath, packedAssetInfo.packedSize);
|
||||
|
||||
bl.Add(asset);
|
||||
}
|
||||
}
|
||||
|
||||
var results = bl
|
||||
.GroupBy(x => x.FullPath)
|
||||
.Select(cx => new BuildListAsset()
|
||||
{
|
||||
AssetType = cx.First().AssetType,
|
||||
FullPath = cx.First().FullPath,
|
||||
Size = cx.Aggregate(0UL, (total, x) => total + x.Size),
|
||||
})
|
||||
.OrderByDescending(x => x.Size)
|
||||
.ToList();
|
||||
|
||||
var totalSize = results.Sum(x => (long) x.Size);
|
||||
|
||||
for (var i = 0; i < results.Count; i++)
|
||||
{
|
||||
results[i].Percentage = (double) results[i].Size / totalSize;
|
||||
}
|
||||
|
||||
for (var i = 0; i < results.Count; i++)
|
||||
{
|
||||
var asset = results[i];
|
||||
|
||||
root.AddChild(new BuildReportItem(i,
|
||||
0,
|
||||
AssetDatabase.GetCachedIcon(asset.FullPath),
|
||||
asset.AssetType,
|
||||
GetFileName(asset.FullPath),
|
||||
asset.FullPath,
|
||||
GetFileExtension(asset.FullPath),
|
||||
asset.Size,
|
||||
asset.Percentage)
|
||||
);
|
||||
}
|
||||
|
||||
return root;
|
||||
|
||||
static string GetFileName(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
if (path.IndexOfAny(Path.GetInvalidPathChars()) >= 0)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
return Path.GetFileName(path);
|
||||
}
|
||||
|
||||
static string GetFileExtension(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path) || path.IndexOfAny(Path.GetInvalidPathChars()) >= 0)
|
||||
{
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
return Path.GetExtension(path);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetReport(BuildReport newReport)
|
||||
{
|
||||
report = newReport;
|
||||
HasReport = report != null;
|
||||
BuildSucceeded = HasReport && report.summary.result == BuildResult.Succeeded;
|
||||
|
||||
if (HasReport && BuildSucceeded)
|
||||
{
|
||||
Reload();
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasMessages()
|
||||
{
|
||||
return report.summary.totalErrors > 0 || report.summary.totalWarnings > 0;
|
||||
}
|
||||
|
||||
private struct CategoryStats
|
||||
{
|
||||
public string Name;
|
||||
public ulong Size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw overall stats view of the current build report
|
||||
/// </summary>
|
||||
public void DrawOverallStats()
|
||||
{
|
||||
if (HasReport && BuildSucceeded)
|
||||
{
|
||||
var stats = base.GetRows().Cast<BuildReportItem>().ToList();
|
||||
|
||||
var totalSize = stats.Aggregate(0UL, (total, x) => total + x.size);
|
||||
|
||||
var grouped = stats
|
||||
.GroupBy(x => x.assetType)
|
||||
.Select(cx => new CategoryStats()
|
||||
{
|
||||
Name = cx.First().assetType,
|
||||
Size = cx.Aggregate(0UL, (total, x) => total + x.size),
|
||||
}).OrderByDescending(x => x.Size)
|
||||
.ToArray();
|
||||
|
||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||
|
||||
for (var i = 0; i < grouped.Length; i++)
|
||||
{
|
||||
var item = grouped[i];
|
||||
|
||||
string name;
|
||||
|
||||
switch (item.Name)
|
||||
{
|
||||
case "Mono":
|
||||
name = "Scripts";
|
||||
break;
|
||||
case "Model":
|
||||
case "Texture":
|
||||
case "Shader":
|
||||
case "Asset":
|
||||
case "TrueTypeFont":
|
||||
case "Plugin":
|
||||
case "Prefab":
|
||||
name = item.Name + "s";
|
||||
break;
|
||||
default:
|
||||
name = item.Name;
|
||||
break;
|
||||
}
|
||||
|
||||
var rect = GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.label);
|
||||
var barGraphRect = rect;
|
||||
|
||||
barGraphRect.width *= (float)((double)item.Size / totalSize);
|
||||
EditorGUI.DrawRect(barGraphRect, new Color(0.28f, 0.37f, 0.51f, 0.6f));
|
||||
EditorGUIUtility.AddCursorRect(rect, MouseCursor.Link);
|
||||
|
||||
GUI.Label(rect , name + " - " + EditorUtility.FormatBytes((long)item.Size), EditorStyles.label);
|
||||
GUI.Label(rect, ((double)item.Size / totalSize).ToString("P"), Styles.BuildReportStatsLabel);
|
||||
|
||||
if (GUI.Button(rect, GUIContent.none, GUIStyle.none))
|
||||
{
|
||||
searchString = item.Name;
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 scrollPosMessages;
|
||||
|
||||
public void DrawMessages()
|
||||
{
|
||||
if (HasReport && HasMessages())
|
||||
{
|
||||
EditorGUILayout.BeginVertical();
|
||||
scrollPosMessages = EditorGUILayout.BeginScrollView(scrollPosMessages);
|
||||
|
||||
var steps = report.steps;
|
||||
|
||||
for (var i = 0; i < steps.Length; i++)
|
||||
{
|
||||
var step = steps[i];
|
||||
|
||||
if (step.messages.Length > 0)
|
||||
{
|
||||
GUILayout.Label(step.name, Styles.BoldWrap);
|
||||
|
||||
for (var j = 0; j < step.messages.Length; j++)
|
||||
{
|
||||
var message = step.messages[j];
|
||||
|
||||
var messageType = MessageType.Info;
|
||||
|
||||
switch (message.type)
|
||||
{
|
||||
case LogType.Error:
|
||||
case LogType.Exception:
|
||||
messageType = MessageType.Error;
|
||||
break;
|
||||
case LogType.Assert:
|
||||
case LogType.Warning:
|
||||
messageType = MessageType.Warning;
|
||||
break;
|
||||
}
|
||||
|
||||
EditorGUILayout.HelpBox(message.content, messageType);
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("", GUI.skin.horizontalSlider);
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.HelpBox("No messages to show.", MessageType.Info);
|
||||
}
|
||||
}
|
||||
|
||||
public static MultiColumnHeaderState CreateDefaultMultiColumnHeaderState(float treeViewWidth)
|
||||
{
|
||||
var columns = new[]
|
||||
{
|
||||
new MultiColumnHeaderState.Column
|
||||
{
|
||||
headerContent = EditorGUIUtility.IconContent("FilterByType"),
|
||||
contextMenuText = "Preview",
|
||||
headerTextAlignment = TextAlignment.Center,
|
||||
canSort = false,
|
||||
width = 20,
|
||||
minWidth = 20,
|
||||
maxWidth = 20,
|
||||
autoResize = false,
|
||||
allowToggleVisibility = true
|
||||
},
|
||||
new MultiColumnHeaderState.Column
|
||||
{
|
||||
headerContent = new GUIContent("USize", "Uncompressed size of the asset. Unity compresses assets during the build process, so this value will not match the size of the asset within the build."),
|
||||
contextMenuText = "USize",
|
||||
headerTextAlignment = TextAlignment.Left,
|
||||
sortedAscending = true,
|
||||
sortingArrowAlignment = TextAlignment.Right,
|
||||
width = 60,
|
||||
minWidth = 60,
|
||||
maxWidth = 75,
|
||||
autoResize = false,
|
||||
allowToggleVisibility = true
|
||||
},
|
||||
new MultiColumnHeaderState.Column
|
||||
{
|
||||
headerContent = new GUIContent("Name"),
|
||||
headerTextAlignment = TextAlignment.Left,
|
||||
sortedAscending = true,
|
||||
sortingArrowAlignment = TextAlignment.Center,
|
||||
width = 250,
|
||||
minWidth = 60,
|
||||
autoResize = true,
|
||||
allowToggleVisibility = false
|
||||
},
|
||||
new MultiColumnHeaderState.Column
|
||||
{
|
||||
headerContent = new GUIContent("Type", "File type"),
|
||||
contextMenuText = "Type",
|
||||
headerTextAlignment = TextAlignment.Left,
|
||||
sortedAscending = true,
|
||||
sortingArrowAlignment = TextAlignment.Right,
|
||||
width = 60,
|
||||
minWidth = 60,
|
||||
maxWidth = 100,
|
||||
autoResize = true,
|
||||
allowToggleVisibility = true
|
||||
},
|
||||
new MultiColumnHeaderState.Column
|
||||
{
|
||||
headerContent = new GUIContent("%", "Percentage out of all assets"),
|
||||
contextMenuText = "Percentage",
|
||||
headerTextAlignment = TextAlignment.Left,
|
||||
sortedAscending = true,
|
||||
sortingArrowAlignment = TextAlignment.Right,
|
||||
width = 60,
|
||||
minWidth = 60,
|
||||
maxWidth = 70,
|
||||
autoResize = false,
|
||||
allowToggleVisibility = true
|
||||
}
|
||||
};
|
||||
|
||||
var state = new MultiColumnHeaderState(columns);
|
||||
return state;
|
||||
}
|
||||
|
||||
protected override void RowGUI(RowGUIArgs args)
|
||||
{
|
||||
var buildReportItem = (BuildReportItem) args.item;
|
||||
|
||||
for (var visibleColumnIndex = 0; visibleColumnIndex < args.GetNumVisibleColumns(); visibleColumnIndex++)
|
||||
{
|
||||
Rect rect;
|
||||
// Get the current cell rect and index
|
||||
if (visibleColumnIndex == 2)
|
||||
{
|
||||
var rectOne = args.GetCellRect(visibleColumnIndex);
|
||||
var rectTwo = args.GetCellRect(3);
|
||||
|
||||
rect = new Rect(rectOne.position, new Vector2(rectOne.width + rectTwo.width, rectOne.height));
|
||||
}
|
||||
else
|
||||
{
|
||||
rect = args.GetCellRect(visibleColumnIndex);
|
||||
}
|
||||
|
||||
var columnIndex = (TreeColumns) args.GetColumn(visibleColumnIndex);
|
||||
|
||||
//Set label style to white if cell is selected otherwise to normal
|
||||
var labelStyle = args.selected ? Styles.TreeViewLabelSelected : Styles.TreeViewLabel;
|
||||
|
||||
//Handle drawing of the columns
|
||||
switch (columnIndex)
|
||||
{
|
||||
case TreeColumns.Type:
|
||||
GUI.Label(rect, buildReportItem.previewIcon, Styles.TreeViewLabelCenter);
|
||||
break;
|
||||
case TreeColumns.Name:
|
||||
if (args.selected && buildReportItem.path != "")
|
||||
{
|
||||
EditorGUI.LabelField(rect, buildReportItem.path, labelStyle);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUI.LabelField(rect, buildReportItem.displayName, labelStyle);
|
||||
}
|
||||
|
||||
break;
|
||||
case TreeColumns.Extension:
|
||||
//EditorGUI.LabelField(rect, buildReportItem.extension, labelStyle);
|
||||
break;
|
||||
case TreeColumns.Size:
|
||||
EditorGUI.LabelField(rect, EditorUtility.FormatBytes((long)buildReportItem.size), labelStyle);
|
||||
break;
|
||||
case TreeColumns.Percentage:
|
||||
EditorGUI.LabelField(rect, buildReportItem.percentage.ToString("P"), labelStyle);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(columnIndex), columnIndex, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle double clicks inside the TreeView
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
protected override void DoubleClickedItem(int id)
|
||||
{
|
||||
base.DoubleClickedItem(id);
|
||||
|
||||
// Get the clicked item
|
||||
var clickedItem = (BuildReportItem) FindItem(id, rootItem);
|
||||
|
||||
//Ping clicked asset in project window
|
||||
EditorGUIUtility.PingObject(AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(clickedItem.path));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle context clicks inside the TreeView
|
||||
/// </summary>
|
||||
/// <param name="id">ID of the clicked TreeView item</param>
|
||||
protected override void ContextClickedItem(int id)
|
||||
{
|
||||
base.ContextClickedItem(id);
|
||||
|
||||
// Get the clicked item
|
||||
var clickedItem = (BuildReportItem) FindItem(id, rootItem);
|
||||
|
||||
//base.SetSelection(new IList<int>());
|
||||
|
||||
// Create new
|
||||
var menu = new GenericMenu();
|
||||
|
||||
// Create the menu items
|
||||
menu.AddItem(new GUIContent("Copy Name"), false, ReplaceClipboard, Path.GetFileName(clickedItem.path));
|
||||
menu.AddItem(new GUIContent("Copy Path"), false, ReplaceClipboard, clickedItem.path);
|
||||
menu.AddItem(new GUIContent("Reveal in Explorer"), false, () => EditorUtility.RevealInFinder(clickedItem.path));
|
||||
menu.AddItem(new GUIContent("Select in Assets"), false, SelectAssetsInProjectWindow);
|
||||
|
||||
// Show the menu
|
||||
menu.ShowAsContext();
|
||||
|
||||
// Function to replace clipboard contents
|
||||
void ReplaceClipboard(object input)
|
||||
{
|
||||
EditorGUIUtility.systemCopyBuffer = (string) input;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects assets in the Project window based on the currently selected BuildReportItems.
|
||||
/// This is useful for quickly selecting a batch of assets to modify their import settings or other properties in bulk.
|
||||
/// </summary>
|
||||
private void SelectAssetsInProjectWindow()
|
||||
{
|
||||
// Retrieve the IDs of currently selected items
|
||||
var selectedItems = GetSelection();
|
||||
var assetPaths = new List<string>();
|
||||
|
||||
// Iterate over each selected item and collect their asset paths
|
||||
foreach (var itemId in selectedItems)
|
||||
{
|
||||
var item = FindItem(itemId, rootItem) as BuildReportItem;
|
||||
if (item != null && !string.IsNullOrEmpty(item.path))
|
||||
{
|
||||
assetPaths.Add(item.path);
|
||||
}
|
||||
}
|
||||
|
||||
// Load and select the assets in the Project window
|
||||
var assets = assetPaths.Select(AssetDatabase.LoadAssetAtPath<UnityEngine.Object>).ToArray();
|
||||
Selection.objects = assets;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if current item matches the search string
|
||||
/// </summary>
|
||||
/// <param name="item">Item to match</param>
|
||||
/// <param name="search">Search string</param>
|
||||
/// <returns>Returns true if the search term matches name or asset type</returns>
|
||||
protected override bool DoesItemMatchSearch(TreeViewItem item, string search)
|
||||
{
|
||||
// Cast match item for parameter access
|
||||
var textureTreeViewItem = (BuildReportItem) item;
|
||||
|
||||
// Try to match the search string to item name or asset type and return true if it does
|
||||
return textureTreeViewItem.displayName.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
textureTreeViewItem.assetType.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
}
|
||||
|
||||
protected override IList<TreeViewItem> BuildRows(TreeViewItem root)
|
||||
{
|
||||
var rows = base.BuildRows(root);
|
||||
|
||||
var sortedColumn = header.sortedColumnIndex;
|
||||
if (sortedColumn < 0) return rows;
|
||||
|
||||
var isSortedAscending = header.IsSortedAscending(sortedColumn);
|
||||
|
||||
var items = rows.Cast<BuildReportItem>();
|
||||
|
||||
Func<BuildReportItem, object> selector = sortedColumn switch
|
||||
{
|
||||
1 => i => i.size,
|
||||
2 => i => i.displayName,
|
||||
3 => i => i.extension,
|
||||
4 => i => i.size,
|
||||
_ => i => i.displayName
|
||||
};
|
||||
|
||||
items = isSortedAscending ? items.OrderBy(selector) : items.OrderByDescending(selector);
|
||||
|
||||
return items.Cast<TreeViewItem>().ToList();
|
||||
}
|
||||
|
||||
private void OnSortingChanged(MultiColumnHeader _)
|
||||
{
|
||||
Reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user