resources optimized

This commit is contained in:
dddushesss 2022-01-16 05:40:49 +03:00
parent 196b483125
commit 74af3ab42d
1883 changed files with 1598074 additions and 7 deletions

9
.gitattributes vendored
View File

@ -1,6 +1,4 @@
# .gitattributes
# Images
*.ai filter=lfs diff=lfs merge=lfs -text
*.cubemap filter=lfs diff=lfs merge=lfs -text
@ -17,7 +15,6 @@
*.tga filter=lfs diff=lfs merge=lfs -text
*.tif filter=lfs diff=lfs merge=lfs -text
*.tiff filter=lfs diff=lfs merge=lfs -text
# Audio
*.mp3 filter=lfs diff=lfs merge=lfs -text
*.wav filter=lfs diff=lfs merge=lfs -text
@ -28,11 +25,9 @@
*.mod filter=lfs diff=lfs merge=lfs -text
*.s3m filter=lfs diff=lfs merge=lfs -text
*.xm filter=lfs diff=lfs merge=lfs -text
# Video
*.mp4 filter=lfs diff=lfs merge=lfs -text
*.mov filter=lfs diff=lfs merge=lfs -text
# 3D Objects
*.3dm filter=lfs diff=lfs merge=lfs -text
*.3ds filter=lfs diff=lfs merge=lfs -text
@ -54,7 +49,6 @@
*.skp filter=lfs diff=lfs merge=lfs -text
*.stl filter=lfs diff=lfs merge=lfs -text
*.ztl filter=lfs diff=lfs merge=lfs -text
# Other
*.a filter=lfs diff=lfs merge=lfs -text
*.exr filter=lfs diff=lfs merge=lfs -text
@ -67,4 +61,5 @@
*.ttf filter=lfs diff=lfs merge=lfs -text
*.rns filter=lfs diff=lfs merge=lfs -text
*.reason filter=lfs diff=lfs merge=lfs -text
*.lxo filter=lfs diff=lfs merge=lfs -text
*.lxo filter=lfs diff=lfs merge=lfs -text
*.anim filter=lfs diff=lfs merge=lfs -text

8
Assets/Heureka.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 235876c72d1be064dba0c8362a7ffcb2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 82438a27e3aa87a4ca1bbdca45986211
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c424fdb6dffbc984d9ade90acf8770d8
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,392 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 7b27603d2be0e81488eb446378c435df, type: 3}
m_Name: AHPRO_Readme
m_EditorClassIdentifier:
Icon: {fileID: 2800000, guid: 94411db6691648e4590eb22360ba6934, type: 3}
PackageShowPrio: 100
AssetName: Asset Hunter PRO
Subheader: Project Cleaning Tool
AssetIdentifier: Heureka.AssetHunterPRO
Description: 'Asset Hunter is THE project cleaning tool of the asset store.
It
helps you locate all the unused asset and delete them. It will reduce time spend
waiting for the editor to load, it also simply boosts productivity in general
by having to search through less stuff.
Quick start:
1: Open Asset
Hunter PRO (Ctrl+H)
2: Create a build
3: Load newly created log into
Asset Hunter PRO
4: Inspect and delete unused assets
5: Use settings
to ignore certain types, folders etc.
Quick Start (Asset dependency graph)
1:
Open Dependency Graph (Ctrl+Shift+H)
2: Press "Build Cache" button
3:
Select an asset in project window
On the roadmap:
Improved unused
script detection
Buildinfo comparison
'
Links:
- ActiveLink: 1
Name: Asset Store
Link: https://prf.hn/click/camref:1011l4Izm/pubref:Package/destination:https%3A%2F%2Fassetstore.unity.com%2Fpackages%2Fslug%2F135296
- ActiveLink: 1
Name: Documentation
Link: https://docs.heurekagames.com/Documentation%20AHP.pdf
- ActiveLink: 1
Name: Forum Thread
Link: http://forum.unity3d.com/threads/released-asset-hunter-project-cleaning.274173/
- ActiveLink: 1
Name: Youtube Promo
Link: https://youtu.be/dxqrJhBKdbY
- ActiveLink: 1
Name: Youtube Introduction
Link: https://youtu.be/m4_DcrhYhxI
VersionData:
- VersionNum:
Major: 1
Minor: 0
Patch: 0
VersionChanges:
- Full re-write of Asset Hunter 2. New features, improved workflow, new UI and
HUGE performance gains
FoldOut: 0
- VersionNum:
Major: 1
Minor: 1
Patch: 0
VersionChanges:
- '[Feature] New window that showcases buildreport information (2018 or newer)'
- '[Feature] Now able to remove text from buttons in settings'
- '[Feature] Added a way to set default save location for userprefs in settings'
- '[Feature] Added a way to set default save location for build info in settings'
- '[Bug] Fixed issue with file endings not being excluding properly'
FoldOut: 0
- VersionNum:
Major: 1
Minor: 1
Patch: 1
VersionChanges:
- '[Feature] Folders show accumulated disc size used'
- '[UI Enhancement] Now the refresh button changes color when project is out
of sync with buildinfo treeview'
- '[Documentation] Added documentation for 1.1.0'
FoldOut: 0
- VersionNum:
Major: 1
Minor: 1
Patch: 2
VersionChanges:
- '[Optimization] Now caches size of each folder, instead of calculating each
frame'
- '[Bug] Fixed an issue with loosing button icons after play/stop'
FoldOut: 0
- VersionNum:
Major: 1
Minor: 1
Patch: 3
VersionChanges:
- '[Bug] Fixed issue with scripting compile symbols if AH2 was manually deleted'
FoldOut: 0
- VersionNum:
Major: 1
Minor: 1
Patch: 4
VersionChanges:
- '[Bug] Added memory usage safeguard to fix issues with Unity running out of
memory when analyzing very large projects'
FoldOut: 0
- VersionNum:
Major: 1
Minor: 1
Patch: 5
VersionChanges:
- '[Bug] Fixed issue with assetbundles in streamingasset folder not adding all
dependencies'
FoldOut: 0
- VersionNum:
Major: 1
Minor: 1
Patch: 6
VersionChanges:
- '[Change] Changed the way we looks for assetbundle assets and dependencies.
No more false errors from unity'
- '[Change] Fixed compile warnings in 2018.3.'
- '[Bug] Fixed issue with nullreference in rare cases when autogenerating buildreport'
FoldOut: 0
- VersionNum:
Major: 1
Minor: 1
Patch: 7
VersionChanges:
- '[Feature] Now the Info button also opens the documentation file'
FoldOut: 0
- VersionNum:
Major: 1
Minor: 1
Patch: 8
VersionChanges:
- '[Bug] Fixed issue with stackoverflow exception when two assetbundles referenced
each other'
FoldOut: 0
- VersionNum:
Major: 1
Minor: 1
Patch: 9
VersionChanges:
- '[Bug] Added a null check before testing file size'
FoldOut: 0
- VersionNum:
Major: 1
Minor: 2
Patch: 0
VersionChanges:
- '[Feature] Added a way to save a given list of assets into a json file for
external use'
FoldOut: 0
- VersionNum:
Major: 1
Minor: 2
Patch: 1
VersionChanges:
- '[Bug] Fixed an issue with resource prefabs not having their dependencies added
correctly'
FoldOut: 0
- VersionNum:
Major: 1
Minor: 2
Patch: 2
VersionChanges:
- '[Bug] Fixed bug with assets referenced from nested prefabs inside resources
folder'
- '[Bug] Fixed issue with not being able to see used assets if the project was
fully cleaned'
FoldOut: 0
- VersionNum:
Major: 1
Minor: 2
Patch: 3
VersionChanges:
- '[Warning] Used precompile directive to remove warning for obsolete method
in 2019'
FoldOut: 0
- VersionNum:
Major: 1
Minor: 2
Patch: 4
VersionChanges:
- '[Bug] Fixed an issue with size of folders not being calculated correctly'
FoldOut: 0
- VersionNum:
Major: 1
Minor: 2
Patch: 5
VersionChanges:
- '[Bug] Cleaned up duplicate delegate subscription issue when opening and closing
windows multiple times'
FoldOut: 0
- VersionNum:
Major: 1
Minor: 2
Patch: 6
VersionChanges:
- '[Feature] Now Asset Hunter ignores .git folders inside the asset folder when
deleting empty folders'
FoldOut: 0
- VersionNum:
Major: 1
Minor: 2
Patch: 7
VersionChanges:
- Removed GC collect, since its performance implications outweighed the rare
outofmemory issues
FoldOut: 0
- VersionNum:
Major: 1
Minor: 2
Patch: 8
VersionChanges:
- Minor refactor
FoldOut: 0
- VersionNum:
Major: 1
Minor: 2
Patch: 9
VersionChanges:
- '[Warning] Fixed facebook warning in 2019.3'
FoldOut: 0
- VersionNum:
Major: 1
Minor: 3
Patch: 0
VersionChanges:
- '[Feature] Added support for Adressables'
- '[Bug] Fixed issue that icon in colored bar didn''t scale'
- '[Bug] Fixed bug with alignment of preview'
FoldOut: 0
- VersionNum:
Major: 1
Minor: 3
Patch: 1
VersionChanges:
- Update to match other Heureka package global code
FoldOut: 0
- VersionNum:
Major: 1
Minor: 3
Patch: 2
VersionChanges:
- Fix for Assembly Definitions in versions 2017.3 through 2018.4
FoldOut: 0
- VersionNum:
Major: 1
Minor: 3
Patch: 3
VersionChanges:
- Fixed issue with import caused by lackluster asset update options
FoldOut: 0
- VersionNum:
Major: 1
Minor: 3
Patch: 4
VersionChanges:
- Fixed issue with UWP
FoldOut: 0
- VersionNum:
Major: 1
Minor: 3
Patch: 5
VersionChanges:
- Removed 'Build' button
- Removed 'AutoUpgrade' functionality from AH2 as it caused issues for a few
people
FoldOut: 0
- VersionNum:
Major: 1
Minor: 3
Patch: 6
VersionChanges:
- Fixed issues with renderpipelines not being recognized as being 'in use'
FoldOut: 0
- VersionNum:
Major: 1
Minor: 3
Patch: 7
VersionChanges:
- Fixed issue where you couldn't sort by type in the overview
FoldOut: 0
- VersionNum:
Major: 1
Minor: 3
Patch: 8
VersionChanges:
- Fixed issue with Addressables not being correctly identified (>2019 only)
FoldOut: 0
- VersionNum:
Major: 2
Minor: 0
Patch: 0
VersionChanges:
- '[New feature] Added Reference Graph'
FoldOut: 0
- VersionNum:
Major: 2
Minor: 1
Patch: 0
VersionChanges:
- '[Feature] Duplicate Assets Window'
- Fixed issue with script detection not triggering correctly
- Fixed issue with unity autogenerating streamingassets folder
- Expanded color palette, now using better red
FoldOut: 0
- VersionNum:
Major: 2
Minor: 1
Patch: 1
VersionChanges:
- Added settings to control automatic sync after projectchanges
- Improved multiselection in treeview to improve delete functionality
FoldOut: 0
- VersionNum:
Major: 2
Minor: 1
Patch: 2
VersionChanges:
- Fixed issues with icon colors in light editor theme
FoldOut: 0
- VersionNum:
Major: 2
Minor: 1
Patch: 3
VersionChanges:
- Fixed issue with Window Store Apps (WSA) Ressources
FoldOut: 0
- VersionNum:
Major: 2
Minor: 1
Patch: 4
VersionChanges:
- Fixed bug with header
- Added MenuItems to Tools in menu
- Tweaked button layout
FoldOut: 0
- VersionNum:
Major: 2
Minor: 1
Patch: 5
VersionChanges:
- Updated documentation file
FoldOut: 0
- VersionNum:
Major: 2
Minor: 2
Patch: 0
VersionChanges:
- Fixed issue with progress bar not actually moving when analyzing project
- Fixed warnings when trying to delete empty folders
- Reduced logfile size
- Tweaked logfile datamodel
- Improved speed by not requiring assetsize calculations by efault
FoldOut: 0
- VersionNum:
Major: 2
Minor: 2
Patch: 1
VersionChanges:
- Fixed faulty documentation link
FoldOut: 0

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 059cb120dafc78d4bbf6d0e96525b794
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b4279a46ecf187080ec577b5bcea148c0f074b132533740ce494df0bf9a912c2
size 762920

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4be7ccf6efbafc94fa96a66c564aa3ff
timeCreated: 1544715872
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7220ae8106f289c4597ec7cfffc7a705
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,168 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 200a2edfc52002c468b33af791f333c0, type: 3}
m_Name: AH_EditorData
m_EditorClassIdentifier:
Documentation: {fileID: 102900000, guid: 4be7ccf6efbafc94fa96a66c564aa3ff, type: 3}
WindowPaneIcon:
isUsingDarkSkin: 0
buildInIconName:
iconCached: {fileID: 2800000, guid: c74dc094c6d624f44ae92d7fcfd10744, type: 3}
m_iconNormalOverride: {fileID: 2800000, guid: c74dc094c6d624f44ae92d7fcfd10744,
type: 3}
m_iconProSkinOverride: {fileID: 2800000, guid: c74dc094c6d624f44ae92d7fcfd10744,
type: 3}
m_darkSkinInvert: 1
WindowHeaderIcon:
isUsingDarkSkin: 0
buildInIconName:
iconCached: {fileID: 2800000, guid: 5c0b496b8934f2646990616a88e2364b, type: 3}
m_iconNormalOverride: {fileID: 2800000, guid: 5c0b496b8934f2646990616a88e2364b,
type: 3}
m_iconProSkinOverride: {fileID: 2800000, guid: 5c0b496b8934f2646990616a88e2364b,
type: 3}
m_darkSkinInvert: 0
SceneIcon:
isUsingDarkSkin: 0
buildInIconName: d_SceneViewOrtho
iconCached: {fileID: 0}
m_iconNormalOverride: {fileID: 2800000, guid: 737d13181cb6632409e566acafe9165e,
type: 3}
m_iconProSkinOverride: {fileID: 2800000, guid: 737d13181cb6632409e566acafe9165e,
type: 3}
m_darkSkinInvert: 1
Settings:
isUsingDarkSkin: 0
buildInIconName:
iconCached: {fileID: 2800000, guid: 3efc0a50e5ff30b408b82ebc5a19a3bc, type: 3}
m_iconNormalOverride: {fileID: 2800000, guid: 3efc0a50e5ff30b408b82ebc5a19a3bc,
type: 3}
m_iconProSkinOverride: {fileID: 2800000, guid: 3efc0a50e5ff30b408b82ebc5a19a3bc,
type: 3}
m_darkSkinInvert: 1
LoadLogIcon:
isUsingDarkSkin: 0
buildInIconName:
iconCached: {fileID: 2800000, guid: 5b3fb49862d40684fa5bb0397be963cb, type: 3}
m_iconNormalOverride: {fileID: 2800000, guid: 5b3fb49862d40684fa5bb0397be963cb,
type: 3}
m_iconProSkinOverride: {fileID: 2800000, guid: 5b3fb49862d40684fa5bb0397be963cb,
type: 3}
m_darkSkinInvert: 1
GenerateIcon:
isUsingDarkSkin: 1
buildInIconName: Toolbar Plus
iconCached: {fileID: 0}
m_iconNormalOverride: {fileID: 2800000, guid: 1c6d63f95ebb3ee47a6b29de1a18ca76,
type: 3}
m_iconProSkinOverride: {fileID: 2800000, guid: 1c6d63f95ebb3ee47a6b29de1a18ca76,
type: 3}
m_darkSkinInvert: 1
RefreshIcon:
isUsingDarkSkin: 0
buildInIconName: d_Refresh
iconCached: {fileID: 2800000, guid: 20aa48ec545c5c04d8888ed19325d565, type: 3}
m_iconNormalOverride: {fileID: 2800000, guid: 20aa48ec545c5c04d8888ed19325d565,
type: 3}
m_iconProSkinOverride: {fileID: 2800000, guid: 20aa48ec545c5c04d8888ed19325d565,
type: 3}
m_darkSkinInvert: 1
MergerIcon:
isUsingDarkSkin: 1
buildInIconName:
iconCached: {fileID: 0}
m_iconNormalOverride: {fileID: 2800000, guid: 7722d61e931fb124fbab508643fd1adc,
type: 3}
m_iconProSkinOverride: {fileID: 2800000, guid: 7722d61e931fb124fbab508643fd1adc,
type: 3}
m_darkSkinInvert: 1
HelpIcon:
isUsingDarkSkin: 0
buildInIconName:
iconCached: {fileID: 2800000, guid: 540dfcbc3ec549e4391cb8295bae971d, type: 3}
m_iconNormalOverride: {fileID: 2800000, guid: 540dfcbc3ec549e4391cb8295bae971d,
type: 3}
m_iconProSkinOverride: {fileID: 2800000, guid: 540dfcbc3ec549e4391cb8295bae971d,
type: 3}
m_darkSkinInvert: 1
AchievementIcon:
isUsingDarkSkin: 0
buildInIconName:
iconCached: {fileID: 0}
m_iconNormalOverride: {fileID: 2800000, guid: e3ad9951a783ce1458596c95375a0e05,
type: 3}
m_iconProSkinOverride: {fileID: 2800000, guid: e3ad9951a783ce1458596c95375a0e05,
type: 3}
m_darkSkinInvert: 0
ReportIcon:
isUsingDarkSkin: 0
buildInIconName:
iconCached: {fileID: 2800000, guid: 30883e27c1d854c45bf96b2a55acd1d6, type: 3}
m_iconNormalOverride: {fileID: 2800000, guid: 30883e27c1d854c45bf96b2a55acd1d6,
type: 3}
m_iconProSkinOverride: {fileID: 2800000, guid: 30883e27c1d854c45bf96b2a55acd1d6,
type: 3}
m_darkSkinInvert: 1
DeleteIcon:
isUsingDarkSkin: 0
buildInIconName:
iconCached: {fileID: 2800000, guid: d6050bdf4fefe05499cd274a728d8aa5, type: 3}
m_iconNormalOverride: {fileID: 2800000, guid: d6050bdf4fefe05499cd274a728d8aa5,
type: 3}
m_iconProSkinOverride: {fileID: 2800000, guid: d6050bdf4fefe05499cd274a728d8aa5,
type: 3}
m_darkSkinInvert: 1
RefToIcon:
isUsingDarkSkin: 0
buildInIconName:
iconCached: {fileID: 2800000, guid: 970f0822e94c6d34cab2061b78d516bf, type: 3}
m_iconNormalOverride: {fileID: 2800000, guid: 970f0822e94c6d34cab2061b78d516bf,
type: 3}
m_iconProSkinOverride: {fileID: 2800000, guid: 970f0822e94c6d34cab2061b78d516bf,
type: 3}
m_darkSkinInvert: 1
RefFromIcon:
isUsingDarkSkin: 0
buildInIconName:
iconCached: {fileID: 2800000, guid: 5f861796146d3af48827e3fd599a0569, type: 3}
m_iconNormalOverride: {fileID: 2800000, guid: 5f861796146d3af48827e3fd599a0569,
type: 3}
m_iconProSkinOverride: {fileID: 2800000, guid: 5f861796146d3af48827e3fd599a0569,
type: 3}
m_darkSkinInvert: 1
RefFromWhiteIcon:
isUsingDarkSkin: 0
buildInIconName:
iconCached: {fileID: 2800000, guid: 9d58f0a14fd71a44faaf72e93d8cf1cb, type: 3}
m_iconNormalOverride: {fileID: 2800000, guid: 9d58f0a14fd71a44faaf72e93d8cf1cb,
type: 3}
m_iconProSkinOverride: {fileID: 2800000, guid: 9d58f0a14fd71a44faaf72e93d8cf1cb,
type: 3}
m_darkSkinInvert: 0
DuplicateIcon:
isUsingDarkSkin: 0
buildInIconName:
iconCached: {fileID: 2800000, guid: bcaae0bce30d6154abf035f64ddd64ff, type: 3}
m_iconNormalOverride: {fileID: 2800000, guid: bcaae0bce30d6154abf035f64ddd64ff,
type: 3}
m_iconProSkinOverride: {fileID: 2800000, guid: bcaae0bce30d6154abf035f64ddd64ff,
type: 3}
m_darkSkinInvert: 1
DuplicateWhiteIcon:
isUsingDarkSkin: 0
buildInIconName:
iconCached: {fileID: 2800000, guid: 8f1965181629b5848a8c3f85a6f4d697, type: 3}
m_iconNormalOverride: {fileID: 2800000, guid: 8f1965181629b5848a8c3f85a6f4d697,
type: 3}
m_iconProSkinOverride: {fileID: 2800000, guid: 8f1965181629b5848a8c3f85a6f4d697,
type: 3}
m_darkSkinInvert: 0

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: a749fc7acdf105544864dc3d96bd8582
timeCreated: 1498416873
licenseType: Store
NativeFormatImporter:
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4c812440a5dcbb54d94816649c8007da
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,24 @@
{
"name": "AH_AssemblyDefinition",
"references": [
"GUID:6b3b893d46a69d5498029e96ff000779",
"GUID:69448af7b92c7f342b298e06a37122aa"
],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [
{
"name": "com.unity.addressables",
"expression": "1.2.0",
"define": "ADRESSABLES_1_2_0_OR_NEWER"
}
]
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4727f108aded7914e8616b79cb513bee
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,161 @@
using HeurekaGames.AssetHunterPRO.BaseTreeviewImpl.AssetTreeView;
using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
namespace HeurekaGames.AssetHunterPRO
{
[Serializable]
public class AH_BuildInfoManager : ScriptableObject
{
public delegate void BuildInfoSelectionChangedDelegate();
public BuildInfoSelectionChangedDelegate OnBuildInfoSelectionChanged;
[SerializeField] private bool hasTreeviewSelection = false;
[SerializeField] private string chosenFilePath;
[SerializeField] private AH_SerializedBuildInfo chosenBuildInfo;
[SerializeField] AH_BuildInfoTreeView treeView;
[SerializeField] private bool projectDirty;
[SerializeField] private bool projectIsClean;
public AH_BuildInfoTreeView TreeView
{
get
{
return treeView;
}
}
public bool HasSelection
{
get
{
return hasTreeviewSelection;
}
}
public bool ProjectDirty {
get => projectDirty;
set
{
projectDirty = value;
}
}
public void OnEnable()
{
hideFlags = HideFlags.HideAndDontSave;
}
private void UpdateBuildInfoFilePaths()
{
//Create new folder if needed
Directory.CreateDirectory(AH_SerializationHelper.GetBuildInfoFolder());
}
public IList<AH_TreeviewElement> GetTreeViewData()
{
if (treeView != null && treeView.treeElements != null && treeView.treeElements.Count > 0)
return treeView.treeElements;
else
Debug.LogError("Missing Data!!!");
return null;
}
internal void SelectBuildInfo(string filePath)
{
hasTreeviewSelection = false;
chosenFilePath = filePath;
chosenBuildInfo = AH_SerializationHelper.LoadBuildReport(filePath);
Version reportVersion;
if (Version.TryParse(chosenBuildInfo.versionNumber, out reportVersion))
{
if (reportVersion.CompareTo(new Version("2.2.0")) < 0) //If report is older than 2.2.0 (tweaked datamodel in 2.2.0 from saving 'Paths' to saving 'guids')
{
//Change paths to guids
foreach (var item in chosenBuildInfo.AssetListUnSorted)
{
item.ChangePathToGUID();
}
}
}
if (chosenBuildInfo == null)
return;
//Make sure JSON is valid
if (populateBuildReport())
{
hasTreeviewSelection = true;
if (OnBuildInfoSelectionChanged != null)
OnBuildInfoSelectionChanged();
}
else
{
EditorUtility.DisplayDialog(
"JSON Parse error",
"The selected file could not be parsed",
"Ok");
}
}
private bool populateBuildReport()
{
treeView = ScriptableObject.CreateInstance<AH_BuildInfoTreeView>();
bool success = treeView.PopulateTreeView(chosenBuildInfo);
projectIsClean = !treeView.HasUnused();
return success;
}
internal bool IsMergedReport()
{
return chosenBuildInfo.IsMergedReport();
}
internal void RefreshBuildInfo()
{
SelectBuildInfo(chosenFilePath);
}
internal string GetSelectedBuildSize()
{
return AH_Utils.GetSizeAsString((long)chosenBuildInfo.TotalSize);
}
internal string GetSelectedBuildDate()
{
return chosenBuildInfo.dateTime;
}
internal string GetSelectedBuildTarget()
{
return chosenBuildInfo.buildTargetInfo;
}
//Only avaliable in 2018
#if UNITY_2018_1_OR_NEWER
internal List<AH_BuildReportFileInfo> GetReportInfoInfo()
{
return chosenBuildInfo.BuildReportInfoList;
}
#endif
internal AH_SerializedBuildInfo GetSerializedBuildInfo()
{
return chosenBuildInfo;
}
internal bool IsProjectClean()
{
return projectIsClean;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d9fd5489ebed282448ffce56090fefd7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,106 @@
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
using System;
using System.Linq;
namespace HeurekaGames.AssetHunterPRO
{
public class AH_BuildInfoMergerWindow : EditorWindow
{
private static AH_BuildInfoMergerWindow m_window;
private string buildInfoFolder;
private Vector2 scrollPos;
private List<BuildInfoSelection> buildInfoFiles;
[MenuItem("Tools/Asset Hunter PRO/Merge tool")]
[MenuItem("Window/Heureka/Asset Hunter PRO/Merge tool")]
public static void Init()
{
m_window = GetWindow<AH_BuildInfoMergerWindow>("AH Merger", true, typeof(AH_Window));
m_window.titleContent.image = AH_EditorData.Instance.MergerIcon.Icon;
m_window.buildInfoFolder = AH_SerializationHelper.GetBuildInfoFolder();
m_window.updateBuildInfoFiles();
}
private void updateBuildInfoFiles()
{
buildInfoFiles = new List<BuildInfoSelection>();
System.IO.DirectoryInfo directoryInfo = new System.IO.DirectoryInfo(buildInfoFolder);
foreach (var item in directoryInfo.GetFiles("*." + AH_SerializationHelper.BuildInfoExtension).OrderByDescending(val=>val.LastWriteTime))
{
buildInfoFiles.Add(new BuildInfoSelection(item));
}
}
private void OnGUI()
{
if (!m_window)
Init();
scrollPos = EditorGUILayout.BeginScrollView(scrollPos);
Heureka_WindowStyler.DrawGlobalHeader(Heureka_WindowStyler.clr_Dark, "BUILD INFO MERGER");
EditorGUILayout.Space();
EditorGUILayout.HelpBox("Select a folder that contains buildinfo files", MessageType.Info);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Change", GUILayout.ExpandWidth(false)))
{
buildInfoFolder = EditorUtility.OpenFolderPanel("Buildinfo folder", buildInfoFolder, "");
updateBuildInfoFiles();
}
EditorGUILayout.LabelField("Current folder: " + buildInfoFolder);
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
//Show all used types
EditorGUILayout.BeginVertical();
foreach (var item in buildInfoFiles)
{
item.Selected = EditorGUILayout.ToggleLeft(item.BuildInfoFile.Name, item.Selected);
}
EditorGUILayout.Space();
EditorGUI.BeginDisabledGroup(buildInfoFiles.Count(val=>val.Selected==true) < 2);
if (GUILayout.Button("Merge Selected", GUILayout.ExpandWidth(false)))
{
AH_SerializedBuildInfo merged = new AH_SerializedBuildInfo();
foreach (var item in buildInfoFiles.FindAll(val=>val.Selected))
{
merged.MergeWith(item.BuildInfoFile.FullName);
}
merged.SaveAfterMerge();
EditorUtility.DisplayDialog("Merge completed", "A new buildinfo was created by combined existing buildinfos", "Ok");
//Reset
buildInfoFiles.ForEach(val => val.Selected = false);
updateBuildInfoFiles();
}
EditorGUI.EndDisabledGroup();
//Make sure this window has focus to update contents
Repaint();
EditorGUILayout.EndVertical();
EditorGUILayout.EndScrollView();
}
[System.Serializable]
private class BuildInfoSelection
{
public System.IO.FileInfo BuildInfoFile;
public bool Selected = false;
public BuildInfoSelection(System.IO.FileInfo buildInfoFile)
{
this.BuildInfoFile = buildInfoFile;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 49792b455df9c0341a51d3ed9126a9cc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,135 @@

namespace HeurekaGames.AssetHunterPRO
{
using System.Linq;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
#if UNITY_2018_1_OR_NEWER
using UnityEditor.Build.Reporting;
using UnityEditor.Build;
class AH_BuildProcessor : IPreprocessBuildWithReport, IPostprocessBuildWithReport, IProcessSceneWithReport
{
public void OnProcessScene(UnityEngine.SceneManagement.Scene scene, BuildReport report)
{
//For some reason I have to do both recursive, and non-recursive version
string[] dependencies = AssetDatabase.GetDependencies(scene.path, true);
dependencies.ToList().AddRange(AssetDatabase.GetDependencies(scene.path, false));
{
foreach (string dependency in dependencies)
processUsedAsset(scene.path, dependency);
}
}
public void OnPreprocessBuild(BuildReport report)
{
initBuildReport(report.summary.platform, report.summary.outputPath);
}
public void OnPostprocessBuild(BuildReport report)
{
finalizeBuildReport(report);
}
private void finalizeBuildReport(BuildReport report)
{
addBuildReportInfo(report);
//Dont force add special folders (resources etc) in 2018.1 because we have asccess to buildreport
finalizeBuildReport(report.summary.platform);
}
private void addBuildReportInfo(BuildReport report)
{
if(buildInfo != null)
buildInfo.ProcessBuildReport(report);
}
#elif UNITY_5_6_OR_NEWER
using UnityEditor.Build;
class AH_BuildProcessor : IProcessScene, IPreprocessBuild, IPostprocessBuild
{
public void OnPreprocessBuild(BuildTarget target, string path)
{
initBuildReport(target, path);
}
public void OnProcessScene(UnityEngine.SceneManagement.Scene scene)
{
Debug.Log("AH: Processing scene: " + scene.name);
string[] dependencies = AssetDatabase.GetDependencies(scene.path, true);
{
foreach (string dependency in dependencies)
processUsedAsset(scene.path, dependency);
}
}
public void OnPostprocessBuild(BuildTarget target, string path)
{
finalizeBuildReport(target);
}
#endif
//#if UNITY_5_6_OR_NEWER
static AH_SerializedBuildInfo buildInfo;
private bool isProcessing;
//private static bool isGenerating;
private void initBuildReport(BuildTarget platform, string outputPath)
{
//Only start processing if its set in preferences
isProcessing = AH_SettingsManager.Instance.AutoCreateLog /*|| isGenerating*/;
if (isProcessing)
{
Debug.Log("AH: Initiated new buildreport - " + System.DateTime.Now);
buildInfo = new AH_SerializedBuildInfo();
}
else
{
Debug.Log("AH: Build logging not automatically started. Open Asset Hunter preferences if you want it to run");
}
}
private void finalizeBuildReport(BuildTarget target)
{
if (isProcessing)
{
isProcessing = false;
Debug.Log("AH: Finalizing new build report - " + System.DateTime.Now);
buildInfo.FinalizeReport(target);
}
}
/*internal static void GenerateBuild()
{
isGenerating = true;
string path = EditorUtility.SaveFolderPanel("Choose Location of Build", "", "");
BuildPlayerOptions bpOptions = new BuildPlayerOptions();
bpOptions.locationPathName = path + "/AH_GeneratedBuild.exe";
bpOptions.scenes = EditorBuildSettings.scenes.Where(val => val.enabled).Select(val => val.path).ToArray<string>();
bpOptions.target = EditorUserBuildSettings.activeBuildTarget;
bpOptions.targetGroup = EditorUserBuildSettings.selectedBuildTargetGroup;
bpOptions.options = BuildOptions.None;
// Build player.
BuildPipeline.BuildPlayer(bpOptions);
}*/
private void processUsedAsset(string scenePath, string assetPath)
{
if (isProcessing)
buildInfo.AddBuildDependency(scenePath, assetPath);
}
public int callbackOrder { get { return 0; } }
}
//#endif
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 173fd4f00e5547f4bb3c260d9cbb16d5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,26 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//Only avaliable in 2018
#if UNITY_2018_1_OR_NEWER
using UnityEditor.Build.Reporting;
namespace HeurekaGames.AssetHunterPRO
{
[System.Serializable]
public class AH_BuildReportFileInfo
{
public string Path;
public string Role;
public ulong Size;
public AH_BuildReportFileInfo(BuildFile file)
{
this.Path = file.path;
this.Role = file.role;
this.Size = file.size;
}
}
}
#endif

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e55a84cb0bcf0414085a72914a3a9841
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,266 @@
//Only avaliable in 2018
#if UNITY_2018_1_OR_NEWER
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace HeurekaGames.AssetHunterPRO
{
public class AH_BuildReportWindow : EditorWindow
{
private static AH_BuildReportWindow m_window;
private Vector2 scrollPos;
protected AH_BuildInfoManager buildInfoManager;
private AH_BuildReportWindowData data;
//Adding same string multiple times in order to show more green and yellow than orange and red
public static readonly List<string> ColorDotIconList = new List<string>() {
"sv_icon_dot6_pix16_gizmo",
"sv_icon_dot5_pix16_gizmo",
"sv_icon_dot5_pix16_gizmo",
"sv_icon_dot4_pix16_gizmo",
"sv_icon_dot4_pix16_gizmo",
"sv_icon_dot4_pix16_gizmo",
"sv_icon_dot3_pix16_gizmo",
"sv_icon_dot3_pix16_gizmo",
"sv_icon_dot3_pix16_gizmo",
"sv_icon_dot3_pix16_gizmo"};
[MenuItem("Tools/Asset Hunter PRO/Build report")]
[MenuItem("Window/Heureka/Asset Hunter PRO/Build report")]
public static void Init()
{
//Make sure it exists so we can attach this window next to it
AH_Window.GetBuildInfoManager();
bool alreadyExist = (m_window != null);
if(!alreadyExist)
{
m_window = GetWindow<AH_BuildReportWindow>("AH Report", true, typeof(AH_Window));
m_window.titleContent.image = AH_EditorData.Instance.ReportIcon.Icon;
m_window.buildInfoManager = AH_Window.GetBuildInfoManager();
m_window.buildInfoManager.OnBuildInfoSelectionChanged += m_window.OnBuildInfoSelectionChanged;
m_window.populateBuildReportWindowData();
}
}
private void OnBuildInfoSelectionChanged()
{
populateBuildReportWindowData();
}
private void populateBuildReportWindowData()
{
if (buildInfoManager.HasSelection)
{
data = new AH_BuildReportWindowData(buildInfoManager.GetSerializedBuildInfo());
data.SetRelativeValuesForFiles();
}
}
void OnGUI()
{
if (!m_window)
Init();
Heureka_WindowStyler.DrawGlobalHeader(Heureka_WindowStyler.clr_Dark, "BUILD REPORT", AH_Window.VERSION);
if (buildInfoManager == null || buildInfoManager.HasSelection == false)
{
Heureka_WindowStyler.DrawCenteredMessage(m_window, AH_EditorData.Instance.WindowHeaderIcon.Icon, 310f, 110f, "No buildinfo currently loaded in main window");
return;
}
else if (buildInfoManager.IsMergedReport())
{
Heureka_WindowStyler.DrawCenteredMessage(m_window, AH_EditorData.Instance.WindowHeaderIcon.Icon, 366f, 110f, "Buildreport window does not work with merged buildreports");
return;
}
scrollPos = EditorGUILayout.BeginScrollView(scrollPos);
data.OnGUI();
EditorGUILayout.EndScrollView();
}
[System.Serializable]
private class AH_BuildReportWindowData
{
[SerializeField] private ulong buildSize;
[SerializeField] private string buildTarget;
[SerializeField] private string buildDate;
[SerializeField] private List<AH_BuildReportWindowRoleInfo> roleInfoList;
public AH_BuildReportWindowData(AH_SerializedBuildInfo buildInfo)
{
this.buildSize = buildInfo.TotalSize;
this.buildTarget = buildInfo.buildTargetInfo;
this.buildDate = buildInfo.dateTime;
roleInfoList = new List<AH_BuildReportWindowRoleInfo>();
foreach (var item in buildInfo.BuildReportInfoList)
{
//Check if role exists already
if (roleInfoList.Exists(val => val.roleName.Equals(item.Role)))
roleInfoList.First(val => val.roleName.Equals(item.Role)).AddToRoleInfo(item);
//If not, add new roleentry
else
roleInfoList.Add(new AH_BuildReportWindowRoleInfo(item));
}
//Sort roles
IEnumerable<AH_BuildReportWindowRoleInfo> tmp = roleInfoList.OrderByDescending(val => val.combinedRoleSize);
roleInfoList = tmp.ToList();
//Sort elements in roles
foreach (var item in roleInfoList)
{
item.Order();
}
}
internal void OnGUI()
{
if (buildSize <= 0)
{
Heureka_WindowStyler.DrawCenteredMessage(m_window, AH_EditorData.Instance.WindowHeaderIcon.Icon, 462f, 120f, "The selected buildinfo lacks information. It was probably created with older version. Create new with this version");
return;
}
int guiWidth = 260;
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(" Combined Build Size:", Heureka_EditorData.Instance.HeadlineStyle, GUILayout.Width(guiWidth));
EditorGUILayout.LabelField(AH_Utils.GetSizeAsString(buildSize), Heureka_EditorData.Instance.HeadlineStyle);
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(" Build Target:", Heureka_EditorData.Instance.HeadlineStyle, GUILayout.Width(guiWidth));
EditorGUILayout.LabelField(buildTarget, Heureka_EditorData.Instance.HeadlineStyle);
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(" Build Time:", Heureka_EditorData.Instance.HeadlineStyle, GUILayout.Width(guiWidth));
string parsedDate = DateTime.ParseExact(buildDate, AH_SerializationHelper.DateTimeFormat, System.Globalization.CultureInfo.CurrentCulture).ToString();
EditorGUILayout.LabelField(parsedDate, Heureka_EditorData.Instance.HeadlineStyle);
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
foreach (var item in roleInfoList)
{
item.OnGUI();
EditorGUILayout.Space();
}
}
internal void SetRelativeValuesForFiles()
{
//Find the relative value of all items so we can show which files are taking up the most space
//A way to keep track of the sorted values
List<AH_BuildReportWindowFileInfo> tmpList = new List<AH_BuildReportWindowFileInfo>();
foreach (var infoList in roleInfoList)
{
foreach (var fileInfo in infoList.fileInfoList)
{
tmpList.Add(fileInfo);
}
}
List<AH_BuildReportWindowFileInfo> sortedFileInfo = tmpList.OrderByDescending(val => val.size).ToList();
for (int i = 0; i < sortedFileInfo.Count; i++)
{
int groupSize = ColorDotIconList.Count;
//Figure out which icon to show (create 4 groups from sortedlist)
int groupIndex = Mathf.FloorToInt((((float)i / (float)sortedFileInfo.Count) * (float)groupSize));
sortedFileInfo[i].SetFileSizeGroup(groupIndex);
}
}
}
[System.Serializable]
internal class AH_BuildReportWindowRoleInfo
{
[SerializeField] internal ulong combinedRoleSize = 0;
[SerializeField] internal string roleName;
[SerializeField] internal List<AH_BuildReportWindowFileInfo> fileInfoList;
public AH_BuildReportWindowRoleInfo(AH_BuildReportFileInfo item)
{
this.roleName = item.Role;
fileInfoList = new List<AH_BuildReportWindowFileInfo>();
addFile(item);
}
internal void AddToRoleInfo(AH_BuildReportFileInfo item)
{
combinedRoleSize += item.Size;
addFile(item);
}
private void addFile(AH_BuildReportFileInfo item)
{
this.combinedRoleSize += item.Size;
fileInfoList.Add(new AH_BuildReportWindowFileInfo(item));
}
internal void OnGUI()
{
EditorGUILayout.HelpBox(roleName + " combined: " + AH_Utils.GetSizeAsString(combinedRoleSize), MessageType.Info);
foreach (var item in fileInfoList)
{
item.OnGUI();
}
}
internal void Order()
{
IEnumerable<AH_BuildReportWindowFileInfo> tmp = fileInfoList.OrderByDescending(val => val.size);
fileInfoList = tmp.ToList();
}
}
[System.Serializable]
internal class AH_BuildReportWindowFileInfo
{
[SerializeField] internal string fileName;
[SerializeField] internal string path;
[SerializeField] internal ulong size;
[SerializeField] internal string sizeString;
[SerializeField] GUIContent content = new GUIContent();
[SerializeField] int fileSizeGroup = 0;
public AH_BuildReportWindowFileInfo(AH_BuildReportFileInfo item)
{
this.path = item.Path;
this.fileName = System.IO.Path.GetFileName(this.path);
this.size = item.Size;
this.sizeString = AH_Utils.GetSizeAsString(this.size);
content.text = this.fileName;
content.tooltip = this.path;
}
internal void OnGUI()
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(content, GUILayout.MinWidth(300));
EditorGUILayout.LabelField(sizeString, GUILayout.MaxWidth(80));
GUILayout.Label(EditorGUIUtility.IconContent(AH_BuildReportWindow.ColorDotIconList[fileSizeGroup]), GUILayout.MaxHeight(16));
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
}
internal void SetFileSizeGroup(int groupIndex)
{
fileSizeGroup = groupIndex;
}
}
private void OnDestroy()
{
m_window.buildInfoManager.OnBuildInfoSelectionChanged -= m_window.OnBuildInfoSelectionChanged;
}
}
}
#endif

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 12cee7fed09c12246ae59c3f225d3243
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,388 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace HeurekaGames.AssetHunterPRO.BaseTreeviewImpl.DependencyGraph
{
[Serializable]
public class AH_DependencyGraphManager : ScriptableSingleton<AH_DependencyGraphManager>, ISerializationCallbackReceiver
{
[SerializeField] public bool IsDirty = true;
[SerializeField] private TreeViewState treeViewStateFrom;
[SerializeField] private TreeViewState treeViewStateTo;
[SerializeField] private MultiColumnHeaderState multiColumnHeaderStateFrom;
[SerializeField] private MultiColumnHeaderState multiColumnHeaderStateTo;
[SerializeField] private AH_DepGraphTreeviewWithModel TreeViewModelFrom;
[SerializeField] private AH_DepGraphTreeviewWithModel TreeViewModelTo;
[SerializeField] private Dictionary<string, List<string>> referencedFrom = new Dictionary<string, List<string>>();
[SerializeField] private Dictionary<string, List<string>> referenceTo = new Dictionary<string, List<string>>();
#region serializationHelpers
[SerializeField] private List<string> _keysFrom = new List<string>();
[SerializeField] private List<AH_WrapperList> _wrapperValuesFrom = new List<AH_WrapperList>();
[SerializeField] private List<string> _keysTo = new List<string>();
[SerializeField] private List<AH_WrapperList> _wrapperValuesTo = new List<AH_WrapperList>();
#endregion
[SerializeField] private string selectedAssetGUID = "";
[SerializeField] private string selectedAssetObjectName = "";
[SerializeField] private UnityEngine.Object selectedAssetObject;
[SerializeField] private List<string> selectionHistory = new List<string>();
[SerializeField] private int selectionHistoryIndex = 0;
private bool lockedSelection;
public bool LockedSelection
{
get { return lockedSelection; }
set
{
lockedSelection = value;
//Update currently selected
if (lockedSelection == false)
{
requiresRefresh = true;
UpdateSelectedAsset(Selection.activeObject);
}
}
}
public bool TraversingHistory { get; set; }
//Force window to refresh selection
private bool requiresRefresh;
//We clear history when project changes, as there are problems in identifying if history points to deleted assets
internal void ResetHistory()
{
var obsoleteAssets = selectionHistory.FindAll(x => AssetDatabase.LoadMainAssetAtPath(x) == null);
//Remove the objets that are no longer in asset db
selectionHistory.RemoveAll(x => obsoleteAssets.Contains(x));
var duplicateCount = obsoleteAssets.Count;
for (int i = selectionHistory.Count - 1; i >= 0; i--)
{
//Find identical IDs directly after each other
if (i > 0 && selectionHistory[i] == selectionHistory[i - 1])
{
selectionHistory.RemoveAt(i);
duplicateCount++;
}
}
//Reset history index to match new history
selectionHistoryIndex -= duplicateCount;
}
public string SearchString
{
get
{
return treeViewStateFrom.searchString;
}
set
{
var tmp = treeViewStateFrom.searchString;
treeViewStateFrom.searchString = treeViewStateTo.searchString = value;
if (tmp != value)
{
TreeViewModelTo.Reload();
TreeViewModelFrom.Reload();
}
}
}
//Return selected asset
public UnityEngine.Object SelectedAsset { get { return selectedAssetObject; } }
public void OnEnable()
{
hideFlags = HideFlags.HideAndDontSave;
}
public void Initialize(SearchField searchField, Rect multiColumnTreeViewRect)
{
int referenceID = 0;
initTreeview(ref treeViewStateFrom, ref multiColumnHeaderStateFrom, multiColumnTreeViewRect, ref TreeViewModelFrom, searchField, referencedFrom, selectedAssetGUID, ref referenceID);
initTreeview(ref treeViewStateTo, ref multiColumnHeaderStateTo, multiColumnTreeViewRect, ref TreeViewModelTo, searchField, referenceTo, selectedAssetGUID, ref referenceID);
requiresRefresh = false;
}
public void RefreshReferenceGraph()
{
referenceTo = new Dictionary<string, List<string>>();
referencedFrom = new Dictionary<string, List<string>>();
var paths = AssetDatabase.GetAllAssetPaths();
var pathCount = paths.Length;
for (int i = 0; i < pathCount; i++)
{
var path = paths[i];
if (AssetDatabase.IsValidFolder(path) || !path.StartsWith("Assets")) //Slow, could be done recusively
continue;
if (EditorUtility.DisplayCancelableProgressBar("Creating Reference Graph", path, ((float)i / (float)pathCount)))
{
referenceTo = new Dictionary<string, List<string>>();
referencedFrom = new Dictionary<string, List<string>>();
break;
}
var allRefs = AssetDatabase.GetDependencies(path, false);
string assetPathGuid = AssetDatabase.AssetPathToGUID(path);
List<string> newList = allRefs.Where(val => val != path).Select(val => AssetDatabase.AssetPathToGUID(val)).ToList();
//Store everything reference by this asset
if (newList.Count > 0)
referenceTo.Add(assetPathGuid, newList);
//Foreach asset refenced by this asset, store the connection
foreach (var reference in allRefs)
{
string refGuid = AssetDatabase.AssetPathToGUID(reference);
if (!referencedFrom.ContainsKey(refGuid))
referencedFrom.Add(refGuid, new List<string>());
referencedFrom[refGuid].Add(assetPathGuid);
}
}
IsDirty = false;
EditorUtility.ClearProgressBar();
}
private void initTreeview(ref TreeViewState _treeViewState, ref MultiColumnHeaderState _headerState, Rect _rect, ref AH_DepGraphTreeviewWithModel _treeView, SearchField _searchField, Dictionary<string, List<string>> referenceDict, string assetGUID, ref int referenceID)
{
bool hasValidReferences;
var treeModel = new TreeModel<AH_DepGraphElement>(getTreeViewData(referenceDict, assetGUID, out hasValidReferences, ref referenceID));
// Check if it already exists (deserialized from window layout file or scriptable object)
if (_treeViewState == null)
_treeViewState = new TreeViewState();
bool firstInit = _headerState == null;
var headerState = AH_DepGraphTreeviewWithModel.CreateDefaultMultiColumnHeaderState(_rect.width);
if (MultiColumnHeaderState.CanOverwriteSerializedFields(_headerState, headerState))
MultiColumnHeaderState.OverwriteSerializedFields(_headerState, headerState);
_headerState = headerState;
var multiColumnHeader = new AH_DepGraphHeader(_headerState);
//if (firstInit)
multiColumnHeader.ResizeToFit();
_treeView = new AH_DepGraphTreeviewWithModel(_treeViewState, multiColumnHeader, treeModel);
_searchField.downOrUpArrowKeyPressed += _treeView.SetFocusAndEnsureSelectedItem;
}
internal void UpdateTreeData(ref AH_DepGraphTreeviewWithModel _treeView, Dictionary<string, List<string>> referenceDict, string assetGUID, ref int referenceID)
{
bool hasValidReferences;
_treeView.treeModel.SetData(getTreeViewData(referenceDict, assetGUID, out hasValidReferences, ref referenceID));
}
internal bool HasCache()
{
return referencedFrom.Count > 0 && referenceTo.Count > 0;
}
internal bool HasHistory(int direction, out string tooltip)
{
int testIndex = selectionHistoryIndex + direction;
bool validIndex = (testIndex >= 0 && testIndex < selectionHistory.Count);
tooltip = validIndex ? (AssetDatabase.LoadMainAssetAtPath(selectionHistory[testIndex])?.name) : String.Empty;
//Validate that history contains that index
return (testIndex >= 0 && testIndex < selectionHistory.Count);
}
public bool HasSelection
{
get
{
return !string.IsNullOrEmpty(selectedAssetGUID);
}
}
internal bool RequiresRefresh()
{
return requiresRefresh;
}
internal AH_DepGraphTreeviewWithModel GetTreeViewTo()
{
return TreeViewModelTo;
}
internal AH_DepGraphTreeviewWithModel GetTreeViewFrom()
{
return TreeViewModelFrom;
}
internal Dictionary<string, List<string>> GetReferencesTo()
{
return referenceTo;
}
internal string GetSelectedName()
{
return selectedAssetObjectName;
}
internal Dictionary<string, List<string>> GetReferencesFrom()
{
return referencedFrom;
}
public IList<AH_DepGraphElement> getTreeViewData(Dictionary<string, List<string>> referenceDict, string assetGUID, out bool success, ref int referenceID)
{
var treeElements = new List<AH_DepGraphElement>();
int depth = -1;
var root = new AH_DepGraphElement("Root", depth, -1, "");
treeElements.Add(root);
Stack<string> referenceQueue = new Stack<string>(); //Since we are creating a tree we want the same asset to be referenced in any branch, but we do NOT want circular references
var references = referenceDict.ContainsKey(assetGUID) ? referenceDict[assetGUID] : null;
if (references != null)
{
foreach (var item in references)
{
addElement(treeElements, referenceDict, item, ref depth, ref referenceID, ref referenceQueue);
}
}
success = treeElements.Count > 2;//Did we find any references (Contains more thatn 'root' and 'self')
TreeElementUtility.ListToTree(treeElements);
EditorUtility.ClearProgressBar();
return treeElements;
}
private void addElement(List<AH_DepGraphElement> treeElements, Dictionary<string, List<string>> referenceDict, string assetGUID, ref int depth, ref int id, ref Stack<string> referenceStack)
{
var path = AssetDatabase.GUIDToAssetPath(assetGUID);
var pathSplit = path.Split('/');
if (referenceStack.Contains(path))
return;
depth++;
treeElements.Add(new AH_DepGraphElement(/*path*/pathSplit.Last(), depth, id++, path));
referenceStack.Push(path); //Add to stack to keep track of circular refs in branch
var references = referenceDict.ContainsKey(assetGUID) ? referenceDict[assetGUID] : null;
if (references != null)
foreach (var item in references)
{
addElement(treeElements, referenceDict, item, ref depth, ref id, ref referenceStack);
}
depth--;
referenceStack.Pop();
}
public void SelectPreviousFromHistory()
{
selectionHistoryIndex--;
SelectFromHistory(selectionHistoryIndex);
}
public void SelectNextFromHistory()
{
selectionHistoryIndex++;
SelectFromHistory(selectionHistoryIndex);
}
private void SelectFromHistory(int index)
{
TraversingHistory = true;
Selection.activeObject = AssetDatabase.LoadMainAssetAtPath(selectionHistory[selectionHistoryIndex]);
}
internal void UpdateSelectedAsset(UnityEngine.Object activeObject)
{
/*if (activeObject == null)
return;*/
var invalid = activeObject == null || AssetDatabase.IsValidFolder(AssetDatabase.GetAssetPath(Selection.activeObject));
if (invalid)
{
selectedAssetGUID = selectedAssetObjectName = String.Empty;
selectedAssetObject = null;
}
else
{
selectedAssetGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(activeObject));
selectedAssetObjectName = activeObject.name;
selectedAssetObject = AssetDatabase.LoadMainAssetAtPath(AssetDatabase.GUIDToAssetPath(selectedAssetGUID));
if (!TraversingHistory)
addToHistory();
}
TraversingHistory = false;
}
private void addToHistory()
{
//Remove the part of the history branch that are no longer needed
if (selectionHistory.Count - 1 > selectionHistoryIndex)
{
selectionHistory.RemoveRange(selectionHistoryIndex, selectionHistory.Count - selectionHistoryIndex);
}
var path = AssetDatabase.GUIDToAssetPath(selectedAssetGUID);
if (selectionHistory.Count == 0 || path != selectionHistory.Last())
{
selectionHistory.Add(AssetDatabase.GUIDToAssetPath(selectedAssetGUID));
selectionHistoryIndex = selectionHistory.Count - 1;
}
}
public void OnBeforeSerialize()
{
_keysFrom.Clear();
_wrapperValuesFrom.Clear();
foreach (var kvp in referencedFrom)
{
_keysFrom.Add(kvp.Key);
_wrapperValuesFrom.Add(new AH_WrapperList(kvp.Value));
}
_keysTo.Clear();
_wrapperValuesTo.Clear();
foreach (var kvp in referenceTo)
{
_keysTo.Add(kvp.Key);
_wrapperValuesTo.Add(new AH_WrapperList(kvp.Value));
}
}
public void OnAfterDeserialize()
{
referencedFrom = new Dictionary<string, List<string>>();
for (int i = 0; i != Math.Min(_keysFrom.Count, _wrapperValuesFrom.Count); i++)
referencedFrom.Add(_keysFrom[i], _wrapperValuesFrom[i].list);
referenceTo = new Dictionary<string, List<string>>();
for (int i = 0; i != Math.Min(_keysTo.Count, _wrapperValuesTo.Count); i++)
referenceTo.Add(_keysTo[i], _wrapperValuesTo[i].list);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 094127970d2184049b1ce37e1eb2896a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,408 @@
using System;
using System.Linq;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using HeurekaGames.AssetHunterPRO.BaseTreeviewImpl.DependencyGraph;
using UnityEditorInternal;
namespace HeurekaGames.AssetHunterPRO
{
public class AH_DependencyGraphWindow : EditorWindow
{
private static AH_DependencyGraphWindow window;
[SerializeField] public AH_DependencyGraphManager dependencyGraphManager;
private GUIContent lockedReference;
private GUIContent unlockedReference;
private GUIContent contentToggleRefsTo;
private GUIContent contentToggleRefsFrom;
[SerializeField] GUIContent guiContentRefresh;
[SerializeField] SearchField searchField;
private bool initialized;
// Editor gameObjectEditor;
private UnityEngine.Object previewObject;
//UI Rect
Vector2 uiStartPos = new Vector2(10, 50);
[SerializeField] private bool seeRefsToInProject;
[SerializeField] private bool seeRefsFromInProject;
private Texture2D previewTexture;
private static readonly string WINDOWNAME = "AH Dependency Graph";
//Add menu named "Dependency Graph" to the window menu
[UnityEditor.MenuItem("Tools/Asset Hunter PRO/Dependency Graph", priority = AH_Window.WINDOWMENUITEMPRIO + 1)]
[UnityEditor.MenuItem("Window/Heureka/Asset Hunter PRO/Dependency Graph _%#h", priority = AH_Window.WINDOWMENUITEMPRIO + 1)]
public static void OpenDependencyGraph()
{
Init();
}
public static void Init()
{
window = AH_DependencyGraphWindow.GetWindow<AH_DependencyGraphWindow>(WINDOWNAME, true);
if (window.dependencyGraphManager == null)
window.dependencyGraphManager = AH_DependencyGraphManager.instance;
window.initializeGUIContent();
}
public static void Init(Docker.DockPosition dockPosition = Docker.DockPosition.Right)
{
Init();
AH_Window[] mainWindows = Resources.FindObjectsOfTypeAll<AH_Window>();
if (mainWindows.Length != 0)
{
HeurekaGames.Docker.Dock(mainWindows[0], window, dockPosition);
}
}
private void OnEnable()
{
Selection.selectionChanged += OnSelectionChanged;
EditorApplication.projectChanged += EditorApplication_projectChanged;
EditorApplication.projectWindowItemOnGUI += EditorApplication_ProjectWindowItemCallback;
contentToggleRefsFrom = new GUIContent(EditorGUIUtility.IconContent("sv_icon_dot9_sml"));
contentToggleRefsFrom.text = "Is dependency";
contentToggleRefsTo = new GUIContent(EditorGUIUtility.IconContent("sv_icon_dot12_sml"));
contentToggleRefsTo.text = "Has dependencies";
lockedReference = new GUIContent()
{
tooltip = "Target Asset is locked, click to unlock",
image = EditorGUIUtility.IconContent("LockIcon-On").image
};
unlockedReference = new GUIContent()
{
tooltip = "Target Asset is unlocked, click to lock",
image = EditorGUIUtility.IconContent("LockIcon").image
};
seeRefsToInProject = EditorPrefs.GetBool("AHP_seeRefsToInProject", true);
seeRefsFromInProject = EditorPrefs.GetBool("AHP_seeRefsFromInProject", true);
}
void EditorApplication_ProjectWindowItemCallback(string guid, Rect r)
{
//If nothing references this asset, ignore it
if (!seeRefsFromInProject && !seeRefsToInProject)
return;
var frame = new Rect(r);
frame.x += frame.width;
if (seeRefsFromInProject && dependencyGraphManager!=null && dependencyGraphManager.GetReferencesFrom().ContainsKey(guid))
{
frame.x += -12;
frame.width += 10f;
GUI.Label(frame, contentToggleRefsFrom.image, EditorStyles.miniLabel);
}
if (seeRefsToInProject && dependencyGraphManager.GetReferencesTo().ContainsKey(guid))
{
frame.x += -12f;
frame.width += 10f;
GUI.Label(frame, contentToggleRefsTo.image, EditorStyles.miniLabel);
}
}
private void EditorApplication_projectChanged()
{
dependencyGraphManager.IsDirty = true;
dependencyGraphManager.ResetHistory();
}
private void OnGUI()
{
initIfNeeded();
doHeader();
if (dependencyGraphManager != null)
{
//If window has no cached data
if (!dependencyGraphManager.HasCache())
{
Heureka_WindowStyler.DrawCenteredMessage(window, AH_EditorData.Instance.RefFromWhiteIcon.Icon, 240f, 110f, "No Graph" + Environment.NewLine + "Build Graph");
EditorGUILayout.BeginVertical();
GUILayout.FlexibleSpace();
Color origClr = GUI.backgroundColor;
GUI.backgroundColor = Heureka_WindowStyler.clr_Red;
if (GUILayout.Button("Build Graph", GUILayout.Height(40)))
{
dependencyGraphManager.RefreshReferenceGraph();
}
GUI.backgroundColor = origClr;
EditorGUILayout.EndVertical();
return;
}
if (dependencyGraphManager.HasSelection)
{
using (new EditorGUILayout.VerticalScope("box"))
{
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button($"{dependencyGraphManager.GetSelectedName()}" + (dependencyGraphManager.LockedSelection ? " (Locked)" : ""), GUILayout.ExpandWidth(true)))
EditorGUIUtility.PingObject(dependencyGraphManager.SelectedAsset);
if (GUILayout.Button(dependencyGraphManager.LockedSelection ? lockedReference : unlockedReference, EditorStyles.boldLabel, GUILayout.ExpandWidth(false)))
dependencyGraphManager.LockedSelection = !dependencyGraphManager.LockedSelection;
}
drawPreview();
}
var viewFrom = dependencyGraphManager.GetTreeViewFrom();
bool isValidFrom = (viewFrom?.treeModel?.numberOfDataElements > 1);
var viewTo = dependencyGraphManager.GetTreeViewTo();
bool isValidTo = (viewTo?.treeModel?.numberOfDataElements > 1);
using (new EditorGUILayout.VerticalScope("box", GUILayout.ExpandWidth(true)))
{
using (new EditorGUILayout.HorizontalScope("box", GUILayout.ExpandWidth(true)))
{
GUILayout.Label(AH_EditorData.Instance.RefFromIcon.Icon, GUILayout.Width(32), GUILayout.Height(32));
using (new EditorGUILayout.VerticalScope(GUILayout.Height(32)))
{
GUILayout.FlexibleSpace();
EditorGUILayout.LabelField($"A dependency of {(isValidFrom ? (viewFrom.treeModel.root.children.Count()) : 0)}", EditorStyles.boldLabel);
GUILayout.FlexibleSpace();
}
}
if (isValidFrom)
drawAssetList(dependencyGraphManager.GetTreeViewFrom());
using (new EditorGUILayout.HorizontalScope("box", GUILayout.ExpandWidth(true)))
{
GUILayout.Label(AH_EditorData.Instance.RefToIcon.Icon, GUILayout.Width(32), GUILayout.Height(32));
using (new EditorGUILayout.VerticalScope(GUILayout.Height(32)))
{
GUILayout.FlexibleSpace();
EditorGUILayout.LabelField($"Depends on {(isValidTo ? (viewTo.treeModel.root.children.Count()) : 0)}", EditorStyles.boldLabel);
GUILayout.FlexibleSpace();
}
}
if (isValidTo)
drawAssetList(dependencyGraphManager.GetTreeViewTo());
//Force flexible size here to make sure the preview area doesn't fill entire window
if (!isValidTo && !isValidFrom)
GUILayout.FlexibleSpace();
}
}
else
{
Heureka_WindowStyler.DrawCenteredMessage(window, AH_EditorData.Instance.RefFromWhiteIcon.Icon, 240f, 110f, "No selection" + Environment.NewLine + "Select asset in project view");
}
}
doFooter();
//Make sure this window has focus to update contents
Repaint();
}
private void drawPreview()
{
if (dependencyGraphManager.SelectedAsset != null)
{
var old = previewObject;
previewObject = dependencyGraphManager.SelectedAsset;
//if (previewObject != old)
{
previewTexture = AssetPreview.GetAssetPreview(previewObject); //Asnyc, so we have to do this each frame
if (previewTexture == null)
previewTexture = AssetPreview.GetMiniThumbnail(previewObject);
}
using (new EditorGUILayout.VerticalScope("box"))
{
EditorGUILayout.BeginHorizontal();
drawHistoryButton(-1);
GUILayout.FlexibleSpace();
if (GUILayout.Button(previewTexture, EditorStyles.boldLabel, /*GUILayout.Width(64),*/ GUILayout.MaxHeight(64), GUILayout.ExpandWidth(true)))
{
EditorGUIUtility.PingObject(previewObject);
}
GUILayout.FlexibleSpace();
drawHistoryButton(1);
EditorGUILayout.EndHorizontal();
}
}
}
private void drawHistoryButton(int direction)
{
if (!dependencyGraphManager.LockedSelection)
{
var style = new GUIStyle(GUI.skin.label) { alignment = TextAnchor.MiddleCenter };
string tooltip;
bool validDirection = dependencyGraphManager.HasHistory(direction, out tooltip);
EditorGUI.BeginDisabledGroup(!validDirection);
var content = new GUIContent(validDirection ? direction == -1 ? "<" : ">" : string.Empty);
if (!string.IsNullOrEmpty(tooltip))
content.tooltip = tooltip;
if (GUILayout.Button(content, style, GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(false), GUILayout.Width(12)))
{
if (direction == -1)
dependencyGraphManager.SelectPreviousFromHistory();
else if (direction == 1)
dependencyGraphManager.SelectNextFromHistory();
else
Debug.LogWarning("Wrong integer. You must select -1 or 1");
}
EditorGUI.EndDisabledGroup();
}
}
private void initIfNeeded()
{
if (!dependencyGraphManager || !window)
Init();
if (dependencyGraphManager.RequiresRefresh())
OnSelectionChanged();
//We dont need to do stuff when in play mode
if (dependencyGraphManager && !initialized)
{
if (searchField == null)
searchField = new SearchField();
dependencyGraphManager.Initialize(searchField, multiColumnTreeViewRect);
initialized = true;
InternalEditorUtility.RepaintAllViews();
}
//This is an (ugly) fix to make sure we dotn loose our icons due to some singleton issues after play/stop
if (guiContentRefresh.image == null)
initializeGUIContent();
}
private void initializeGUIContent()
{
titleContent = new GUIContent(WINDOWNAME, AH_EditorData.Instance.RefFromIcon.Icon);
guiContentRefresh = new GUIContent(AH_EditorData.Instance.RefreshIcon.Icon, "Refresh data");
}
private void doFooter()
{
if (dependencyGraphManager != null)
{
if (!dependencyGraphManager.HasSelection)
GUILayout.FlexibleSpace();
GUIContent RefreshGUIContent = new GUIContent(guiContentRefresh);
Color origColor = GUI.color;
if (dependencyGraphManager.IsDirty)
{
GUI.color = Heureka_WindowStyler.clr_Red;
RefreshGUIContent.tooltip = String.Format("{0}{1}", RefreshGUIContent.tooltip, " (Project has changed which means that treeview is out of date)");
}
if (AH_UIUtilities.DrawSelectionButton(RefreshGUIContent))
dependencyGraphManager.RefreshReferenceGraph();
GUI.color = origColor;
EditorGUILayout.BeginHorizontal();
EditorGUI.BeginChangeCheck();
seeRefsToInProject = GUILayout.Toggle(seeRefsToInProject, contentToggleRefsTo);
seeRefsFromInProject = GUILayout.Toggle(seeRefsFromInProject, contentToggleRefsFrom);
//Do we need to repaint projewct view?
if (EditorGUI.EndChangeCheck())
{
EditorPrefs.SetBool("AHP_seeRefsToInProject", seeRefsToInProject);
EditorPrefs.SetBool("AHP_seeRefsFromInProject", seeRefsFromInProject);
InternalEditorUtility.RepaintAllViews();
}
EditorGUILayout.EndHorizontal();
}
}
private void doHeader()
{
Heureka_WindowStyler.DrawGlobalHeader(Heureka_WindowStyler.clr_lBlue, WINDOWNAME);
bool hasReferenceGraph = (dependencyGraphManager != null);
if (hasReferenceGraph)
{
if (dependencyGraphManager.HasSelection && dependencyGraphManager.HasCache())
{
EditorGUILayout.BeginHorizontal(GUILayout.Height(AH_Window.ButtonMaxHeight));
doSearchBar(searchBar);
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
}
}
}
private void drawAssetList(TreeView view)
{
if (view != null)
{
var rect = EditorGUILayout.BeginVertical();
GUILayout.FlexibleSpace();
view.OnGUI(rect);
EditorGUILayout.EndVertical();
}
}
/*private bool doSelectionButton(GUIContent content)
{
GUIContent btnContent = new GUIContent(content);
if (AH_SettingsManager.Instance.HideButtonText)
btnContent.text = null;
return GUILayout.Button(btnContent, GUILayout.MaxHeight(AH_SettingsManager.Instance.HideButtonText ? AH_Window.ButtonMaxHeight * 2f : AH_Window.ButtonMaxHeight));
}*/
void doSearchBar(Rect rect)
{
if (searchField != null)
dependencyGraphManager.SearchString = searchField.OnGUI(rect, dependencyGraphManager.SearchString);
}
private void OnSelectionChanged()
{
if (!dependencyGraphManager.LockedSelection)
{
dependencyGraphManager.UpdateSelectedAsset((Selection.activeObject) ? Selection.activeObject : null);
initialized = false;
}
}
Rect searchBar
{
get { return new Rect(uiStartPos.x + AH_Window.ButtonMaxHeight, uiStartPos.y - (AH_Window.ButtonMaxHeight + 6), position.width - (uiStartPos.x * 2) - AH_Window.ButtonMaxHeight * 2, AH_Window.ButtonMaxHeight); }
}
Rect multiColumnTreeViewRect
{
get
{
Rect newRect = new Rect(uiStartPos.x, uiStartPos.y + 20 + (AH_SettingsManager.Instance.HideButtonText ? 20 : 0), position.width - (uiStartPos.x * 2), position.height - 90 - (AH_SettingsManager.Instance.HideButtonText ? 20 : 0));
return newRect;
}
}
private void OnDisable()
{
Selection.selectionChanged -= OnSelectionChanged;
EditorApplication.projectChanged -= EditorApplication_projectChanged;
EditorApplication.projectWindowItemOnGUI -= EditorApplication_ProjectWindowItemCallback;
}
private void OnDestroy()
{
DestroyImmediate(dependencyGraphManager);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 38e00993adc35ec42a114a8fc65532f0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,197 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace HeurekaGames.AssetHunterPRO
{
[Serializable]
public class AH_DuplicateDataManager : ScriptableSingleton<AH_DuplicateDataManager>, ISerializationCallbackReceiver
{
[Serializable]
public class DuplicateAssetData
{
public List<string> Guids;
private List<string> paths;
private Texture2D preview;
public Texture2D Preview
{
get
{
if (preview != null)
return preview;
else
{
var loadedObj = AssetDatabase.LoadMainAssetAtPath(Paths[0]);
return preview = AssetPreview.GetAssetPreview(loadedObj);
}
}
}
public List<string> Paths
{
get
{
if (paths != null)
return paths;
else
return this.paths = this.Guids.Select(x => AssetDatabase.GUIDToAssetPath(x)).ToList();
}
}
public DuplicateAssetData(List<string> guids)
{
this.Guids = guids;
}
}
[SerializeField] public bool IsDirty = true;
public bool RequiresScrollviewRebuild { get; internal set; }
public bool HasCache { get; private set; }
[SerializeField] private Dictionary<string, DuplicateAssetData> duplicateDict = new Dictionary<string, DuplicateAssetData>();
#region serializationHelpers
[SerializeField] private List<string> _duplicateDictKeys = new List<string>();
[SerializeField] private List<DuplicateAssetData> _duplicateDictValues = new List<DuplicateAssetData>();
public Dictionary<string, DuplicateAssetData> Entries { get {return duplicateDict; } }
#endregion
internal bool HasDuplicates()
{
return duplicateDict.Count > 0;
}
public void OnBeforeSerialize()
{
_duplicateDictKeys.Clear();
_duplicateDictValues.Clear();
foreach (var kvp in duplicateDict)
{
_duplicateDictKeys.Add(kvp.Key);
_duplicateDictValues.Add(kvp.Value);
}
}
public void OnAfterDeserialize()
{
duplicateDict = new Dictionary<string, DuplicateAssetData>();
for (int i = 0; i != Math.Min(_duplicateDictKeys.Count, _duplicateDictValues.Count); i++)
duplicateDict.Add(_duplicateDictKeys[i], new DuplicateAssetData(_duplicateDictValues[i].Guids));
}
internal void RefreshData()
{
//We need to analyze the scrollview to optimize how we draw it
RequiresScrollviewRebuild = true;
duplicateDict = new Dictionary<string, DuplicateAssetData>();
var hashDict = new Dictionary<string, List<string>>();
var paths = AssetDatabase.GetAllAssetPaths();
var pathCount = paths.Length;
using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
{
string assetPathGuid;
string hash;
UnityEngine.Object LoadedObj;
int maxReadCount = 30;//We dont need to read every line using streamreader. We only need the m_name property, and that comes early in the file
int lineCounter = 0;
for (int i = 0; i < pathCount; i++)
{
var path = paths[i];
if (AssetDatabase.IsValidFolder(path) || !path.StartsWith("Assets")) //Slow, could be done recusively
continue;
if (EditorUtility.DisplayCancelableProgressBar("Finding duplicates", path, ((float)i / (float)pathCount)))
{
duplicateDict = new Dictionary<string, DuplicateAssetData>();
break;
}
assetPathGuid = AssetDatabase.AssetPathToGUID(path);
LoadedObj = AssetDatabase.LoadMainAssetAtPath(path);
string line = "";
bool foundName = false;
if (path.EndsWith("LightingData.asset") || path.Contains("NavMesh"))
Debug.Log("LOOK HERE");
if (LoadedObj != null)
{
try
{
using (FileStream stream = File.OpenRead(path))
{
//We need to loop through certain native types (such as materials) to remove name from metadata - if we dont they wont have same hash
if (AssetDatabase.IsNativeAsset(LoadedObj) && !LoadedObj.GetType().IsSubclassOf(typeof(ScriptableObject)))
{
string appendString = "";
using (StreamReader sr = new StreamReader(stream))
{
//bool foundFileName = false;
lineCounter = 0;
while (!sr.EndOfStream)
{
lineCounter++;
if (lineCounter >= maxReadCount)
appendString += sr.ReadToEnd();
else
{
line = sr.ReadLine();
foundName = line.Contains(LoadedObj.name);
if (!foundName)//we want to ignore the m_name property, since that modifies the hashvalue
appendString += line;
else
{
appendString += sr.ReadToEnd();
}
}
}
}
hash = BitConverter.ToString(System.Text.UnicodeEncoding.Unicode.GetBytes(appendString));
//hash = appendString.GetHashCode().ToString();
}
else
{
hash = BitConverter.ToString(md5.ComputeHash(stream));
}
if (!hashDict.ContainsKey(hash))
hashDict.Add(hash, new List<string>() { assetPathGuid });
else
hashDict[hash].Add(assetPathGuid);
}
}
catch (Exception e)
{
Debug.LogError(e);
}
}
}
foreach (var pair in hashDict)
{
if (pair.Value.Count > 1)
{
duplicateDict.Add(pair.Key, new DuplicateAssetData(pair.Value));
}
}
IsDirty = false;
HasCache = true;
EditorUtility.ClearProgressBar();
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 932600e0df520a746a6b3c38609a0bc4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,237 @@

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
namespace HeurekaGames.AssetHunterPRO
{
public class AH_DuplicatesWindow : EditorWindow
{
private static readonly string WINDOWNAME = "AH Duplicates";
private static AH_DuplicatesWindow window;
private AH_DuplicateDataManager duplicateDataManager;
private Vector2 scrollPosition;
private int scrollStartIndex;
private GUIContent guiContentRefresh;
private GUIContent buttonSelectContent;
private GUIContent labelBtnContent;
private GUIStyle labelBtnStyle;
private List<float> scrollviewPositionList = new List<float>();
private Rect scrollArea;
private int scrollEndIndex;
//Add menu named "Dependency Graph" to the window menu
[UnityEditor.MenuItem("Tools/Asset Hunter PRO/Find Duplicates")]
[UnityEditor.MenuItem("Window/Heureka/Asset Hunter PRO/Find Duplicates")]
public static void OpenDuplicatesView()
{
Init();
}
private void OnEnable()
{
//Selection.selectionChanged += OnSelectionChanged;
EditorApplication.projectChanged += EditorApplication_projectChanged;
}
private void OnDisable()
{
EditorApplication.projectChanged -= EditorApplication_projectChanged;
}
private void OnGUI()
{
initIfNeeded();
doHeader();
if (duplicateDataManager != null)
{
//If window has no cached data
if (!duplicateDataManager.HasCache)
{
Heureka_WindowStyler.DrawCenteredMessage(window, AH_EditorData.Instance.DuplicateWhiteIcon.Icon, 240f, 110f, "No data" + Environment.NewLine + "Find duplicates");
EditorGUILayout.BeginVertical();
GUILayout.FlexibleSpace();
Color origClr = GUI.backgroundColor;
GUI.backgroundColor = Color.red;
if (GUILayout.Button("Find Duplicates", GUILayout.Height(40)))
{
duplicateDataManager.RefreshData();
}
GUI.backgroundColor = origClr;
EditorGUILayout.EndVertical();
return;
}
else
{
if (!duplicateDataManager.HasDuplicates())
{
Heureka_WindowStyler.DrawCenteredMessage(window, AH_EditorData.Instance.DuplicateWhiteIcon.Icon, 240f, 110f, "Hurray" + Environment.NewLine + "No duplicates assets" + Environment.NewLine + "in project :)");
GUILayout.FlexibleSpace();
}
else
doBody();
}
}
doFooter();
}
public static void Init()
{
window = AH_DuplicatesWindow.GetWindow<AH_DuplicatesWindow>(WINDOWNAME, true);
if (window.duplicateDataManager == null)
window.duplicateDataManager = AH_DuplicateDataManager.instance;
window.initializeGUIContent();
}
public static void Init(Docker.DockPosition dockPosition = Docker.DockPosition.Right)
{
Init();
AH_Window[] mainWindows = Resources.FindObjectsOfTypeAll<AH_Window>();
if (mainWindows.Length != 0)
{
HeurekaGames.Docker.Dock(mainWindows[0], window, dockPosition);
}
}
private void initIfNeeded()
{
if (!duplicateDataManager || !window)
Init();
//This is an (ugly) fix to make sure we dotn loose our icons due to some singleton issues after play/stop
if (guiContentRefresh.image == null)
initializeGUIContent();
}
private void initializeGUIContent()
{
titleContent = new GUIContent(WINDOWNAME, AH_EditorData.Instance.DuplicateIcon.Icon);
guiContentRefresh = new GUIContent(AH_EditorData.Instance.RefreshIcon.Icon, "Refresh data");
buttonSelectContent = new GUIContent() { };
labelBtnStyle = new GUIStyle(EditorStyles.label);
labelBtnStyle.border = new RectOffset(0, 0, 0, 0);
labelBtnContent = new GUIContent();
}
private void EditorApplication_projectChanged()
{
duplicateDataManager.IsDirty = true;
}
private void doHeader()
{
Heureka_WindowStyler.DrawGlobalHeader(Heureka_WindowStyler.clr_Red, WINDOWNAME);
}
private void doBody()
{
if(duplicateDataManager.RequiresScrollviewRebuild)
scrollviewPositionList = new List<float>();
using (EditorGUILayout.ScrollViewScope scrollview = new EditorGUILayout.ScrollViewScope(scrollPosition))
{
scrollPosition = scrollview.scrollPosition;
//Bunch of stuff to figure which guielements we want to draw inside scrollview (We dont want to draw every single element, only the ones that are infact inside scrollview)
if (Event.current.type == EventType.Layout)
{
scrollStartIndex = scrollviewPositionList.FindLastIndex(x => x < scrollPosition.y);
if (scrollStartIndex == -1) scrollStartIndex = 0;
float scrollMaxY = scrollPosition.y + scrollArea.height;
scrollEndIndex = scrollviewPositionList.FindLastIndex(x => x <= scrollMaxY) + 1; //Add one since we want to make sure the entire height of the guielement is shown
if (scrollEndIndex > scrollviewPositionList.Count - 1)
scrollEndIndex = scrollviewPositionList.Count >= 1 ? scrollviewPositionList.Count - 1 : duplicateDataManager.Entries.Count - 1; //Dont want out of bounds
}
//Insert empty space in the BEGINNING of scrollview
if (scrollStartIndex >= 0 && scrollviewPositionList.Count>0)
GUILayout.Space(scrollviewPositionList[scrollStartIndex]);
int counter = -1;
foreach (var kvPair in duplicateDataManager.Entries)
{
counter++;
if (counter < scrollStartIndex)
{
continue;
}
else if (counter > scrollEndIndex)
{
break;
}
using (var hScope = new EditorGUILayout.HorizontalScope("box"))
{
Rect hScopeSize = hScope.rect;
buttonSelectContent.image = kvPair.Value.Preview;
if (GUILayout.Button(buttonSelectContent, EditorStyles.boldLabel, GUILayout.Width(64), GUILayout.MaxHeight(64)))
{
var assets = kvPair.Value.Paths.Select(x => AssetDatabase.LoadMainAssetAtPath(x)).ToArray();
Selection.objects = assets;
}
//EditorGUILayout.LabelField(kvPair.Key);
using (var vScope = new EditorGUILayout.VerticalScope("box"))
{
foreach (var path in kvPair.Value.Paths)
{
using (new EditorGUI.DisabledGroupScope(Selection.objects.Any(x => AssetDatabase.GetAssetPath(x) == path)))
{
int charCount = (int)vScope.rect.width / 7;
labelBtnContent.text = AH_Utils.ShrinkPathEnd(path.Remove(0, 7), charCount);
labelBtnContent.tooltip = path;
if (GUILayout.Button(labelBtnContent, labelBtnStyle))
Selection.activeObject = AssetDatabase.LoadMainAssetAtPath(path);
}
}
}
if (duplicateDataManager.RequiresScrollviewRebuild && Event.current.type == EventType.Repaint)
{
scrollviewPositionList.Add(hScope.rect.y); //Store Y position of guielement rect
}
}
}
//We have succesfully rebuild the scrollview position list
if (duplicateDataManager.RequiresScrollviewRebuild && Event.current.type == EventType.Repaint)
{
duplicateDataManager.RequiresScrollviewRebuild = false;
}
//Insert empty space at the END of scrollview
if (scrollEndIndex < scrollviewPositionList.Count - 1)
GUILayout.Space(scrollviewPositionList.Last() - scrollviewPositionList[scrollEndIndex]);
}
if (Event.current.type == EventType.Repaint)
scrollArea = GUILayoutUtility.GetLastRect();
}
private void doFooter()
{
GUIContent RefreshGUIContent = new GUIContent(guiContentRefresh);
Color origColor = GUI.color;
if (duplicateDataManager.IsDirty)
{
GUI.color = Heureka_WindowStyler.clr_Red;
RefreshGUIContent.tooltip = String.Format("{0}{1}", RefreshGUIContent.tooltip, " (Project has changed which means that data is out of sync)");
}
if (AH_UIUtilities.DrawSelectionButton(RefreshGUIContent))
duplicateDataManager.RefreshData();
GUI.color = origColor;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 32b3afe601d97d643bb716a62b16106c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,64 @@
using System;
using UnityEditor;
using UnityEngine;
namespace HeurekaGames.AssetHunterPRO
{
public class AH_EditorData : ScriptableObject
{
public delegate void EditorDataRefreshDelegate();
public static event EditorDataRefreshDelegate OnEditorDataRefresh;
private static AH_EditorData m_instance;
public static AH_EditorData Instance
{
get
{
if (!m_instance)
{
m_instance = loadData();
}
return m_instance;
}
}
private static AH_EditorData loadData()
{
//LOGO ON WINDOW
string[] configData = AssetDatabase.FindAssets("EditorData t:" + typeof(AH_EditorData).ToString(), null);
if (configData.Length >= 1)
{
return AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(configData[0]), typeof(AH_EditorData)) as AH_EditorData;
}
Debug.LogError("Failed to find config data");
return null;
}
internal void RefreshData()
{
if (OnEditorDataRefresh != null)
OnEditorDataRefresh();
}
public UnityEditor.DefaultAsset Documentation;
[SerializeField] public ConfigurableIcon WindowPaneIcon = new ConfigurableIcon();
[SerializeField] public ConfigurableIcon WindowHeaderIcon = new ConfigurableIcon();
[SerializeField] public ConfigurableIcon SceneIcon = new ConfigurableIcon();
[SerializeField] public ConfigurableIcon Settings = new ConfigurableIcon();
[SerializeField] public ConfigurableIcon LoadLogIcon = new ConfigurableIcon();
[SerializeField] public ConfigurableIcon GenerateIcon = new ConfigurableIcon();
[SerializeField] public ConfigurableIcon RefreshIcon = new ConfigurableIcon();
[SerializeField] public ConfigurableIcon MergerIcon = new ConfigurableIcon();
[SerializeField] public ConfigurableIcon HelpIcon = new ConfigurableIcon();
[SerializeField] public ConfigurableIcon AchievementIcon = new ConfigurableIcon();
[SerializeField] public ConfigurableIcon ReportIcon = new ConfigurableIcon();
[SerializeField] public ConfigurableIcon DeleteIcon = new ConfigurableIcon();
[SerializeField] public ConfigurableIcon RefToIcon = new ConfigurableIcon();
[SerializeField] public ConfigurableIcon RefFromIcon = new ConfigurableIcon();
[SerializeField] public ConfigurableIcon RefFromWhiteIcon = new ConfigurableIcon();
[SerializeField] public ConfigurableIcon DuplicateIcon = new ConfigurableIcon();
[SerializeField] public ConfigurableIcon DuplicateWhiteIcon = new ConfigurableIcon();
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 200a2edfc52002c468b33af791f333c0
timeCreated: 1498416726
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,79 @@
using HeurekaGames.AssetHunterPRO.BaseTreeviewImpl.AssetTreeView;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace HeurekaGames.AssetHunterPRO
{
[System.Serializable]
public class AH_ElementList
{
public List<AssetDumpData> elements;
public AH_ElementList(List<AssetDumpData> elements)
{
this.elements = elements;
}
internal static void DumpCurrentListToFile(AH_TreeViewWithTreeModel m_TreeView)
{
var path = EditorUtility.SaveFilePanel(
"Dump current list to file",
AH_SerializationHelper.GetBuildInfoFolder(),
"AH_Listdump_" + System.Environment.UserName,
AH_SerializationHelper.FileDumpExtension);
if (path.Length != 0)
{
List<AssetDumpData> elements = new List<AssetDumpData>();
foreach (var element in m_TreeView.GetRows())
populateDumpListRecursively(m_TreeView.treeModel.Find(element.id), ((AH_MultiColumnHeader)m_TreeView.multiColumnHeader).ShowMode, ref elements);
AH_ElementList objectToSave = new AH_ElementList(elements);
AH_SerializationHelper.SerializeAndSave(objectToSave, path);
}
}
private static void populateDumpListRecursively(AH_TreeviewElement element, AH_MultiColumnHeader.AssetShowMode showmode, ref List<AssetDumpData> elements)
{
if (element.HasChildrenThatMatchesState(showmode))
{
foreach (var child in element.children)
{
populateDumpListRecursively((AH_TreeviewElement)child, showmode, ref elements);
}
}
else if(element.AssetMatchesState(showmode))
{
UnityEngine.Debug.Log("Adding " + element.Name);
elements.Add(new AssetDumpData(element.GUID,/*element.AbsPath,*/element.RelativePath, /*element.AssetSize,*/ element.FileSize, element.UsedInBuild,element.ScenesReferencingAsset));
}
}
[System.Serializable]
public struct AssetDumpData
{
#pragma warning disable
[SerializeField] private string GUID;
//[SerializeField] private string absPath;
[SerializeField] private string relativePath;
//[SerializeField] private long assetSize;
[SerializeField] private long fileSize;
[SerializeField] private bool usedInBuild;
[SerializeField] private List<string> scenesReferencingAsset;
#pragma warning restore
public AssetDumpData(string guid, /*string absPath,*/ string relativePath, /*long assetSize,*/ long fileSize, bool usedInBuild, List<string> scenesReferencingAsset)
{
this.GUID = guid;
//this.absPath = absPath;
this.relativePath = relativePath;
//this.assetSize = assetSize;
this.fileSize = fileSize;
this.usedInBuild = usedInBuild;
this.scenesReferencingAsset = scenesReferencingAsset;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f678a0e10225c0a4faaf391cccd00d2b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,18 @@
using System.Collections.Generic;
namespace HeurekaGames.AssetHunterPRO
{
public interface AH_IIgnoreListActions
{
event System.EventHandler<IgnoreListEventArgs> IgnoredAddedEvent;
string Header { get; }
string FoldOutContent { get; }
void DrawIgnored(AH_IgnoreList ignoredList);
void IgnoreCallback(UnityEngine.Object obj, string identifier);
string GetFormattedItem(string identifier);
string GetFormattedItemShort(string identifier);
string GetLabelFormattedItem(string item);
bool ContainsElement(List<string> ignoredList, string element, string identifier = "");
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4b539e63be4aca346ad27ff903abb802
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,303 @@
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using HeurekaGames;
namespace HeurekaGames.AssetHunterPRO
{
[System.Serializable]
public class AH_IgnoreList
{
public delegate void ListUpdatedHandler();
public event ListUpdatedHandler ListUpdatedEvent;
private List<string> DefaultIgnored;
/// <summary>
/// List of the Ignored items
/// </summary>
/*[SerializeField]
private List<string> CombinedIgnored = new List<string>();*/
[SerializeField]
private SerializedIgnoreArray CombinedIgnored = new SerializedIgnoreArray();
/// <summary>
/// Id of the playerpref location
/// </summary>
private string playerPrefKey;
/// <summary>
/// Interface that deals with drawing and excluding in a way that is specific to the type of exclusion we are doing
/// </summary>
private AH_IIgnoreListActions exclusionActions;
//Size of buttons
public const float ButtonSpacer = 70;
private string toBeDeleted;
private bool isDirty;
public AH_IgnoreList(AH_IIgnoreListActions exclusionActions, List<string> Ignored, string playerPrefKey)
{
this.exclusionActions = exclusionActions;
this.exclusionActions.IgnoredAddedEvent += onAddedToignoredList;
this.playerPrefKey = playerPrefKey;
this.DefaultIgnored = new List<string>(Ignored);
}
internal List<string> GetIgnored()
{
//We already have a list of Ignores
if (CombinedIgnored.Ignored.Count >= 1)
return CombinedIgnored.Ignored;
//We have no list, so read the defaults
else if (EditorPrefs.HasKey(playerPrefKey))
{
//Populates this class from prefs
CombinedIgnored = LoadFromPrefs();
}
else
{
//Save the default values into prefs
SaveDefault();
//Try to get values again after having set default to prefs
return GetIgnored();
}
//Make sure default and combined are synced
//If combined has element that doesn't exist in combined, add it!
if (DefaultIgnored.Exists(val => !CombinedIgnored.Ignored.Contains(val)))
CombinedIgnored.Ignored.AddRange(DefaultIgnored.FindAll(val => !CombinedIgnored.Ignored.Contains(val)));
//Returns the values that have been read from prefs
return CombinedIgnored.Ignored;
}
public SerializedIgnoreArray LoadFromPrefs()
{
return JsonUtility.FromJson<SerializedIgnoreArray>(EditorPrefs.GetString(playerPrefKey));
}
internal bool IsDirty()
{
return isDirty;
}
public void Save()
{
string newJsonString = JsonUtility.ToJson(CombinedIgnored);
string oldJsonString = EditorPrefs.GetString(playerPrefKey);
//If we haven't changed anything, dont save
if (newJsonString.Equals(oldJsonString))
return;
SetDirty(true);
//Copy the default values into the other list
EditorPrefs.SetString(playerPrefKey, newJsonString);
//Send event that list was update
if (ListUpdatedEvent != null)
ListUpdatedEvent();
}
public void SaveDefault()
{
//Copy the default values into the other list
CombinedIgnored = new SerializedIgnoreArray(DefaultIgnored);
Save();
}
internal void Reset()
{
SaveDefault();
}
internal void DrawIgnoreButton()
{
/*if (isDirty)
return;*/
exclusionActions.DrawIgnored(this);
}
internal void OnGUI()
{
if (Event.current.type != EventType.Layout && isDirty)
{
SetDirty(false);
return;
}
//See if we are currently deleting an Ignore
checkToDelete();
EditorGUILayout.BeginVertical();
EditorGUILayout.Space();
EditorGUILayout.HelpBox(exclusionActions.Header + " (" + GetIgnored().Count + ")", MessageType.None);
if (GetIgnored().Count > 0)
{
EditorGUI.indentLevel++;
foreach (var item in GetIgnored())
{
drawIgnored(item, exclusionActions.GetLabelFormattedItem(item));
}
EditorGUI.indentLevel--;
}
EditorGUILayout.EndVertical();
}
private void checkToDelete()
{
if (string.IsNullOrEmpty(toBeDeleted))
return;
removeFromignoredList(toBeDeleted);
}
private void drawIgnored(string identifier, string legible)
{
if (string.IsNullOrEmpty(identifier))
return;
EditorGUILayout.BeginHorizontal();
if (!DefaultIgnored.Contains(identifier))
{
if (GUILayout.Button("Un-Ignore", EditorStyles.miniButton, GUILayout.Width(ButtonSpacer)))
{
markForDeletion(identifier);
}
}
//Hidden button to help align, probably not the most elegant solution, but Ill fix later
else if (DefaultIgnored.Count != GetIgnored().Count)
GUILayout.Button("", GUIStyle.none, GUILayout.Width(ButtonSpacer + 4));
float curWidth = EditorGUIUtility.labelWidth;
EditorGUILayout.LabelField(getLabelContent(legible), GUILayout.MaxWidth(1024));
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
}
internal void IgnoreElement(string identifier, bool ignore)
{
if (ignore && !ExistsInList(identifier))
AddToignoredList(identifier);
if (!ignore && ExistsInList(identifier))
markForDeletion(identifier);
}
private void markForDeletion(string item)
{
toBeDeleted = item;
}
protected virtual string getLabelContent(string item)
{
return item;
}
public bool ExistsInList(string element)
{
return (GetIgnored().Contains(element));
}
private void onAddedToignoredList(object sender, IgnoreListEventArgs e)
{
AddToignoredList(e.Item);
}
public void AddToignoredList(string element)
{
if (GetIgnored().Contains(element))
{
Debug.LogWarning("AH: Element already ignored: " + element);
return;
}
GetIgnored().Add(element);
//Save to prefs
Save();
}
protected void removeFromignoredList(string element)
{
toBeDeleted = "";
GetIgnored().Remove(element);
//Save to prefs
Save();
}
//Sets dirty so we know we need to manage the IMGUI (Mismatched LayoutGroup.Repaint)
private void SetDirty(bool bDirty)
{
isDirty = bDirty;
}
internal bool ContainsElement(string localpath, string identifier = "")
{
return exclusionActions.ContainsElement(GetIgnored(), localpath, identifier);
}
[Serializable]
public class SerializedIgnoreArray
{
/// <summary>
/// List of the Ignored items
/// </summary>
[SerializeField]
public List<string> Ignored = new List<string>();
public SerializedIgnoreArray() { }
public SerializedIgnoreArray(List<string> defaultIgnored)
{
this.Ignored = new List<string>(defaultIgnored);
}
}
}
public class AH_ExclusionTypeList : AH_IgnoreList
{
//Call base constructor but convert the types into serializable values
public AH_ExclusionTypeList(AH_IIgnoreListActions exclusionAction, List<Type> Ignored, string playerPrefsKey) : base(exclusionAction, Ignored.ConvertAll<string>(val => Heureka_Serializer.SerializeType(val)), playerPrefsKey)
{
}
//Return the type tostring instead of the fully qualified type identifier
protected override string getLabelContent(string item)
{
Type deserializedType = Heureka_Serializer.DeSerializeType(base.getLabelContent(item));
if (deserializedType != null)
return deserializedType.ToString();
//The Ignored type does no longer exist in project
else
return "Unrecognized type : " + item;
}
internal void IgnoreType(Type t, bool ignore)
{
IgnoreElement(Heureka_Serializer.SerializeType(t), ignore);
}
}
public class IgnoreListEventArgs : EventArgs
{
public string Item;
public IgnoreListEventArgs(Type item)
{
this.Item = Heureka_Serializer.SerializeType(item);
}
public IgnoreListEventArgs(string item)
{
this.Item = item;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e666beed7750ac4438892d0bb9ec6257
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,330 @@
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace HeurekaGames.AssetHunterPRO
{
public class AH_ignoredListEventActionBase
{
public event System.EventHandler<IgnoreListEventArgs> IgnoredAddedEvent;
public virtual string Header { get; protected set; }
public virtual string FoldOutContent { get; protected set; }
[SerializeField] private int ignoredListIndex;
[SerializeField] private Action<int> buttonDownCallBack;
public AH_ignoredListEventActionBase(int ignoredListIndex, Action<int> buttonDownCallBack)
{
this.ignoredListIndex = ignoredListIndex;
this.buttonDownCallBack = buttonDownCallBack;
}
protected void getObjectInfo(out string path, out bool isMain, out bool isFolder)
{
path = AssetDatabase.GetAssetPath(Selection.activeObject);
isMain = AssetDatabase.IsMainAsset(Selection.activeObject);
isFolder = AssetDatabase.IsValidFolder(path);
}
public void IgnoreCallback(UnityEngine.Object obj, string identifier)
{
IgnoredAddedEvent(obj, new IgnoreListEventArgs(identifier));
//Notify the list was changed
buttonDownCallBack(ignoredListIndex);
}
/// <summary>
/// draw the Ignore button
/// </summary>
/// <param name="buttonText">What should the button read</param>
/// <param name="ignoredList">The current list of Ignores this is supposed to be appended to</param>
/// <param name="identifier">The unique identifier of the Ignore</param>
/// <param name="optionalLegibleIdentifier">Humanly legible identifier</param>
protected void drawButton(bool validSelected, string buttonText, AH_IgnoreList ignoredList, string identifier, string optionalLegibleIdentifier = "")
{
bool btnUsable = validSelected && !ignoredList.ExistsInList(identifier);
EditorGUILayout.BeginHorizontal();
EditorGUI.BeginDisabledGroup(!btnUsable);
if (GUILayout.Button(buttonText, GUILayout.Width(150)) && btnUsable)
{
IgnoreCallback(Selection.activeObject, identifier);
}
EditorGUI.EndDisabledGroup();
//Select the proper string to write on label
string label = (!btnUsable) ?
((validSelected) ?
"Already Ignored" : "Invalid selection")
: (string.IsNullOrEmpty(optionalLegibleIdentifier) ?
identifier : optionalLegibleIdentifier);
GUIContent content = new GUIContent(label, EditorGUIUtility.IconContent((!btnUsable) ? ((validSelected) ? "d_lightOff" : "d_orangeLight") : "d_greenLight").image);
GUILayout.Label(content, GUILayout.MaxHeight(16));
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
}
protected bool hasValidSelectionObject()
{
return (Selection.activeObject != null && Selection.objects.Length == 1);
}
public virtual string GetFormattedItem(string identifier)
{
return identifier;
}
public virtual string GetFormattedItemShort(string identifier)
{
return GetFormattedItem(identifier);
}
public virtual string GetLabelFormattedItem(string identifier)
{
return GetFormattedItem(identifier);
}
}
public class IgnoredEventActionExtension : AH_ignoredListEventActionBase, AH_IIgnoreListActions
{
public IgnoredEventActionExtension(int ignoredListIndex, Action<int> buttonDownCallBack) : base(ignoredListIndex, buttonDownCallBack)
{
Header = "File extensions ignored from search";
FoldOutContent = "See ignored file extensions";
}
public bool ContainsElement(List<string> ignoredList, string path, string assetId)
{
string element;
pathContainsValidElement(path, out element);
return ignoredList.Contains(element.ToLower());
}
private bool pathContainsValidElement(string path, out string extension)
{
extension = "";
bool hasExtension = System.IO.Path.HasExtension(path);
if (hasExtension)
extension = System.IO.Path.GetExtension(path).ToLower();
return hasExtension;
}
//Check if the currectly selected asset if excludable as file extension
public void DrawIgnored(AH_IgnoreList ignoredList)
{
if (!hasValidSelectionObject())
return;
string path;
bool isMain;
bool isFolder;
getObjectInfo(out path, out isMain, out isFolder);
//if (isMain)
{
string extension;
bool validElement = (pathContainsValidElement(path, out extension));
drawButton(isMain && validElement, "Ignore file extension", ignoredList, extension);
}
}
}
public class IgnoredEventActionPathEndsWith : AH_ignoredListEventActionBase, AH_IIgnoreListActions
{
public IgnoredEventActionPathEndsWith(int ignoredListIndex, Action<int> buttonDownCallBack) : base(ignoredListIndex, buttonDownCallBack)
{
Header = "Folder paths with the following ending are ignored";
FoldOutContent = "See ignored folder endings";
}
public bool ContainsElement(List<string> ignoredList, string path, string assetId)
{
return ignoredList.Contains(getIdentifier(path));
}
private string getIdentifier(string path)
{
if (string.IsNullOrEmpty(path))
return "";
string fullPath = System.IO.Path.GetFullPath(path).TrimEnd(System.IO.Path.DirectorySeparatorChar);
return System.IO.Path.DirectorySeparatorChar + System.IO.Path.GetFileName(fullPath).ToLower();
}
//Check if the currectly selected asset if excludable as path ending
public void DrawIgnored(AH_IgnoreList ignoredList)
{
if (!hasValidSelectionObject())
return;
string path;
bool isMain;
bool isFolder;
getObjectInfo(out path, out isMain, out isFolder);
drawButton((isMain && isFolder), "Ignore folder ending", ignoredList, getIdentifier(path));
}
}
public class IgnoredEventActionType : AH_ignoredListEventActionBase, AH_IIgnoreListActions
{
public IgnoredEventActionType(int ignoredListIndex, Action<int> buttonDownCallBack) : base(ignoredListIndex, buttonDownCallBack)
{
Header = "Asset types ignored";
FoldOutContent = "See ignored types";
}
public bool ContainsElement(List<string> ignoredList, string path, string assetId)
{
Type assetType;
return ignoredList.Contains(getIdentifier(path, out assetType));
}
private string getIdentifier(string path, out Type assetType)
{
assetType = AssetDatabase.GetMainAssetTypeAtPath(path);
if (assetType != null)
return Heureka_Serializer.SerializeType(assetType);
else
return "";
}
//Check if the currectly selected asset if excludable as type
public void DrawIgnored(AH_IgnoreList ignoredList)
{
if (!hasValidSelectionObject())
return;
string path;
bool isMain;
bool isFolder;
getObjectInfo(out path, out isMain, out isFolder);
Type assetType;
drawButton((isMain && !isFolder), "Ignore Type", ignoredList, getIdentifier(path, out assetType), assetType != null ? assetType.ToString() : "");
}
}
public class IgnoredEventActionFile : AH_ignoredListEventActionBase, AH_IIgnoreListActions
{
public IgnoredEventActionFile(int ignoredListIndex, Action<int> buttonDownCallBack) : base(ignoredListIndex, buttonDownCallBack)
{
Header = "Specific files ignored";
FoldOutContent = "See ignored files";
}
public bool ContainsElement(List<string> ignoredList, string path, string assetId)
{
if (!string.IsNullOrEmpty(assetId))
return ignoredList.Contains(assetId);
else
return ignoredList.Contains(getIdentifier(path));
}
private string getIdentifier(string path)
{
return AssetDatabase.AssetPathToGUID(path);
}
//Check if the currectly selected asset if excludable as file
public void DrawIgnored(AH_IgnoreList ignoredList)
{
if (!hasValidSelectionObject())
return;
string path;
bool isMain;
bool isFolder;
getObjectInfo(out path, out isMain, out isFolder);
string assetGUID = getIdentifier(path);
drawButton((isMain && !isFolder), "Ignore file", ignoredList, assetGUID, GetFormattedItemShort(assetGUID, 45));
}
public override string GetFormattedItem(string identifier)
{
return AssetDatabase.GUIDToAssetPath(identifier);
}
public string GetFormattedItemShort(string identifier, int charCount)
{
return AH_Utils.ShrinkPathEnd(GetFormattedItem(identifier), charCount);
}
public override string GetFormattedItemShort(string identifier)
{
return GetFormattedItemShort(identifier, 50);
}
public override string GetLabelFormattedItem(string identifier)
{
return GetFormattedItemShort(identifier, 60);
}
}
public class IgnoredEventActionFolder : AH_ignoredListEventActionBase, AH_IIgnoreListActions
{
public IgnoredEventActionFolder(int ignoredListIndex, Action<int> buttonDownCallBack) : base(ignoredListIndex, buttonDownCallBack)
{
Header = "Specific folders ignored";
FoldOutContent = "See ignored folders";
}
public bool ContainsElement(List<string> ignoredList, string path, string assetId)
{
if (!string.IsNullOrEmpty(assetId))
return ignoredList.Contains(assetId);
else
return ignoredList.Contains(getIdentifier(path));
}
private string getIdentifier(string path)
{
return AssetDatabase.AssetPathToGUID(path);
}
//Check if the currectly selected asset if excludable as folder
public void DrawIgnored(AH_IgnoreList ignoredList)
{
if (!hasValidSelectionObject())
return;
string path;
bool isMain;
bool isFolder;
getObjectInfo(out path, out isMain, out isFolder);
string assetGUID = getIdentifier(path);
drawButton((isMain && isFolder), "Ignore folder", ignoredList, assetGUID, GetFormattedItemShort(assetGUID, 40));
}
public override string GetFormattedItemShort(string identifier)
{
return GetFormattedItemShort(identifier, 50);
}
private string GetFormattedItemShort(string identifier, int charCount)
{
return AH_Utils.ShrinkPathEnd(GetFormattedItem(identifier), charCount);
}
public override string GetFormattedItem(string identifier)
{
return AssetDatabase.GUIDToAssetPath(identifier);
}
public override string GetLabelFormattedItem(string identifier)
{
return GetFormattedItemShort(identifier, 60);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3a1fe58bf2b20a944bfd906f72b39287
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,39 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
public class AH_PreProcessor : MonoBehaviour
{
public const string DefineScriptAllow = "AH_SCRIPT_ALLOW";
//public const string DefineHasOldVersion = "AH_HAS_OLD_INSTALLED";
/// <summary>
/// Add define symbols as soon as Unity gets done compiling.
/// </summary>
public static void AddDefineSymbols(string symbol, bool addDefine)
{
string definesString = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
List<string> allDefines = definesString.Split(';').ToList();
bool updateDefines = false;
if (addDefine && !allDefines.Contains(symbol))
{
allDefines.Add(symbol);
updateDefines = true;
}
else if (!addDefine && allDefines.Contains(symbol))
{
allDefines.Remove(symbol);
updateDefines = true;
}
if (updateDefines)
PlayerSettings.SetScriptingDefineSymbolsForGroup(
EditorUserBuildSettings.selectedBuildTargetGroup,
string.Join(";", allDefines.ToArray()));
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9d0994f98ad87954690048c4b3e19047
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,100 @@
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
using System;
using System.Linq;
namespace HeurekaGames.AssetHunterPRO
{
public class AH_SceneReferenceWindow : EditorWindow
{
private static AH_SceneReferenceWindow m_window;
private Vector2 scrollPos;
[SerializeField]
private float btnMinWidthSmall = 50;
private List<String> m_allScenesInProject;
private List<String> m_allScenesInBuildSettings;
private List<String> m_allEnabledScenesInBuildSettings;
private List<String> m_allUnreferencedScenes;
private List<String> m_allDisabledScenesInBuildSettings;
private static readonly string WINDOWNAME = "AH Scenes";
[MenuItem("Tools/Asset Hunter PRO/Scene overview")]
[MenuItem("Window/Heureka/Asset Hunter PRO/Scene overview")]
public static void Init()
{
m_window = AH_SceneReferenceWindow.GetWindow<AH_SceneReferenceWindow>(WINDOWNAME, true, typeof(AH_Window));
m_window.titleContent.image = AH_EditorData.Instance.SceneIcon.Icon;
m_window.GetSceneInfo();
}
private void GetSceneInfo()
{
m_allScenesInProject = AH_Utils.GetAllSceneNames().ToList<string>();
m_allScenesInBuildSettings = AH_Utils.GetAllSceneNamesInBuild().ToList<string>();
m_allEnabledScenesInBuildSettings = AH_Utils.GetEnabledSceneNamesInBuild().ToList<string>();
m_allDisabledScenesInBuildSettings = SubtractSceneArrays(m_allScenesInBuildSettings, m_allEnabledScenesInBuildSettings);
m_allUnreferencedScenes = SubtractSceneArrays(m_allScenesInProject, m_allScenesInBuildSettings);
}
//Get the subset of scenes where we subtract "secondary" from "main"
private List<String> SubtractSceneArrays(List<String> main, List<String> secondary)
{
return main.Except<string>(secondary).ToList<string>();
}
private void OnFocus()
{
GetSceneInfo();
}
private void OnGUI()
{
if (!m_window)
Init();
scrollPos = EditorGUILayout.BeginScrollView(scrollPos);
Heureka_WindowStyler.DrawGlobalHeader(Heureka_WindowStyler.clr_Dark, "SCENE REFERENCES");
//Show all used types
EditorGUILayout.BeginVertical();
//Make sure this window has focus to update contents
Repaint();
if (m_allEnabledScenesInBuildSettings.Count == 0)
Heureka_WindowStyler.DrawCenteredMessage(m_window, AH_EditorData.Instance.WindowHeaderIcon.Icon, 310f, 110f, "There are no enabled scenes in build settings");
drawScenes("These scenes are added and enabled in build settings", m_allEnabledScenesInBuildSettings);
drawScenes("These scenes are added to build settings but disabled", m_allDisabledScenesInBuildSettings);
drawScenes("These scenes are not referenced anywhere in build settings", m_allUnreferencedScenes);
EditorGUILayout.EndVertical();
EditorGUILayout.EndScrollView();
}
private void drawScenes(string headerMsg, List<string> scenes)
{
if (scenes.Count > 0)
{
EditorGUILayout.HelpBox(headerMsg, MessageType.Info);
foreach (string scenePath in scenes)
{
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Ping", GUILayout.Width(btnMinWidthSmall)))
{
Selection.activeObject = AssetDatabase.LoadAssetAtPath(scenePath, typeof(UnityEngine.Object));
EditorGUIUtility.PingObject(Selection.activeObject);
}
EditorGUILayout.LabelField(scenePath);
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.Separator();
}
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 7e664ab2ccf1df844bb835e85586b62d
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using HeurekaGames.AssetHunterPRO.BaseTreeviewImpl.AssetTreeView;
using UnityEditor;
using UnityEngine;
namespace HeurekaGames.AssetHunterPRO
{
internal class AH_SerializationHelper
{
public delegate void NewBuildInfoCreatedDelegate(string path);
public static NewBuildInfoCreatedDelegate NewBuildInfoCreated;
public const string BuildInfoExtension = "ahbuildinfo";
public const string SettingsExtension = "ahsetting";
public const string FileDumpExtension = "ahfiledump";
public const string DateTimeFormat = "yyyy_MM_dd_HH_mm_ss";
internal static void SerializeAndSave(AH_SerializedBuildInfo ahBuildInfo)
{
string buildinfoFileName = ahBuildInfo.buildTargetInfo + "_" + ahBuildInfo.dateTime + "." + BuildInfoExtension;
string filePath = GetBuildInfoFolder() + System.IO.Path.DirectorySeparatorChar + buildinfoFileName;
System.IO.Directory.CreateDirectory(GetBuildInfoFolder());
System.IO.File.WriteAllText(filePath, JsonUtility.ToJson(ahBuildInfo));
if (AH_SettingsManager.Instance.AutoOpenLog)
EditorUtility.RevealInFinder(filePath);
if (NewBuildInfoCreated != null)
NewBuildInfoCreated(filePath);
}
internal static string GetDateString()
{
return DateTime.Now.ToString(DateTimeFormat);
}
internal static void SerializeAndSave(object instance, string path)
{
System.IO.File.WriteAllText(path, JsonUtility.ToJson(instance));
}
internal static AH_SerializedBuildInfo LoadBuildReport(string path)
{
string fileContent = "";
try
{
fileContent = System.IO.File.ReadAllText(path);
}
catch (System.IO.FileNotFoundException e)
{
EditorUtility.DisplayDialog(
"File Not Found",
"Unable to find:" + Environment.NewLine + path,
"Ok");
Debug.LogError("AH: Unable to find: " + path + Environment.NewLine + e);
return null;
}
try
{
AH_SerializedBuildInfo buildInfo = JsonUtility.FromJson<AH_SerializedBuildInfo>(fileContent);
buildInfo.Sort();
return buildInfo;
}
catch (Exception e)
{
Debug.LogError("AH: JSON Parse error of " + path + Environment.NewLine + "- " + e.ToString());
return null;
}
}
internal static string GetBuildInfoFolder()
{
return AH_SettingsManager.Instance.BuildInfoPath; // System.IO.Directory.GetParent(Application.dataPath).FullName + System.IO.Path.DirectorySeparatorChar + "SerializedBuildInfo";
}
internal static string GetSettingFolder()
{
string userpreferencesPath = AH_SettingsManager.Instance.UserPreferencePath;
System.IO.DirectoryInfo dirInfo = System.IO.Directory.CreateDirectory(userpreferencesPath);
return dirInfo.FullName;
}
internal static string GetBackupFolder()
{
return System.IO.Directory.GetParent(Application.dataPath).FullName;
}
internal static void LoadSettings(AH_SettingsManager instance, string path)
{
string text = System.IO.File.ReadAllText(path);
try
{
EditorJsonUtility.FromJsonOverwrite(text, instance);
}
catch (Exception e)
{
Debug.LogError("AH: JSON Parse error of " + path + Environment.NewLine + "- " + e.ToString());
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9499f6dadb6456a45ad8decc1c18deef
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
[System.Serializable]
public class AH_SerializableAssetInfo
{
public string ID;
/// <summary>
/// In 2.1.5 and older this property is a list of paths
/// </summary>
public List<string> Refs;
/// <summary>
/// but in 2.1.6 and newer its a list of indexes that points to a guid (Scene assets)
/// </summary>
//[UnityEngine.SerializeField] private List<string> sceneIDs;
public AH_SerializableAssetInfo()
{ }
public AH_SerializableAssetInfo(string assetPath, List<string> scenes)
{
this.ID = UnityEditor.AssetDatabase.AssetPathToGUID(assetPath);
this.Refs = scenes;// scenes.Select(x => UnityEditor.AssetDatabase.AssetPathToGUID(x)).ToList();
}
/*public List<string> SceneIDs
{
get
{
if (sceneIDs.Count>0 || Refs==null)
return sceneIDs;
else
return sceneIDs = Refs.Select(x => UnityEditor.AssetDatabase.AssetPathToGUID(x)).ToList();
}
set
{
sceneIDs = value;
}
}*/
internal void ChangePathToGUID()
{
Refs = Refs.Select(x => UnityEditor.AssetDatabase.AssetPathToGUID(x)).ToList();
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9d1ad9523d8b90e4aa519e18c20cc206
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,290 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using System.Linq;
//Only avaliable in 2018
#if UNITY_2018_1_OR_NEWER
using UnityEditor.Build.Reporting;
#endif
#if ADRESSABLES_1_2_0_OR_NEWER
using UnityEditor.AddressableAssets;
#endif
namespace HeurekaGames.AssetHunterPRO
{
[System.Serializable]
public class AH_SerializedBuildInfo
{
private const string mergeIdentifier = "MergedBuildInfo";
public string versionNumber;
public string buildTargetInfo;
public string dateTime;
public ulong TotalSize;
//Temporary dict for populating the asset usage data
Dictionary<string, List<string>> assetDict = new Dictionary<string, List<string>>();
//The serialized information stored in the JSON
public List<AH_SerializableAssetInfo> AssetListUnSorted = new List<AH_SerializableAssetInfo>();
//Only avaliable in 2018
#if UNITY_2018_1_OR_NEWER
public List<AH_BuildReportFileInfo> BuildReportInfoList = new List<AH_BuildReportFileInfo>();
#endif
//A sorted version of the assetList
SortedList<string, AH_SerializableAssetInfo> assetListSorted = new SortedList<string, AH_SerializableAssetInfo>();
public SortedList<string, AH_SerializableAssetInfo> AssetListSorted
{
get
{
return assetListSorted;
}
}
void setMetaData()
{
setMetaData(EditorUserBuildSettings.activeBuildTarget.ToString());
}
void setMetaData(string buildTargetInfoString)
{
dateTime = AH_SerializationHelper.GetDateString();
buildTargetInfo = buildTargetInfoString;
versionNumber = AH_Window.VERSION;
}
private void addFolderToReport(string foldername)
{
var foo = System.IO.Directory.GetDirectories(Application.dataPath, foldername, System.IO.SearchOption.AllDirectories);
//Add resources to build report
var folders = (from subdirectory in System.IO.Directory.GetDirectories(Application.dataPath, foldername, System.IO.SearchOption.AllDirectories)
where subdirectory.EndsWith(System.IO.Path.DirectorySeparatorChar + foldername)
select subdirectory).ToArray<string>();
//Change pats to project relative
for (int i = 0; i < folders.Count(); i++)
{
var relativePath =
folders[i] = FileUtil.GetProjectRelativePath(folders[i]);
}
//For some reason streamingassets folders are generated by unity when building, only to be deleted immediately after. Need to take that under consideration here.
folders = folders.Where(x => AssetDatabase.IsValidFolder(x)).ToArray();
//Make sure folder result is valid
if (folders != null && folders.Count() >= 1 && !string.IsNullOrEmpty(folders[0]))
foreach (string assetguid in AssetDatabase.FindAssets("*", folders))
{
AddBuildDependency(null, AssetDatabase.GUIDToAssetPath(assetguid));
}
}
private void SerializeAndSave()
{
setMetaData();
AH_SerializationHelper.SerializeAndSave(this);
}
internal void MergeWith(string fullName)
{
AH_SerializedBuildInfo newBuildInfo = AH_SerializationHelper.LoadBuildReport(fullName);
if (newBuildInfo != null)
{
Debug.Log("AH: Merging with " + fullName);
foreach (var newItem in newBuildInfo.AssetListUnSorted)
{
//If asset ID already exists
if (AssetListUnSorted.Any(val => val.ID == newItem.ID))
{
AH_SerializableAssetInfo OrigItem = AssetListUnSorted.Find(val => val.ID == newItem.ID);
//Check if new scene ref list have entries that doesn't exist in orig
bool newSceneRefsExist = newItem.Refs.Any(val => !OrigItem.Refs.Contains(val));
//Get the new refs
if (newSceneRefsExist)
{
List<string> newSceneRefs = newItem.Refs.FindAll(val => !OrigItem.Refs.Contains(val));
//Add them to list
OrigItem.Refs.AddRange(newSceneRefs); //Does this serialize as expected? Since we are adding through a setter?
}
}
else
AssetListUnSorted.Add(newItem);
}
}
else
Debug.Log("AH: Merging failed: " + fullName);
}
internal bool IsMergedReport()
{
return buildTargetInfo.Equals(mergeIdentifier);
}
internal void SaveAfterMerge()
{
setMetaData(mergeIdentifier);
AH_SerializationHelper.SerializeAndSave(this);
}
//Add the assets specific to buildtarget
private void addBuildtargetAssets(BuildTarget buildTarget)
{
BuildTargetGroup targetGroup = BuildPipeline.GetBuildTargetGroup(buildTarget);
List<Texture> buildTargetAssetDependencies = AH_Utils.GetTargetGroupAssetDependencies(targetGroup);
//Add all the textures to unsorted
foreach (var item in buildTargetAssetDependencies)
{
AddBuildDependency(null, AssetDatabase.GetAssetPath(item));
}
}
//Only avaliable in 2018
#if UNITY_2018_1_OR_NEWER
internal void ProcessBuildReport(BuildReport report)
{
TotalSize = report.summary.totalSize;
foreach (var file in report.files)
{
BuildReportInfoList.Add(new AH_BuildReportFileInfo(file));
}
}
#endif
//Stores in XML friendly format and saves
internal void FinalizeReport(BuildTarget target)
{
addBuildtargetAssets(target);
FinalizeReport();
}
internal void FinalizeReport()
{
//TODO: "Note that if the Resources folder is an Editor subfolder, the Assets in it are loadable from Editor scripts but are stripped from builds."
//https://docs.unity3d.com/Manual/SpecialFolders.html
addFolderToReport("Resources");
addFolderToReport("StreamingAssets");
addAssetBundlesToReport();
addAdressablesToReport();
addRenderPipelinesToReport();
//Add all the used assets to list
foreach (var item in assetDict)
{
var sceneIDs = item.Value.Select(x => AssetDatabase.AssetPathToGUID(x)).ToList(); //Id scene IDs from paths
AH_SerializableAssetInfo newAssetInfo = new AH_SerializableAssetInfo(item.Key, sceneIDs);
AssetListUnSorted.Add(newAssetInfo);
}
//TODO Clean AssetListUnsorted to make sure we dont have duplicates? How does this work with the scene refs
SerializeAndSave();
}
private void addRenderPipelinesToReport()
{
#if UNITY_2019_3_OR_NEWER
var pipelines = UnityEngine.Rendering.GraphicsSettings.allConfiguredRenderPipelines;
foreach (var pipeline in pipelines)
{
var path = AssetDatabase.GetAssetPath(pipeline);
AddBuildDependency("", path);
}
#endif
}
private void addAdressablesToReport()
{
#if ADRESSABLES_1_2_0_OR_NEWER
if (AddressableAssetSettingsDefaultObject.SettingsExists)
{
var fooSettings = AddressableAssetSettingsDefaultObject.Settings;
//Get all the build relevant files for addressables
string[] guids = AssetDatabase.FindAssets(string.Format("t:{0}", typeof(AddressableAssetSettingsDefaultObject)));
foreach (var guid in guids)
AddBuildDependency("", AssetDatabase.GUIDToAssetPath(guid));
guids = AssetDatabase.FindAssets(string.Format("addressables_content_state"));
foreach (var guid in guids)
AddBuildDependency("", AssetDatabase.GUIDToAssetPath(guid));
//TODO add addressable as path? (But that requires some refactor since we assume that to be a scene rather than an addressable NB: IF we do that we need to make sure dependencies are still being added in AddBuildDependency method
AddBuildDependency("", AssetDatabase.GetAssetPath(AddressableAssetSettingsDefaultObject.Settings));
foreach (var item in AddressableAssetSettingsDefaultObject.Settings.groups)
{
foreach (var entry in item.entries)
{
AddBuildDependency("", entry.AssetPath);
}
}
}
#endif
}
private void addAssetBundlesToReport()
{
string[] assetBundleNames = AssetDatabase.GetAllAssetBundleNames();
foreach (var bundleName in assetBundleNames)
{
foreach (var bundledAssetPath in AssetDatabase.GetAssetPathsFromAssetBundle(bundleName))
{
//TODO add assetbundle as path? (But that requires some refactor since we assume that to be a scene rather than a bundle NB: IF we do that we need to make sure dependencies are still being added in AddBuildDependency method
AddBuildDependency("", bundledAssetPath);
}
}
}
internal void AddBuildDependency(string scenePath, string assetPath)
{
if (!assetDict.ContainsKey(assetPath))
assetDict.Add(assetPath, new List<string>());
if (!string.IsNullOrEmpty(scenePath))
assetDict[assetPath].Add(scenePath);
//This is not a scene asset so it must be in resources/streaming ressources so we need to manage dependencies manually
else
{
string[] dependencies = AssetDatabase.GetDependencies(assetPath, false);
dependencies.ToList().AddRange(AssetDatabase.GetDependencies(assetPath, true).ToList());
//Loop assets
foreach (var aPath in dependencies)
{
//This asset is already referenced, so return
if (assetDict.ContainsKey(aPath))
continue;
//Dependencies also return the asset itself, and we dont want to keep looking at the same asset
if (!assetPath.Equals(aPath))
AddBuildDependency(scenePath, aPath);
}
}
}
internal AH_SerializableAssetInfo GetItemInfo(string assetID)
{
AH_SerializableAssetInfo assetInfo;
if (assetListSorted.TryGetValue(assetID, out assetInfo))
return assetInfo;
else
return null;
}
internal void Sort()
{
foreach (var item in AssetListUnSorted)
{
if (!assetListSorted.ContainsKey(item.ID))
assetListSorted.Add(item.ID, item);
}
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: f4578e682bc79b847ba0187ca63670a5
timeCreated: 1529398555
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,423 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace HeurekaGames.AssetHunterPRO
{
public class AH_SettingsManager
{
private static readonly AH_SettingsManager instance = new AH_SettingsManager();
#region singleton
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static AH_SettingsManager()
{
instance.Init();
}
private AH_SettingsManager()
{
}
public static AH_SettingsManager Instance
{
get
{
return instance;
}
}
#endregion
public delegate void IgnoreListUpdatedHandler();
public event IgnoreListUpdatedHandler IgnoreListUpdatedEvent;
#region Fields
[SerializeField] private int ignoredListChosenIndex;
private readonly static string ProjectPostFix = "." + Application.dataPath; // AssetDatabase.AssetPathToGUID(FileUtil.GetProjectRelativePath(Application.dataPath));
private readonly static string PrefsAutoCreateLog = "AH.AutoCreateLog" + ProjectPostFix;
private readonly static string PrefsAutoOpenLog = "AH.AutoOpenLog" + ProjectPostFix;
private readonly static string PrefsAutoRefreshLog = "AH.AutoRefreshLog" + ProjectPostFix;
private readonly static string PrefsEstimateAssetSize = "AH.PrefsEstimateAssetSize" + ProjectPostFix;
private readonly static string PrefsHideButtonText = "AH.HideButtonText" + ProjectPostFix;
private readonly static string PrefsIgnoreScriptFiles = "AH.IgnoreScriptfiles" + ProjectPostFix;
private readonly static string PrefsIgnoredTypes = "AH.DefaultIgnoredTypes" + ProjectPostFix;
private readonly static string PrefsIgnoredPathEndsWith = "AH.IgnoredPathEndsWith" + ProjectPostFix;
private readonly static string PrefsIgnoredExtensions = "AH.IgnoredExtensions" + ProjectPostFix;
private readonly static string PrefsIgnoredFiles = "AH.IgnoredFiles" + ProjectPostFix;
private readonly static string PrefsIgnoredFolders = "AH.IgnoredFolders" + ProjectPostFix;
private readonly static string PrefsUserPrefPath = "AH.UserPrefPath" + ProjectPostFix;
private readonly static string PrefsBuildInfoPath = "AH.BuildInfoPath" + ProjectPostFix;
internal readonly static bool InitialValueAutoCreateLog = true;
internal readonly static bool InitialValueAutoOpenLog = false;
internal readonly static bool InitialValueAutoRefreshLog = false;
internal readonly static bool InitialValueEstimateAssetSize = false;
internal readonly static bool InitialValueHideButtonText = true;
internal readonly static bool InitialIgnoreScriptFiles = true;
internal readonly static string InitialUserPrefPath = Application.dataPath + System.IO.Path.DirectorySeparatorChar + "AH_Prefs";
internal readonly static string InitialBuildInfoPath = System.IO.Directory.GetParent(Application.dataPath).FullName + System.IO.Path.DirectorySeparatorChar + "SerializedBuildInfo";
//Types to Ignore by default
#if UNITY_2017_3_OR_NEWER
internal readonly static List<Type> InitialValueIgnoredTypes = new List<Type>() {
typeof(UnityEditorInternal.AssemblyDefinitionAsset)
#if !AH_SCRIPT_ALLOW //DEFINED IN AH_PREPROCESSOR
,typeof(MonoScript)
#endif
};
#else
internal readonly static List<Type> InitialValueIgnoredTypes = new List<Type>() {
#if !AH_SCRIPT_ALLOW //DEFINED IN AH_PREPROCESSOR
typeof(MonoScript)
#endif
};
#endif
//File extensions to Ignore by default
internal readonly static List<string> InitialValueIgnoredExtensions = new List<string>() {
".dll",
"."+AH_SerializationHelper.SettingsExtension,
"."+AH_SerializationHelper.BuildInfoExtension
};
//List of strings which, if contained in asset path, is ignored (Editor, Resources, etc)
internal readonly static List<string> InitialValueIgnoredPathEndsWith = new List<string>() {
string.Format("{0}heureka", System.IO.Path.DirectorySeparatorChar),
string.Format("{0}editor", System.IO.Path.DirectorySeparatorChar),
string.Format("{0}plugins", System.IO.Path.DirectorySeparatorChar),
string.Format("{0}gizmos", System.IO.Path.DirectorySeparatorChar),
string.Format("{0}editor default resources", System.IO.Path.DirectorySeparatorChar)
};
internal readonly static List<string> InitialValueIgnoredFiles = new List<string>();
internal readonly static List<string> InitialValueIgnoredFolders = new List<string>();
[SerializeField] private AH_ExclusionTypeList ignoredListTypes;
[SerializeField] private AH_IgnoreList ignoredListPathEndsWith;
[SerializeField] private AH_IgnoreList ignoredListExtensions;
[SerializeField] private AH_IgnoreList ignoredListFiles;
[SerializeField] private AH_IgnoreList ignoredListFolders;
#endregion
#region Properties
[SerializeField]
public bool AutoCreateLog
{
get { return ((!EditorPrefs.HasKey(PrefsAutoCreateLog) && InitialValueAutoCreateLog) || AH_Utils.IntToBool(EditorPrefs.GetInt(PrefsAutoCreateLog))); }
internal set { EditorPrefs.SetInt(PrefsAutoCreateLog, AH_Utils.BoolToInt(value)); }
}
[SerializeField]
public bool AutoOpenLog
{
get { return ((!EditorPrefs.HasKey(PrefsAutoOpenLog) && InitialValueAutoOpenLog) || AH_Utils.IntToBool(EditorPrefs.GetInt(PrefsAutoOpenLog))); }
internal set { EditorPrefs.SetInt(PrefsAutoOpenLog, AH_Utils.BoolToInt(value)); }
}
[SerializeField]
public bool AutoRefreshLog
{
get { return ((!EditorPrefs.HasKey(PrefsAutoRefreshLog) && InitialValueAutoRefreshLog) || AH_Utils.IntToBool(EditorPrefs.GetInt(PrefsAutoRefreshLog))); }
internal set { EditorPrefs.SetInt(PrefsAutoRefreshLog, AH_Utils.BoolToInt(value)); }
}
[SerializeField]
public bool EstimateAssetSize
{
get { return ((!EditorPrefs.HasKey(PrefsEstimateAssetSize) && InitialValueEstimateAssetSize) || AH_Utils.IntToBool(EditorPrefs.GetInt(PrefsEstimateAssetSize))); }
internal set { EditorPrefs.SetInt(PrefsEstimateAssetSize, AH_Utils.BoolToInt(value)); }
}
[SerializeField]
public bool HideButtonText
{
get { return ((!EditorPrefs.HasKey(PrefsHideButtonText) && InitialValueHideButtonText) || AH_Utils.IntToBool(EditorPrefs.GetInt(PrefsHideButtonText))); }
internal set { EditorPrefs.SetInt(PrefsHideButtonText, AH_Utils.BoolToInt(value)); }
}
[SerializeField]
public bool IgnoreScriptFiles
{
get { return ((!EditorPrefs.HasKey(PrefsIgnoreScriptFiles) && InitialIgnoreScriptFiles) || AH_Utils.IntToBool(EditorPrefs.GetInt(PrefsIgnoreScriptFiles))); }
internal set { EditorPrefs.SetInt(PrefsIgnoreScriptFiles, AH_Utils.BoolToInt(value)); }
}
[SerializeField]
public string UserPreferencePath
{
get
{
if (EditorPrefs.HasKey(PrefsUserPrefPath))
return EditorPrefs.GetString(PrefsUserPrefPath);
else
return InitialUserPrefPath;
}
internal set { EditorPrefs.SetString(PrefsUserPrefPath, value); }
}
[SerializeField]
public string BuildInfoPath
{
get
{
if (EditorPrefs.HasKey(PrefsBuildInfoPath))
return EditorPrefs.GetString(PrefsBuildInfoPath);
else
return InitialBuildInfoPath;
}
internal set { EditorPrefs.SetString(PrefsBuildInfoPath, value); }
}
public GUIContent[] GUIcontentignoredLists = new GUIContent[5]
{
new GUIContent("Endings"),
new GUIContent("Types"),
new GUIContent("Folders"),
new GUIContent("Files"),
new GUIContent("Extentions")
};
#endregion
private void Init()
{
ignoredListPathEndsWith = new AH_IgnoreList(new IgnoredEventActionPathEndsWith(0, onIgnoreButtonDown), InitialValueIgnoredPathEndsWith, PrefsIgnoredPathEndsWith);
ignoredListTypes = new AH_ExclusionTypeList(new IgnoredEventActionType(1, onIgnoreButtonDown), InitialValueIgnoredTypes, PrefsIgnoredTypes);
ignoredListFolders = new AH_IgnoreList(new IgnoredEventActionFolder(2, onIgnoreButtonDown), InitialValueIgnoredFolders, PrefsIgnoredFolders);
ignoredListFiles = new AH_IgnoreList(new IgnoredEventActionFile(3, onIgnoreButtonDown), InitialValueIgnoredFiles, PrefsIgnoredFiles);
ignoredListExtensions = new AH_IgnoreList(new IgnoredEventActionExtension(4, onIgnoreButtonDown), InitialValueIgnoredExtensions, PrefsIgnoredExtensions);
//Todo subscribing to these 5 times, means that we might refresh buildinfo 5 times when reseting...We might be able to batch that somehow
ignoredListPathEndsWith.ListUpdatedEvent += OnListUpdatedEvent;
ignoredListTypes.ListUpdatedEvent += OnListUpdatedEvent;
ignoredListFolders.ListUpdatedEvent += OnListUpdatedEvent;
ignoredListFiles.ListUpdatedEvent += OnListUpdatedEvent;
ignoredListExtensions.ListUpdatedEvent += OnListUpdatedEvent;
}
private void OnListUpdatedEvent()
{
if (IgnoreListUpdatedEvent != null)
IgnoreListUpdatedEvent();
}
internal void ResetAll()
{
ignoredListPathEndsWith.Reset();
ignoredListTypes.Reset();
ignoredListExtensions.Reset();
ignoredListFiles.Reset();
ignoredListFolders.Reset();
AutoCreateLog = AH_SettingsManager.InitialValueAutoCreateLog;
AutoOpenLog = AH_SettingsManager.InitialValueAutoOpenLog;
AutoRefreshLog = AH_SettingsManager.InitialValueAutoRefreshLog;
EstimateAssetSize = AH_SettingsManager.InitialValueEstimateAssetSize;
HideButtonText = AH_SettingsManager.InitialValueHideButtonText;
IgnoreScriptFiles = AH_SettingsManager.InitialIgnoreScriptFiles;
UserPreferencePath = AH_SettingsManager.InitialUserPrefPath;
BuildInfoPath = AH_SettingsManager.InitialBuildInfoPath;
}
internal void DrawIgnored()
{
EditorGUILayout.HelpBox("IGNORE ASSETS" + Environment.NewLine + "-Select asset in project view to ignore", MessageType.Info);
EditorGUILayout.BeginHorizontal();
ignoredListChosenIndex = GUILayout.Toolbar(ignoredListChosenIndex, GUIcontentignoredLists);
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
drawIgnoreButtons();
switch (ignoredListChosenIndex)
{
case 0:
ignoredListPathEndsWith.OnGUI();
break;
case 1:
ignoredListTypes.OnGUI();
break;
case 2:
ignoredListFolders.OnGUI();
break;
case 3:
ignoredListFiles.OnGUI();
break;
case 4:
ignoredListExtensions.OnGUI();
break;
default:
break;
}
}
private void drawIgnoreButtons()
{
GUILayout.Space(12);
ignoredListPathEndsWith.DrawIgnoreButton();
ignoredListTypes.DrawIgnoreButton();
ignoredListFolders.DrawIgnoreButton();
ignoredListFiles.DrawIgnoreButton();
ignoredListExtensions.DrawIgnoreButton();
GUILayout.Space(4);
}
//Callback from Ignore button down
void onIgnoreButtonDown(int exclusionIndex)
{
ignoredListChosenIndex = exclusionIndex;
}
//public List<Type> GetIgnoredTypes() { return ignoredListTypes.GetIgnored(); }
public List<string> GetIgnoredPathEndsWith() { return ignoredListPathEndsWith.GetIgnored(); }
public List<string> GetIgnoredFileExtentions() { return ignoredListExtensions.GetIgnored(); }
public List<string> GetIgnoredFiles() { return ignoredListFiles.GetIgnored(); }
public List<string> GetIgnoredFolders() { return ignoredListFolders.GetIgnored(); }
private int drawSetting(string title, int value, int min, int max, string prefixAppend)
{
EditorGUILayout.PrefixLabel(title + prefixAppend);
return EditorGUILayout.IntSlider(value, min, max);
}
internal void DrawSettings()
{
EditorGUILayout.HelpBox("File save locations", MessageType.None);
UserPreferencePath = drawSettingsFolder("User prefs", UserPreferencePath, AH_SettingsManager.InitialUserPrefPath);
BuildInfoPath = drawSettingsFolder("Build info", BuildInfoPath, AH_SettingsManager.InitialBuildInfoPath);
EditorGUILayout.Space();
EditorGUILayout.HelpBox("Settings", MessageType.None);
AutoCreateLog = drawSetting("Auto create log when building", AutoCreateLog, AH_SettingsManager.InitialValueAutoCreateLog);
AutoOpenLog = drawSetting("Auto open log location after building", AutoOpenLog, AH_SettingsManager.InitialValueAutoOpenLog);
AutoRefreshLog = drawSetting("Auto refresh when project changes", AutoRefreshLog, AH_SettingsManager.InitialValueAutoRefreshLog);
EstimateAssetSize = drawSetting("Estimate runtime filesize for each asset", EstimateAssetSize, AH_SettingsManager.InitialValueEstimateAssetSize);
HideButtonText = drawSetting("Hide buttontexts", HideButtonText, AH_SettingsManager.InitialValueHideButtonText);
EditorGUILayout.BeginHorizontal();
EditorGUI.BeginChangeCheck();
IgnoreScriptFiles = drawSetting("Ignore script files", IgnoreScriptFiles, AH_SettingsManager.InitialIgnoreScriptFiles);
if (EditorGUI.EndChangeCheck())
{
//ADD OR REMOVE DEFINITION FOR PREPROCESSING
AH_PreProcessor.AddDefineSymbols(AH_PreProcessor.DefineScriptAllow, !IgnoreScriptFiles);
ignoredListTypes.IgnoreType(typeof(MonoScript), IgnoreScriptFiles);
if (!IgnoreScriptFiles)
{
EditorUtility.DisplayDialog("Now detecting unused scripts", "This is an experimental feature, and it cannot promise with any certainty that script files marked as unused are indeed unused. Only works with scripts that are directly used in a scene - Use at your own risk", "Ok");
}
}
GUIContent content = new GUIContent("EXPERIMENTAL FEATURE!", EditorGUIUtility.IconContent("console.warnicon.sml").image, "Cant be 100% sure script files are usused, so you need to handle with care");
//TODO PARTIAL CLASSES
//INHERITANCE
//AddComponent<Type>
//Reflection
//Interfaces
EditorGUILayout.LabelField(content, EditorStyles.boldLabel);
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
}
private string drawSettingsFolder(string title, string path, string defaultVal)
{
string validPath = path;
string newPath = "";
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Select", GUILayout.ExpandWidth(false)))
newPath = EditorUtility.OpenFolderPanel("Select folder", path, "");
if (newPath != "")
validPath = newPath;
GUIContent content = new GUIContent(title + ": " + AH_Utils.ShrinkPathMiddle(validPath, 44), title + " is saved at " + validPath);
GUILayout.Label(content, (defaultVal != path) ? EditorStyles.boldLabel : EditorStyles.label);
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
return validPath;
}
private bool drawSetting(string title, bool value, bool defaultVal)
{
return EditorGUILayout.ToggleLeft(title, value, (defaultVal != value) ? EditorStyles.boldLabel : EditorStyles.label);
}
internal bool HasIgnoredFolder(string folderPath, string assetID)
{
bool IgnoredEnding = ignoredListPathEndsWith.ContainsElement(folderPath, assetID);
bool folderIgnored = ignoredListFolders.ContainsElement(folderPath, assetID);
return IgnoredEnding || folderIgnored;
}
internal void AddIgnoredFolder(string element)
{
ignoredListFolders.AddToignoredList(element);
}
internal void AddIgnoredAssetTypes(string element)
{
ignoredListTypes.AddToignoredList(element);
}
internal void AddIgnoredAssetGUIDs(string element)
{
ignoredListFiles.AddToignoredList(element);
}
internal bool HasIgnoredAsset(string relativePath, string assetID)
{
bool IgnoredType = ignoredListTypes.ContainsElement(relativePath, assetID);
bool IgnoredFile = ignoredListFiles.ContainsElement(relativePath, assetID);
bool IgnoredExtension = ignoredListExtensions.ContainsElement(relativePath, assetID);
return IgnoredType || IgnoredFile || IgnoredExtension;
}
internal void SaveToFile()
{
var path = EditorUtility.SaveFilePanel(
"Save current settings",
AH_SerializationHelper.GetSettingFolder(),
"AH_UserPrefs_" + Environment.UserName,
AH_SerializationHelper.SettingsExtension);
if (path.Length != 0)
AH_SerializationHelper.SerializeAndSave(instance, path);
AssetDatabase.Refresh();
}
internal void LoadFromFile()
{
var path = EditorUtility.OpenFilePanel(
"settings",
AH_SerializationHelper.GetSettingFolder(),
AH_SerializationHelper.SettingsExtension
);
if (path.Length != 0)
{
AH_SerializationHelper.LoadSettings(instance, path);
ignoredListTypes.Save();
ignoredListPathEndsWith.Save();
ignoredListTypes.Save();
ignoredListExtensions.Save();
ignoredListFolders.Save();
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 212dc98f02d51a3469c3d1f4dc517bc0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,74 @@
using UnityEditor;
using UnityEngine;
namespace HeurekaGames.AssetHunterPRO
{
public class AH_SettingsWindow : EditorWindow
{
private const string WINDOWNAME = "AH Settings";
private Vector2 scrollPos;
private static AH_SettingsWindow m_window;
[UnityEditor.MenuItem("Tools/Asset Hunter PRO/Settings")]
[UnityEditor.MenuItem("Window/Heureka/Asset Hunter PRO/Settings")]
public static void OpenAssetHunter()
{
Init(false);
}
public static void Init(bool attemptDock, Docker.DockPosition dockPosition = Docker.DockPosition.Right)
{
bool firstInit = (m_window == null);
m_window = AH_SettingsWindow.GetWindow<AH_SettingsWindow>(WINDOWNAME, true);
m_window.titleContent.image = AH_EditorData.Instance.Settings.Icon;
AH_Window[] mainWindows = Resources.FindObjectsOfTypeAll<AH_Window>();
if (attemptDock && mainWindows.Length != 0 && firstInit)
{
HeurekaGames.Docker.Dock(mainWindows[0], m_window, dockPosition);
}
}
void OnInspectorUpdate()
{
Repaint();
}
void OnGUI()
{
if (!m_window)
Init(true);
Heureka_WindowStyler.DrawGlobalHeader(Heureka_WindowStyler.clr_dBlue, "SETTINGS");
scrollPos = EditorGUILayout.BeginScrollView(scrollPos);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Reset Settings"))
{
if (EditorUtility.DisplayDialog("Reset Settings", "Are you sure you want to reset Settings completely", "OK", "CANCEL"))
{
AH_SettingsManager.Instance.ResetAll();
}
}
if (GUILayout.Button("Save prefs to file"))
AH_SettingsManager.Instance.SaveToFile();
if (GUILayout.Button("Load prefs from file"))
AH_SettingsManager.Instance.LoadFromFile();
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
AH_SettingsManager.Instance.DrawSettings();
EditorGUILayout.Space();
AH_SettingsManager.Instance.DrawIgnored();
EditorGUILayout.EndScrollView();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2611a0e0cb88863459b1f2cfb4b68112
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,302 @@
using System;
using System.Collections;
using System.Collections.Generic;
using HeurekaGames.AssetHunterPRO.BaseTreeviewImpl.AssetTreeView;
using UnityEngine;
using UnityEditor;
using System.Linq;
using HeurekaGames.AssetHunterPRO.BaseTreeviewImpl;
using System.IO;
namespace HeurekaGames.AssetHunterPRO
{
[System.Serializable]
public class AH_TreeViewSelectionInfo
{
public delegate void AssetDeletedHandler();
public static event AssetDeletedHandler OnAssetDeleted;
private bool hasSelection;
public bool HasSelection
{
get
{
return hasSelection;
}
}
public const float Height = 64;
private AH_MultiColumnHeader multiColumnHeader;
private List<AH_TreeviewElement> selection;
internal void Reset()
{
selection = null;
hasSelection = false;
}
internal void SetSelection(AH_TreeViewWithTreeModel treeview, IList<int> selectedIds)
{
multiColumnHeader = (AH_MultiColumnHeader)(treeview.multiColumnHeader);
selection = new List<AH_TreeviewElement>();
foreach (var itemID in selectedIds)
{
selection.Add(treeview.treeModel.Find(itemID));
}
hasSelection = (selection.Count > 0);
//If we have more, select the assets in project view
if (hasSelection)
{
if (selection.Count > 1)
{
UnityEngine.Object[] selectedObjects = new UnityEngine.Object[selection.Count];
for (int i = 0; i < selection.Count; i++)
{
selectedObjects[i] = AssetDatabase.LoadMainAssetAtPath(selection[i].RelativePath);
}
Selection.objects = selectedObjects;
}
else
Selection.activeObject = AssetDatabase.LoadMainAssetAtPath(selection[0].RelativePath);
AH_Utils.PingObjectAtPath(selection[selection.Count - 1].RelativePath, false);
}
}
internal void OnGUISelectionInfo(Rect selectionRect)
{
GUILayout.BeginArea(selectionRect);
//TODO MAKE SURE WE DONT DO ALL OF THIS EACH FRAME, BUT CACHE THE SELECTION DATA
using (new EditorGUILayout.HorizontalScope())
{
if (selection.Count == 1)
{
drawSingle();
}
else
{
drawMulti();
}
}
GUILayout.EndArea();
}
private void drawSingle()
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.BeginVertical();
drawAssetPreview(true);
EditorGUILayout.EndVertical();
//Draw info from single asset
EditorGUILayout.BeginVertical();
GUILayout.Label(selection[0].RelativePath);
if (!selection[0].IsFolder)
{
GUILayout.Label("(" + selection[0].AssetType + ")");
}
EditorGUILayout.EndVertical();
GUILayout.FlexibleSpace();
if (selection[0].IsFolder)
DrawDeleteFolderButton(selection[0]);
else
drawDeleteAssetsButton();
EditorGUILayout.EndHorizontal();
}
private void drawMulti()
{
//Make sure we have not selected folders
bool allFolders = selection.All(val => val.IsFolder);
bool allFiles = !selection.Any(val => val.IsFolder);
var allSameType = selection.All(var => var.AssetType == selection[0].AssetType);
//Find if we have selected nested assets or folders
var containsNested = selection.Any(file => selection.Any(y => file != y && (y.RelativePath.StartsWith(file.RelativePath) && y.RelativePath.Split(Path.DirectorySeparatorChar).Length != file.RelativePath.Split(Path.DirectorySeparatorChar).Length)));
drawAssetPreview(allSameType);
EditorGUILayout.BeginHorizontal();
//Draw info from multiple
EditorGUILayout.BeginVertical();
//Identical files
if (allSameType && allFiles)
{
GUILayout.Label(selection[0].AssetType.ToString() + " (" + selection.Count() + ")");
}
//all folders
else if (allSameType)
{
GUILayout.Label("Folders (" + selection.Count() + ")");
}
//Non identical selection
else
{
GUILayout.Label("Items (" + selection.Count() + ")");
}
EditorGUILayout.EndVertical();
if (!containsNested)
drawDeleteAssetsButton();
EditorGUILayout.EndHorizontal();
}
private void drawDeleteAssetsButton()
{
if (multiColumnHeader.ShowMode != AH_MultiColumnHeader.AssetShowMode.Unused)
return;
long combinedSize = 0;
foreach (var item in selection)
{
if (item.IsFolder)
combinedSize += item.GetFileSizeRecursively(AH_MultiColumnHeader.AssetShowMode.Unused);
else
combinedSize += item.FileSize;
}
if (GUILayout.Button("Delete " + (AH_Utils.GetSizeAsString(combinedSize)), GUILayout.Width(160), GUILayout.Height(32)))
deleteUnusedAssets();
}
private void DrawDeleteFolderButton(AH_TreeviewElement folder)
{
if (multiColumnHeader.ShowMode != AH_MultiColumnHeader.AssetShowMode.Unused)
return;
string description = "Delete unused assets from folder";
GUIContent content = new GUIContent("Delete " + (AH_Utils.GetSizeAsString(folder.GetFileSizeRecursively(AH_MultiColumnHeader.AssetShowMode.Unused))), description);
GUIStyle style = new GUIStyle(GUI.skin.button);
DrawDeleteFolderButton(content, folder, style, description, "Do you want to delete all unused assets from:" + Environment.NewLine + folder.RelativePath, GUILayout.Width(160), GUILayout.Height(32));
}
public void DrawDeleteFolderButton(GUIContent content, AH_TreeviewElement folder, GUIStyle style, string dialogHeader, string dialogDescription, params GUILayoutOption[] layout)
{
if (GUILayout.Button(content, style, layout))
deleteUnusedFromFolder(dialogHeader, dialogDescription, folder);
}
private void drawAssetPreview(bool bDraw)
{
GUIContent content = new GUIContent();
//Draw asset preview
if (bDraw && !selection[0].IsFolder)
{
var preview = AssetPreview.GetAssetPreview(AssetDatabase.LoadMainAssetAtPath(selection[0].RelativePath));
content = new GUIContent(preview);
}
//Draw Folder icon
else if (bDraw)
content = EditorGUIUtility.IconContent("Folder Icon");
GUILayout.Label(content,GUILayout.Width(Height), GUILayout.Height(Height));
}
private void deleteUnusedAssets()
{
int choice = EditorUtility.DisplayDialogComplex("Delete unused assets", "Do you want to delete the selected assets", "Yes", "Cancel", "Backup");
List<string> affectedAssets = new List<string>();
if (choice == 0)//Delete
{
foreach (var item in selection)
{
affectedAssets.Add(item.RelativePath);
}
deleteMultipleAssets(affectedAssets);
}
else if (choice == 2)//Backup
{
foreach (var item in selection)
{
affectedAssets.Add(item.RelativePath);
exportAssetsToPackage("Backup as unitypackage", affectedAssets);
}
}
}
private void deleteUnusedFromFolder(AH_TreeviewElement folder)
{
deleteUnusedFromFolder("Delete unused assets from folder", "Do you want to delete all unused assets from:" + Environment.NewLine + folder.RelativePath, folder);
}
private void deleteUnusedFromFolder(string header, string description, AH_TreeviewElement folder)
{
int choice = EditorUtility.DisplayDialogComplex(header, description, "Yes", "Cancel", "Backup (Slow)");
List<string> affectedAssets = new List<string>();
if (choice != 1)//Not Cancel
{
//Collect affected assets
affectedAssets = folder.GetUnusedPathsRecursively();
}
if (choice == 0)//Delete
{
deleteMultipleAssets(affectedAssets);
}
else if (choice == 2)//Backup
{
exportAssetsToPackage("Backup as unitypackage", affectedAssets);
}
}
private void exportAssetsToPackage(string header, List<string> affectedAssets)
{
string filename = Environment.UserName + "_Backup_" + "_" + AH_SerializationHelper.GetDateString();
string savePath = EditorUtility.SaveFilePanel(
header,
AH_SerializationHelper.GetBackupFolder(),
filename,
"unitypackage");
if (!string.IsNullOrEmpty(savePath))
{
EditorUtility.DisplayProgressBar("Backup", "Creating backup of " + affectedAssets.Count() + " assets", 0f);
AssetDatabase.ExportPackage(affectedAssets.ToArray<string>(), savePath, ExportPackageOptions.Default);
EditorUtility.ClearProgressBar();
EditorUtility.RevealInFinder(savePath);
deleteMultipleAssets(affectedAssets);
}
}
private void deleteMultipleAssets(List<string> affectedAssets)
{
double startTime = EditorApplication.timeSinceStartup;
for (int i = 0; i < affectedAssets.Count(); i++)
{
EditorUtility.DisplayProgressBar("Deleting unused assets", "Deleting " + i + "/" + affectedAssets.Count() + Environment.NewLine + affectedAssets[i], ((float)i) / ((float)affectedAssets.Count()));
}
#if UNITY_2020_1_OR_NEWER
List<string> failedPaths = new List<string>();
AssetDatabase.DeleteAssets(affectedAssets.ToArray(), failedPaths);
#else
foreach (var asset in affectedAssets)
{
AssetDatabase.DeleteAsset(asset);
}
#endif
EditorUtility.ClearProgressBar();
AssetDatabase.Refresh();
if (OnAssetDeleted != null)
OnAssetDeleted();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 34d0a9acdf4dbcb4f82cfd0156081719
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,18 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace HeurekaGames.AssetHunterPRO
{
public static class AH_UIUtilities
{
public static bool DrawSelectionButton(GUIContent content)
{
GUIContent btnContent = new GUIContent(content);
if (AH_SettingsManager.Instance.HideButtonText)
btnContent.text = null;
return GUILayout.Button(btnContent, GUILayout.MaxHeight(AH_SettingsManager.Instance.HideButtonText ? AH_Window.ButtonMaxHeight * 2f : AH_Window.ButtonMaxHeight));
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2dfbd02bf3ebeca47b215bc02773c500
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,349 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace HeurekaGames.AssetHunterPRO
{
public static class AH_Utils
{
internal static string GetSizeAsString(ulong byteSize)
{
return GetSizeAsString((long)byteSize);
}
internal static string GetSizeAsString(long byteSize)
{
string sizeAsString = String.Empty;
float b = byteSize;
float kb = b / 1024f;
float mb = kb / 1024;
float gb = mb / 1024;
if (gb >= 1)
{
sizeAsString = String.Format(((float)Math.Round(gb, 1)).ToString(), "0.00") + " gb";
}
else if (mb >= 1)
{
sizeAsString = String.Format(((float)Math.Round(mb, 1)).ToString(), "0.00") + " mb";
}
else if (kb >= 1)
{
sizeAsString = String.Format(((float)Math.Round(kb, 1)).ToString(), "0.00") + " kb";
}
else if (byteSize >= 0)
{
sizeAsString = String.Format(((float)Math.Round(b, 1)).ToString(), "0.00") + " b";
}
return sizeAsString;
}
internal static void GetRelativePathAndAssetID(string absPath, out string relativePath, out string assetGuid)
{
relativePath = FileUtil.GetProjectRelativePath(absPath);
assetGuid = AssetDatabase.AssetPathToGUID(relativePath);
}
public static string[] GetEnabledSceneNamesInBuild()
{
return (from scene in EditorBuildSettings.scenes where scene.enabled select scene.path).ToArray();
}
public static string[] GetAllSceneNamesInBuild()
{
return (from scene in EditorBuildSettings.scenes select scene.path).ToArray();
}
public static string[] GetAllSceneNames()
{
return (from scene in AssetDatabase.GetAllAssetPaths() where scene.EndsWith(".unity") select scene).ToArray();
}
public static System.String BytesToString(long byteCount)
{
string[] suf = { "B", "KB", "MB", "GB", "TB", "PB", "EB" }; //Longs run out around EB
if (byteCount == 0)
return "0" + suf[0];
long bytes = Math.Abs(byteCount);
int place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024)));
double num = Math.Round(bytes / Math.Pow(1024, place), 1);
return (Math.Sign(byteCount) * num).ToString() + suf[place];
}
internal static void PingObjectAtPath(string assetPath, bool select)
{
UnityEngine.Object loadObj = AssetDatabase.LoadAssetAtPath(assetPath, typeof(UnityEngine.Object));
EditorGUIUtility.PingObject(loadObj);
if (select)
Selection.activeObject = loadObj;
}
/// <summary>
/// A method to shorten file paths using ... delimiter
/// </summary>
/// <param name="absolutepath">The path to compress</param>
/// <param name="limit">The maximum length</param>
/// <returns></returns>
public static string ShrinkPathMiddle(string absolutepath, int limit)
{
//no path provided
if (string.IsNullOrEmpty(absolutepath))
{
return "";
}
var name = Path.GetFileName(absolutepath);
int namelen = name.Length;
int pathlen = absolutepath.Length;
var dir = absolutepath.Substring(0, pathlen - namelen);
string delimiter = "…";
int delimlen = delimiter.Length;
int idealminlen = namelen + delimlen;
var slash = (absolutepath.IndexOf("/") > -1 ? "/" : "\\");
//less than the minimum amt
if (limit < ((2 * delimlen) + 1))
{
return "";
}
//fullpath
if (limit >= pathlen)
{
return absolutepath;
}
//file name condensing
if (limit < idealminlen)
{
return delimiter + name.Substring(0, (limit - (2 * delimlen))) + delimiter;
}
//whole name only, no folder structure shown
if (limit == idealminlen)
{
return delimiter + name;
}
return dir.Substring(0, (limit - (idealminlen + 1))) + delimiter + slash + name;
}
internal static int BoolToInt(bool value)
{
return value ? 1 : 0;
}
internal static bool IntToBool(int value)
{
return (value != 0) ? true : false;
}
public static string ShrinkPathEnd(string absolutepath, int limit)
{
//no path provided
if (string.IsNullOrEmpty(absolutepath))
{
return "";
}
var name = Path.GetFileName(absolutepath);
int namelen = name.Length;
int pathlen = absolutepath.Length;
string delimiter = "…";
int delimlen = delimiter.Length;
var slash = (absolutepath.IndexOf("/") > -1 ? "/" : "\\");
//filesname longer than limit
if (namelen >= limit)
{
return name;
}
//fullpath
if (limit >= pathlen)
{
return absolutepath;
}
//Get substring within limit
var pathWithinLimit = absolutepath.Substring(pathlen - limit, limit);
int indexOfSlash = pathWithinLimit.IndexOf(slash);
return delimiter + pathWithinLimit.Substring(indexOfSlash, pathWithinLimit.Length - indexOfSlash);
}
internal static List<Texture> GetTargetGroupAssetDependencies(BuildTargetGroup targetGroup)
{
List<Texture> buildTargetAssetDependencies = new List<Texture>();
//Run through icons, splashscreens etc and include them as being used
Texture2D[] targetGroupIcons = PlayerSettings.GetIconsForTargetGroup(targetGroup);
List<Texture2D> additionalTargetGroupIcons = getAdditionalTargetAssets(targetGroup);
Texture2D[] unknownTargetGroupIcons = PlayerSettings.GetIconsForTargetGroup(BuildTargetGroup.Unknown);
PlayerSettings.SplashScreenLogo[] splashLogos = PlayerSettings.SplashScreen.logos;
//Loop default targetgroup icons
for (int i = 0; i < unknownTargetGroupIcons.Length; i++)
{
addTextureToPlayerSettingsList(ref buildTargetAssetDependencies, unknownTargetGroupIcons[i]);
}
//Loop targetgroup icons
for (int i = 0; i < targetGroupIcons.Length; i++)
{
addTextureToPlayerSettingsList(ref buildTargetAssetDependencies, targetGroupIcons[i]);
}
//Loop additional targetgroup icons
if (additionalTargetGroupIcons != null)
for (int i = 0; i < additionalTargetGroupIcons.Count; i++)
{
addTextureToPlayerSettingsList(ref buildTargetAssetDependencies, additionalTargetGroupIcons[i]);
}
//Loop splash
for (int i = 0; i < splashLogos.Length; i++)
{
addTextureToPlayerSettingsList(ref buildTargetAssetDependencies, splashLogos[i].logo);
}
//Get all the custom playersetting textures
addTextureToPlayerSettingsList(ref buildTargetAssetDependencies, PlayerSettings.defaultCursor);
addTextureToPlayerSettingsList(ref buildTargetAssetDependencies, PlayerSettings.virtualRealitySplashScreen);
addTextureToPlayerSettingsList(ref buildTargetAssetDependencies, PlayerSettings.SplashScreen.background);
addTextureToPlayerSettingsList(ref buildTargetAssetDependencies, PlayerSettings.SplashScreen.backgroundPortrait);
#if !UNITY_2019_1_OR_NEWER
addTextureToPlayerSettingsList(ref buildTargetAssetDependencies, PlayerSettings.resolutionDialogBanner);
#endif
return buildTargetAssetDependencies;
}
private static List<Texture2D> getAdditionalTargetAssets(BuildTargetGroup targetGroup)
{
switch (targetGroup)
{
#if !UNITY_2018_3_OR_NEWER
case BuildTargetGroup.N3DS:
{
break;
}
case BuildTargetGroup.PSP2:
{
break;
}
case BuildTargetGroup.Tizen:
{
break;
}
#endif
#if !UNITY_2019_3_OR_NEWER
case BuildTargetGroup.Facebook:
{
break;
}
#endif
case BuildTargetGroup.Android:
{
break;
}
case BuildTargetGroup.iOS:
{
break;
}
case BuildTargetGroup.PS4:
{
Debug.Log("AH: Need " + targetGroup + " documentation to add platform specific images and assets");
break;
}
case BuildTargetGroup.Standalone:
{
break;
}
case BuildTargetGroup.Switch:
{
return PlayerSettings.Switch.icons.ToList();
}
case BuildTargetGroup.tvOS:
{
break;
}
case BuildTargetGroup.WebGL:
{
break;
}
case BuildTargetGroup.WSA:
{
List<Texture2D> textures = new List<Texture2D>();
#if !UNITY_2021_1_OR_NEWER
//Obsolete at some point in 2021
textures.Add(AssetDatabase.LoadAssetAtPath<Texture2D>(PlayerSettings.WSA.packageLogo));
#endif
HashSet<PlayerSettings.WSAImageScale> exceptionScales = new HashSet<PlayerSettings.WSAImageScale>();
foreach (PlayerSettings.WSAImageType imageType in Enum.GetValues(typeof(PlayerSettings.WSAImageType)))
{
foreach (PlayerSettings.WSAImageScale imageScale in Enum.GetValues(typeof(PlayerSettings.WSAImageScale)))
{
try
{
string imagePath = PlayerSettings.WSA.GetVisualAssetsImage(imageType, imageScale);
textures.Add(AssetDatabase.LoadAssetAtPath<Texture2D>(imagePath));
}
catch (Exception)
{
exceptionScales.Add(imageScale);
//If that scale doesn't apply to the given WSA image type
}
}
}
if (exceptionScales.Count >= 1)
{
string scaleListString = "";
foreach (var item in exceptionScales)
{
scaleListString += item.ToString() + (exceptionScales.ElementAt(exceptionScales.Count - 1) == item ? "":", ");
}
Debug.Log("GetVisualAssetsImage method missing support for WSA image scale: " + scaleListString);
}
return textures;
}
case BuildTargetGroup.XboxOne:
{
Debug.Log("AH: Need " + targetGroup + " documentation to add platform specific images and assets");
break;
}
default:
{
Debug.LogWarning("AH: Targetgroup unknown: " + targetGroup);
break;
}
}
return null;
}
private static void addTextureToPlayerSettingsList(ref List<Texture> playerSettingsTextures, Sprite sprite)
{
if (sprite != null)
addTextureToPlayerSettingsList(ref playerSettingsTextures, sprite.texture);
}
private static void addTextureToPlayerSettingsList(ref List<Texture> playerSettingsTextures, Texture2D texture)
{
if ((texture != null) && AssetDatabase.IsMainAsset(texture))
playerSettingsTextures.Add(texture);
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 4cdbc131ece9fb340865ffc0cc57f156
timeCreated: 1529997872
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,100 @@
/*using System;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;
using UnityEngine;
using UnityEngine.Assertions;
namespace HeurekaGames.AssetHunterPRO
{
#if UNITY_2018_1_OR_NEWER
[InitializeOnLoad]
public static class AH_VersionUpgrader
{
public static ListRequest Request { get; }
static AH_VersionUpgrader()
{
Request = Client.List(); // List packages installed for the Project
EditorApplication.update += verifyPackages;
//https://docs.unity3d.com/Manual/upm-api.html
}
[UnityEditor.Callbacks.DidReloadScripts]
private static void OnScriptsReloaded()
{
Debug.LogWarning("OnScriptsReloaded");
//Request = Client.List(); // List packages installed for the Project
//EditorApplication.update += verifyPackages;
}
//See if we have addressables package installed and set DefineSymbol accordingly
private static void verifyPackages()
{
if (Request.IsCompleted)
{
bool foundAddressables = false;
if (Request.Status == StatusCode.Success)
foreach (var package in Request.Result)
{
if (package.name == "com.unity.addressables")
{
Version version = new Version(package.version);
if (version >= new Version("1.2.0"))
{
foundAddressables = true;
AddAddressables(true);
}
}
}
else if (Request.Status >= StatusCode.Failure)
Debug.Log(Request.Error.message);
if(!foundAddressables)
AddAddressables(false);
EditorApplication.update -= verifyPackages;
}
}
private static void AddAddressables(bool useAddressables)
{
Debug.LogWarning("AddAddressables " + useAddressables);
UnityEditor.Compilation.Assembly[] editorAssemblies =
UnityEditor.Compilation.CompilationPipeline.GetAssemblies(UnityEditor.Compilation.AssembliesType.Editor);
var AddressablesAssembly = editorAssemblies.SingleOrDefault(a => a.name.Equals("Unity.Addressables.Editor"));
var AH_Assembly = editorAssemblies.SingleOrDefault(a => a.name.Equals("AH_AssemblyDefinition"));
Assert.IsNotNull(AH_Assembly, "No AHP Assembly Definition Present in project");
bool hasRefToAddressables = AH_Assembly.assemblyReferences.Any(a => a.name.Equals("Unity.Addressables.Editor"));
var newAssemblyRefList = AH_Assembly.assemblyReferences.ToList();
//If we have reference to addressables assembly but dont want it
if (!useAddressables && hasRefToAddressables)
newAssemblyRefList.Remove(AddressablesAssembly);
else if (useAddressables && !hasRefToAddressables)
newAssemblyRefList.Add(AddressablesAssembly);
//Get the readonly field for adding assembly references
FieldInfo field = typeof(UnityEditor.Compilation.Assembly).GetField($"<{nameof(UnityEditor.Compilation.Assembly.assemblyReferences)}>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic);
var value = field.GetValue(AH_Assembly);
field.SetValue(AH_Assembly, newAssemblyRefList.ToArray());
var pipe = UnityEditor.Compilation.CompilationPipeline.GetAssemblyDefinitionFilePathFromAssemblyName("AH_AssemblyDefinition");
//CompilationPipeline
//var builder = new UnityEditor.Compilation.AssemblyBuilder(AddressablesAssembly.
//var stronglyTypedField = value as UnityEditor.Compilation.Assembly[];
//stronglyTypedField = newAssemblyRefList.ToArray();
//UnityEditor.Compilation.CompilationPipeline.
//AH_PreProcessor.AddDefineSymbols("ADRESSABLES_1_2_0_OR_NEWER", useAddressables);
}
}
#endif
}
*/

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 137ae79880bd61047a9749429269f90c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,489 @@
using UnityEngine;
using UnityEditor;
using System;
using UnityEditor.IMGUI.Controls;
using HeurekaGames.AssetHunterPRO.BaseTreeviewImpl.AssetTreeView;
using HeurekaGames.AssetHunterPRO.BaseTreeviewImpl;
//Only avaliable in 2018
#if UNITY_2018_1_OR_NEWER
using UnityEditor.Build.Reporting;
#endif
namespace HeurekaGames.AssetHunterPRO
{
public class AH_Window : EditorWindow
{
public const int WINDOWMENUITEMPRIO = 11;
public const string VERSION = "2.2.1";
private static AH_Window m_window;
[NonSerialized] bool m_Initialized;
[SerializeField] TreeViewState m_TreeViewState; // Serialized in the window layout file so it survives assembly reloading
[SerializeField] MultiColumnHeaderState m_MultiColumnHeaderState;
SearchField m_SearchField;
private AH_TreeViewWithTreeModel m_TreeView;
[SerializeField] public AH_BuildInfoManager buildInfoManager;
public bool m_BuildLogLoaded { get; set; }
//Button guiContent
[SerializeField] GUIContent guiContentLoadBuildInfo;
[SerializeField] GUIContent guiContentSettings;
[SerializeField] GUIContent guiContentGenerateReferenceGraph;
[SerializeField] GUIContent guiContentDuplicates;
//Only avaliable in 2018
#if UNITY_2018_1_OR_NEWER
[SerializeField] GUIContent guiContentBuildReport;
#endif
[SerializeField] GUIContent guiContentReadme;
[SerializeField] GUIContent guiContentDeleteAll;
[SerializeField] GUIContent guiContentRefresh;
//UI Rect
Vector2 uiStartPos = new Vector2(10, 50);
public static float ButtonMaxHeight = 18;
//Add menu named "Asset Hunter" to the window menu
[UnityEditor.MenuItem("Tools/Asset Hunter PRO/Asset Hunter PRO", priority = WINDOWMENUITEMPRIO)]
[UnityEditor.MenuItem("Window/Heureka/Asset Hunter PRO/Asset Hunter PRO _%h", priority = WINDOWMENUITEMPRIO)]
public static void OpenAssetHunter()
{
if (!m_window)
initializeWindow();
}
private static AH_Window initializeWindow()
{
//Open ReadMe
Heureka_PackageDataManagerEditor.SelectReadme();
m_window = EditorWindow.GetWindow<AH_Window>();
AH_TreeViewSelectionInfo.OnAssetDeleted += m_window.OnAssetDeleted;
#if UNITY_2018_1_OR_NEWER
EditorApplication.projectChanged += m_window.OnProjectChanged;
#elif UNITY_5_6_OR_NEWER
EditorApplication.projectWindowChanged += m_window.OnProjectChanged;
#endif
if (m_window.buildInfoManager == null)
m_window.buildInfoManager = ScriptableObject.CreateInstance<AH_BuildInfoManager>();
m_window.initializeGUIContent();
//Subscribe to changes to list of ignored items
AH_SettingsManager.Instance.IgnoreListUpdatedEvent += m_window.OnIgnoreListUpdatedEvent;
return m_window;
}
internal static AH_BuildInfoManager GetBuildInfoManager()
{
if (!m_window)
initializeWindow();
return m_window.buildInfoManager;
}
private void OnEnable()
{
AH_SerializationHelper.NewBuildInfoCreated += onBuildInfoCreated;
}
private void OnDisable()
{
AH_SerializationHelper.NewBuildInfoCreated -= onBuildInfoCreated;
}
void OnInspectorUpdate()
{
if (!m_window)
initializeWindow();
}
void OnGUI()
{
/*if (Application.isPlaying)
return;*/
InitIfNeeded();
doHeader();
if (buildInfoManager == null || !buildInfoManager.HasSelection)
{
doNoBuildInfoLoaded();
return;
}
if (buildInfoManager.IsProjectClean() && ((AH_MultiColumnHeader)m_TreeView.multiColumnHeader).ShowMode == AH_MultiColumnHeader.AssetShowMode.Unused)
{
Heureka_WindowStyler.DrawCenteredImage(m_window, AH_EditorData.Instance.AchievementIcon.Icon);
return;
}
doSearchBar(toolbarRect);
doTreeView(multiColumnTreeViewRect);
doBottomToolBar(bottomToolbarRect);
}
void OnProjectChanged()
{
buildInfoManager.ProjectDirty = true;
}
//Callback
private void OnAssetDeleted()
{
//TODO need to improve the deletion of empty folder. Currently leaves meta file behind, causing warnings
if (EditorUtility.DisplayDialog("Delete empty folders", "Do you want to delete any empty folders?", "Yes", "No"))
{
deleteEmptyFolders();
}
//This might be called excessively
if (AH_SettingsManager.Instance.AutoRefreshLog)
RefreshBuildLog();
}
//callback
private void onBuildInfoCreated(string path)
{
if (EditorUtility.DisplayDialog(
"New buildinfo log created",
"Do you want to load it into Asset Hunter",
"Ok", "Cancel"))
{
m_Initialized = false;
buildInfoManager.SelectBuildInfo(path);
}
}
void InitIfNeeded()
{
//We dont need to do stuff when in play mode
if (buildInfoManager && buildInfoManager.HasSelection && !m_Initialized)
{
// Check if it already exists (deserialized from window layout file or scriptable object)
if (m_TreeViewState == null)
m_TreeViewState = new TreeViewState();
bool firstInit = m_MultiColumnHeaderState == null;
var headerState = AH_TreeViewWithTreeModel.CreateDefaultMultiColumnHeaderState(multiColumnTreeViewRect.width);
if (MultiColumnHeaderState.CanOverwriteSerializedFields(m_MultiColumnHeaderState, headerState))
MultiColumnHeaderState.OverwriteSerializedFields(m_MultiColumnHeaderState, headerState);
m_MultiColumnHeaderState = headerState;
var multiColumnHeader = new AH_MultiColumnHeader(headerState);
if (firstInit)
multiColumnHeader.ResizeToFit();
var treeModel = new TreeModel<AH_TreeviewElement>(buildInfoManager.GetTreeViewData());
m_TreeView = new AH_TreeViewWithTreeModel(m_TreeViewState, multiColumnHeader, treeModel);
m_SearchField = new SearchField();
m_SearchField.downOrUpArrowKeyPressed += m_TreeView.SetFocusAndEnsureSelectedItem;
m_Initialized = true;
buildInfoManager.ProjectDirty = false;
}
//This is an (ugly) fix to make sure we dotn loose our icons due to some singleton issues after play/stop
if (guiContentRefresh.image == null)
initializeGUIContent();
}
private void deleteEmptyFolders()
{
checkEmptyFolder(Application.dataPath);
AssetDatabase.Refresh();
}
//This needs some work, it leaves the meta file. TAken out of codebase until a fix has been found
private bool checkEmptyFolder(string dataPath)
{
if (dataPath.EndsWith(".git", StringComparison.InvariantCultureIgnoreCase))
return false;
string[] files = System.IO.Directory.GetFiles(dataPath);
bool hasValidAsset = false;
for (int i = 0; i < files.Length; i++)
{
string relativePath;
string assetID;
AH_Utils.GetRelativePathAndAssetID(files[i], out relativePath, out assetID);
//This folder has a valid asset inside
if (!string.IsNullOrEmpty(assetID))
{
hasValidAsset = true;
break;
}
}
string[] folders = System.IO.Directory.GetDirectories(dataPath);
bool hasFolderWithContents = false;
for (int i = 0; i < folders.Length; i++)
{
bool folderIsEmpty = checkEmptyFolder(folders[i]);
if (!folderIsEmpty)
{
hasFolderWithContents = true;
}
else
{
//if (EditorUtility.DisplayDialog("Delete folder", folders[i] + " seems to be empty, do you want to delete it?", "Yes", "No"))
{
Debug.Log("AH: Deleting empty folder " + folders[i]);
AssetDatabase.DeleteAsset(FileUtil.GetProjectRelativePath(folders[i]));
}
}
}
return (!hasValidAsset && !hasFolderWithContents);
}
private void initializeGUIContent()
{
titleContent = new GUIContent("Asset Hunter", AH_EditorData.Instance.WindowPaneIcon.Icon);
guiContentLoadBuildInfo = new GUIContent("Load", AH_EditorData.Instance.LoadLogIcon.Icon, "Load info from a previous build");
guiContentSettings = new GUIContent("Settings", AH_EditorData.Instance.Settings.Icon, "Open settings");
guiContentGenerateReferenceGraph = new GUIContent("Dependencies", AH_EditorData.Instance.RefFromIcon.Icon, "See asset dependency graph");
guiContentDuplicates = new GUIContent("Duplicates", AH_EditorData.Instance.DuplicateIcon.Icon, "Find duplicate assets");
//Only avaliable in 2018
#if UNITY_2018_1_OR_NEWER
guiContentBuildReport = new GUIContent("Report", AH_EditorData.Instance.ReportIcon.Icon, "Build report overview (Build size information)");
#endif
guiContentReadme = new GUIContent("Info", AH_EditorData.Instance.HelpIcon.Icon, "Open the readme file for all installed Heureka Games products");
guiContentDeleteAll = new GUIContent("Clean ALL", AH_EditorData.Instance.DeleteIcon.Icon, "Delete ALL unused assets in project ({0}) Remember to manually exclude relevant assets in the settings window");
guiContentRefresh = new GUIContent(AH_EditorData.Instance.RefreshIcon.Icon, "Refresh data");
}
private void doNoBuildInfoLoaded()
{
Heureka_WindowStyler.DrawCenteredMessage(m_window, AH_EditorData.Instance.WindowHeaderIcon.Icon, 380f, 110f, "Buildinfo not yet loaded" + Environment.NewLine + "Load existing / create new build");
}
private void doHeader()
{
Heureka_WindowStyler.DrawGlobalHeader(Heureka_WindowStyler.clr_Pink, "ASSET HUNTER PRO", VERSION);
EditorGUILayout.BeginHorizontal();
bool infoLoaded = (buildInfoManager != null && buildInfoManager.HasSelection);
if (infoLoaded)
{
GUIContent RefreshGUIContent = new GUIContent(guiContentRefresh);
Color origColor = GUI.color;
if (buildInfoManager.ProjectDirty)
{
GUI.color = Heureka_WindowStyler.clr_Red;
RefreshGUIContent.tooltip = String.Format("{0}{1}", RefreshGUIContent.tooltip, " (Project has changed which means that treeview is out of date)");
}
if (doSelectionButton(RefreshGUIContent))
RefreshBuildLog();
GUI.color = origColor;
}
if (doSelectionButton(guiContentLoadBuildInfo))
openBuildInfoSelector();
if (doSelectionButton(guiContentDuplicates))
AH_DuplicatesWindow.Init(Docker.DockPosition.Left);
if (doSelectionButton(guiContentGenerateReferenceGraph))
AH_DependencyGraphWindow.Init(Docker.DockPosition.Right);
//Only avaliable in 2018
#if UNITY_2018_1_OR_NEWER
if (infoLoaded && doSelectionButton(guiContentBuildReport))
AH_BuildReportWindow.Init();
#endif
if (doSelectionButton(guiContentSettings))
AH_SettingsWindow.Init(true);
/*
#if AH_HAS_OLD_INSTALLED
//Transfer settings to PRO
GUIContent TransferSettingsContent = new GUIContent("Transfer Settings", "Transfer your settings from old Asset Hunter into PRO");
if (AH_VersionUpgrader.VersionUpgraderReady && GUILayout.Button(TransferSettingsContent, GUILayout.MaxHeight(18)))
AH_VersionUpgrader.RunUpgrade();
#endif
*/
if (infoLoaded && m_TreeView.GetCombinedUnusedSize() > 0)
{
string sizeAsString = AH_Utils.GetSizeAsString(m_TreeView.GetCombinedUnusedSize());
GUIContent instancedGUIContent = new GUIContent(guiContentDeleteAll);
instancedGUIContent.tooltip = string.Format(instancedGUIContent.tooltip, sizeAsString);
if (AH_SettingsManager.Instance.HideButtonText)
instancedGUIContent.text = null;
GUIStyle btnStyle = "button";
GUIStyle newStyle = new GUIStyle(btnStyle);
newStyle.normal.textColor = Heureka_WindowStyler.clr_Pink;
m_TreeView.DrawDeleteAllButton(instancedGUIContent, newStyle, GUILayout.MaxHeight(AH_SettingsManager.Instance.HideButtonText ? ButtonMaxHeight * 2f : ButtonMaxHeight));
}
GUILayout.FlexibleSpace();
GUILayout.Space(20);
if (m_TreeView != null)
m_TreeView.AssetSelectionToolBarGUI();
if (doSelectionButton(guiContentReadme))
{
Heureka_PackageDataManagerEditor.SelectReadme();
if (AH_EditorData.Instance.Documentation != null)
AssetDatabase.OpenAsset(AH_EditorData.Instance.Documentation);
}
EditorGUILayout.EndHorizontal();
}
private void doSearchBar(Rect rect)
{
if (m_TreeView != null)
m_TreeView.searchString = m_SearchField.OnGUI(rect, m_TreeView.searchString);
}
private void doTreeView(Rect rect)
{
if (m_TreeView != null)
m_TreeView.OnGUI(rect);
}
private void doBottomToolBar(Rect rect)
{
if (m_TreeView == null)
return;
GUILayout.BeginArea(rect);
using (new EditorGUILayout.HorizontalScope())
{
GUIStyle style = "miniButton";
if (GUILayout.Button("Expand All", style))
{
m_TreeView.ExpandAll();
}
if (GUILayout.Button("Collapse All", style))
{
m_TreeView.CollapseAll();
}
GUILayout.Label("Build: " + buildInfoManager.GetSelectedBuildDate() + " (" + buildInfoManager.GetSelectedBuildTarget() + ")");
GUILayout.FlexibleSpace();
GUILayout.Label(buildInfoManager.TreeView != null ? AssetDatabase.GetAssetPath(buildInfoManager.TreeView) : string.Empty);
GUILayout.FlexibleSpace();
if (((AH_MultiColumnHeader)m_TreeView.multiColumnHeader).mode == AH_MultiColumnHeader.Mode.SortedList || !string.IsNullOrEmpty(m_TreeView.searchString))
{
if (GUILayout.Button("Return to Treeview", style))
{
m_TreeView.ShowTreeMode();
}
}
GUIContent exportContent = new GUIContent("Export list", "Export all the assets in the list above to a json file");
if (GUILayout.Button(exportContent, style))
{
AH_ElementList.DumpCurrentListToFile(m_TreeView);
}
}
GUILayout.EndArea();
}
private bool doSelectionButton(GUIContent content)
{
GUIContent btnContent = new GUIContent(content);
if (AH_SettingsManager.Instance.HideButtonText)
btnContent.text = null;
return GUILayout.Button(btnContent, GUILayout.MaxHeight(AH_SettingsManager.Instance.HideButtonText ? ButtonMaxHeight * 2f : ButtonMaxHeight));
}
private void OnIgnoreListUpdatedEvent()
{
buildInfoManager.ProjectDirty = true;
if (AH_SettingsManager.Instance.AutoOpenLog)
RefreshBuildLog();
}
private void RefreshBuildLog()
{
if (buildInfoManager != null && buildInfoManager.HasSelection)
{
m_Initialized = false;
buildInfoManager.RefreshBuildInfo();
}
}
private void openBuildInfoSelector()
{
string fileSelected = EditorUtility.OpenFilePanel("", AH_SerializationHelper.GetBuildInfoFolder(), AH_SerializationHelper.BuildInfoExtension);
if (!string.IsNullOrEmpty(fileSelected))
{
m_Initialized = false;
buildInfoManager.SelectBuildInfo(fileSelected);
}
}
Rect toolbarRect
{
get { return new Rect(UiStartPos.x, UiStartPos.y + (AH_SettingsManager.Instance.HideButtonText ? 20 : 0), position.width - (UiStartPos.x * 2), 20f); }
}
Rect multiColumnTreeViewRect
{
get { return new Rect(UiStartPos.x, UiStartPos.y + 20 + (AH_SettingsManager.Instance.HideButtonText ? 20 : 0), position.width - (UiStartPos.x * 2), position.height - 90 - (AH_SettingsManager.Instance.HideButtonText ? 20 : 0)); }
}
Rect assetInfoRect
{
get { return new Rect(UiStartPos.x, position.height - 66f, position.width - (UiStartPos.x * 2), 16f); }
}
Rect bottomToolbarRect
{
get { return new Rect(UiStartPos.x, position.height - 18, position.width - (UiStartPos.x * 2), 16f); }
}
public Vector2 UiStartPos
{
get
{
return uiStartPos;
}
set
{
uiStartPos = value;
}
}
private void OnDestroy()
{
AH_TreeViewSelectionInfo.OnAssetDeleted -= m_window.OnAssetDeleted;
#if UNITY_2018_1_OR_NEWER
EditorApplication.projectChanged -= m_window.OnProjectChanged;
#elif UNITY_5_6_OR_NEWER
EditorApplication.projectWindowChanged -= m_window.OnProjectChanged;
#endif
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: c15a7cb4e3c84c141a3abe55fd9c73fe
timeCreated: 1529997477
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
namespace HeurekaGames.AssetHunterPRO
{
[Serializable]
public class AH_WrapperList
{
public List<string> list = new List<string>();
public AH_WrapperList(List<string> value)
{
this.list = value;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 98f18890366e7f746afc4ba3fdb9b0a4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 5b0f914bfa5d220479ce3949edf63b61
folderAsset: yes
timeCreated: 1544690523
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,171 @@
#if UNITY_EDITOR
using System;
using System.Reflection;
using UnityEditor;
using UnityEngine;
//Credit https://gist.github.com/Thundernerd/5085ec29819b2960f5ff2ee32ad57cbb
namespace HeurekaGames
{
public static class Docker
{
#region Reflection Types
private class _EditorWindow
{
private EditorWindow instance;
private Type type;
public _EditorWindow(EditorWindow instance)
{
this.instance = instance;
type = instance.GetType();
}
public object m_Parent
{
get
{
var field = type.GetField("m_Parent", BindingFlags.Instance | BindingFlags.NonPublic);
return field.GetValue(instance);
}
}
}
private class _DockArea
{
private object instance;
private Type type;
public _DockArea(object instance)
{
this.instance = instance;
type = instance.GetType();
}
public object window
{
get
{
var property = type.GetProperty("window", BindingFlags.Instance | BindingFlags.Public);
return property.GetValue(instance, null);
}
}
public object s_OriginalDragSource
{
set
{
var field = type.GetField("s_OriginalDragSource", BindingFlags.Static | BindingFlags.NonPublic);
field.SetValue(null, value);
}
}
}
private class _ContainerWindow
{
private object instance;
private Type type;
public _ContainerWindow(object instance)
{
this.instance = instance;
type = instance.GetType();
}
public object rootSplitView
{
get
{
var property = type.GetProperty("rootSplitView", BindingFlags.Instance | BindingFlags.Public);
return property.GetValue(instance, null);
}
}
}
private class _SplitView
{
private object instance;
private Type type;
public _SplitView(object instance)
{
this.instance = instance;
type = instance.GetType();
}
public object DragOver(EditorWindow child, Vector2 screenPoint)
{
var method = type.GetMethod("DragOver", BindingFlags.Instance | BindingFlags.Public);
return method.Invoke(instance, new object[] { child, screenPoint });
}
public void PerformDrop(EditorWindow child, object dropInfo, Vector2 screenPoint)
{
var method = type.GetMethod("PerformDrop", BindingFlags.Instance | BindingFlags.Public);
try
{
if (dropInfo != null)
method.Invoke(instance, new object[] { child, dropInfo, screenPoint });
}
catch (Exception)
{
Debug.Log($"AHP Unable to autodock {child.GetType().Name} because AHP mainwindow is already docked");
}
}
}
#endregion
public enum DockPosition
{
Left,
Top,
Right,
Bottom
}
/// <summary>
/// Docks the second window to the first window at the given position
/// </summary>
public static void Dock(this EditorWindow wnd, EditorWindow other, DockPosition position)
{
var mousePosition = GetFakeMousePosition(wnd, position);
var parent = new _EditorWindow(wnd);
var child = new _EditorWindow(other);
var dockArea = new _DockArea(parent.m_Parent);
var containerWindow = new _ContainerWindow(dockArea.window);
var splitView = new _SplitView(containerWindow.rootSplitView);
var dropInfo = splitView.DragOver(other, mousePosition);
dockArea.s_OriginalDragSource = child.m_Parent;
splitView.PerformDrop(other, dropInfo, mousePosition);
}
private static Vector2 GetFakeMousePosition(EditorWindow wnd, DockPosition position)
{
Vector2 mousePosition = Vector2.zero;
// The 20 is required to make the docking work.
// Smaller values might not work when faking the mouse position.
switch (position)
{
case DockPosition.Left:
mousePosition = new Vector2(20, wnd.position.size.y / 2);
break;
case DockPosition.Top:
mousePosition = new Vector2(wnd.position.size.x / 2, 20);
break;
case DockPosition.Right:
mousePosition = new Vector2(wnd.position.size.x - 20, wnd.position.size.y / 2);
break;
case DockPosition.Bottom:
mousePosition = new Vector2(wnd.position.size.x / 2, wnd.position.size.y - 20);
break;
}
return GUIUtility.GUIToScreenPoint(mousePosition);
}
}
}
#endif

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 0c1c6abb88a39844090db7cb9f31b438
timeCreated: 1544690009
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 7e8494b23d4cc3e42a812c1bc45860e7
folderAsset: yes
timeCreated: 1472481428
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,176 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using System.IO;
namespace HeurekaGames.AssetHunterPRO.BaseTreeviewImpl.AssetTreeView
{
[Serializable]
public class AH_BuildInfoTreeView : ScriptableObject, ISerializationCallbackReceiver
{
//Serialization helper lists
private List<string> serializationHelperListIconTypes;
private List<Texture> serializationHelperListIconTextures;
[SerializeField]
List<AH_TreeviewElement> m_TreeElements;
internal List<AH_TreeviewElement> treeElements
{
get { return m_TreeElements; }
set { m_TreeElements = value; }
}
public void OnEnable()
{
hideFlags = HideFlags.HideAndDontSave;
}
public bool PopulateTreeView(AH_SerializedBuildInfo chosenBuildInfo)
{
//Todo, maybe not get ALL assets, but just the assets in the project folder (i.e. -meta etc)?
treeElements = new List<AH_TreeviewElement>();
int depth = -1;
int id = 0;
var root = new AH_TreeviewElement("Root", depth, id, "", "", new List<string>(), false);
treeElements.Add(root);
depth++;
id++;
//This is done because I cant find all assets in toplevel folders through the Unity API (Remove whem the API allows it)
int folderCount = System.IO.Directory.GetDirectories(Application.dataPath,"*",SearchOption.AllDirectories).Count();
int foldersProcessed = 0;
bool populatedSuccesfully = AddFilesRecursively(Application.dataPath, chosenBuildInfo, depth, ref id, folderCount, ref foldersProcessed);
//Cleanup garbage
AssetDatabase.Refresh();
GC.Collect();
//Create tree
if (populatedSuccesfully)
TreeElementUtility.ListToTree(treeElements);
EditorUtility.ClearProgressBar();
return populatedSuccesfully;
}
private bool AddFilesRecursively(string absPath, AH_SerializedBuildInfo chosenBuildInfo, int treeViewDepth, ref int treeViewID, int folderCount, ref int foldersProcessed)
{
string relativePath;
string folderID;
AH_Utils.GetRelativePathAndAssetID(absPath, out relativePath, out folderID);
//For some reason streamingassets folders are generated by unity when building, only to be deleted immediately after. Need to take that under consideration here.
if (!AssetDatabase.IsValidFolder(relativePath))
{
return false;
}
//Increment folder process count
foldersProcessed++;
var progress = (float)((float)foldersProcessed / (float)folderCount);
EditorUtility.DisplayProgressBar($"Analyzing project ({foldersProcessed}/{folderCount}", relativePath, progress); //Todo make cancellable
//Check if this folder has been Ignored
if (AH_SettingsManager.Instance.HasIgnoredFolder(relativePath, folderID))
return false;
//Add folder
System.IO.DirectoryInfo dirInfo = new System.IO.DirectoryInfo(absPath);
string dirInfoName = dirInfo.Name;
//Increment ID
treeViewID++;
//TODO creating new treeviewelements loads asset from memory...DONT DO THAT!! Get filesize info somewhere else
AH_TreeviewElement threeViewFolder = new AH_TreeviewElement(dirInfoName, treeViewDepth, treeViewID, ((treeViewDepth != -1) ? relativePath : ""), "", null, false);
treeElements.Add(threeViewFolder);
//Increment depth
treeViewDepth++;
//Track if this folder has valid children
bool hasValidChildren = false;
foreach (var assetPath in System.IO.Directory.GetFiles(absPath).Where(val => Path.GetExtension(val) != ".meta"))// !val.EndsWith(".meta")))
{
string relativepath;
string assetID;
AH_Utils.GetRelativePathAndAssetID(assetPath, out relativepath, out assetID);
//If this is not an unity asset
if (string.IsNullOrEmpty(assetID))
continue;
//Has this file been Ignored?
if (AH_SettingsManager.Instance.HasIgnoredAsset(relativepath, assetID))
continue;
AH_SerializableAssetInfo usedAssetInfo = chosenBuildInfo.GetItemInfo(assetID);
bool isAssetUsed = (usedAssetInfo != null);
//TODO CONTINUE LOOP AND ADDING OF ASSETS
treeViewID++;
AH_TreeviewElement treeViewElement = new AH_TreeviewElement(assetPath, treeViewDepth, treeViewID, relativepath, assetID, ((isAssetUsed) ? usedAssetInfo.Refs : null), isAssetUsed);
treeElements.Add(treeViewElement);
hasValidChildren = true;
}
foreach (var dir in System.IO.Directory.GetDirectories(absPath))
{
if (AddFilesRecursively(dir, chosenBuildInfo, treeViewDepth, ref treeViewID, folderCount, ref foldersProcessed))
hasValidChildren = true;
}
if (!hasValidChildren && (treeViewDepth != -1))
{
treeElements.Remove(threeViewFolder);
//Decrement ID
treeViewID--;
//Decrement depth
treeViewDepth--;
}
//Return true if folder added succesfully
return hasValidChildren;
}
internal bool HasUnused()
{
bool hasUnused = m_TreeElements.Any(val => !val.UsedInBuild && !val.IsFolder && val.depth != -1);
return hasUnused;
}
private string[] getAssetsOfType(Type type)
{
return AssetDatabase.FindAssets("t:" + type.Name);
}
#region Serialization callbacks
//Store serializable string so we can retrieve type after serialization
public void OnBeforeSerialize()
{
serializationHelperListIconTypes = AH_TreeviewElement.GetStoredIconTypes();
serializationHelperListIconTextures = AH_TreeviewElement.GetStoredIconTextures();
}
public void OnAfterDeserialize()
{
AH_TreeviewElement.UpdateIconDictAfterSerialization(serializationHelperListIconTypes, serializationHelperListIconTextures);
serializationHelperListIconTypes = null;
serializationHelperListIconTextures = null;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a118fee61d7a50043b17a704f0909285
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,77 @@

using System;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace HeurekaGames.AssetHunterPRO.BaseTreeviewImpl.AssetTreeView
{
internal class AH_MultiColumnHeader : MultiColumnHeader
{
AssetShowMode m_showMode;
public enum AssetShowMode
{
Unused,
Used,
All
}
Mode m_Mode;
public enum Mode
{
//LargeHeader,
Treeview,
SortedList
}
public AH_MultiColumnHeader(MultiColumnHeaderState state) : base(state)
{
mode = Mode.Treeview;
}
public Mode mode
{
get
{
return m_Mode;
}
set
{
m_Mode = value;
switch (m_Mode)
{
case Mode.Treeview:
canSort = true;
height = DefaultGUI.minimumHeight;
break;
case Mode.SortedList:
canSort = true;
height = DefaultGUI.defaultHeight;
break;
}
}
}
public AssetShowMode ShowMode
{
get
{
return m_showMode;
}
set
{
m_showMode = value;
}
}
protected override void ColumnHeaderClicked(MultiColumnHeaderState.Column column, int columnIndex)
{
if (mode == Mode.Treeview)
{
mode = Mode.SortedList;
}
base.ColumnHeaderClicked(column, columnIndex);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: efad43b96d78a3940a3d101b069a04ff
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,599 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using UnityEngine.Assertions;
namespace HeurekaGames.AssetHunterPRO.BaseTreeviewImpl.AssetTreeView
{
class AH_TreeViewWithTreeModel : TreeViewWithTreeModel<AH_TreeviewElement>
{
const float kRowHeights = 20f;
const float kToggleWidth = 18f;
AH_TreeViewSelectionInfo treeviewSelectionInfo = new AH_TreeViewSelectionInfo();
GUIContent[] guiContents_toolbarShowSelection = new GUIContent[3]
{
new GUIContent(AH_MultiColumnHeader.AssetShowMode.Unused.ToString(),"Show only assets that was NOT included in build"),
new GUIContent(AH_MultiColumnHeader.AssetShowMode.Used.ToString(),"Show only assets that WAS included in build"),
new GUIContent(AH_MultiColumnHeader.AssetShowMode.All.ToString(),"Show all assets in project")
};
// All columns
enum MyColumns
{
//Dummy,
Icon,
Name,
AssetSize,
FileSize,
UsedInBuild,
LevelUsage
}
public enum SortOption
{
AssetType,
Name,
AssetSize,
FileSize,
Used,
LevelRefs,
}
// Sort options per column
SortOption[] m_SortOptions =
{
//SortOption.Value1,
SortOption.AssetType,
SortOption.Name,
SortOption.AssetSize,
SortOption.FileSize,
SortOption.Used,
SortOption.LevelRefs
};
public static void TreeToList(TreeViewItem root, IList<TreeViewItem> result)
{
if (root == null)
throw new NullReferenceException("root");
if (result == null)
throw new NullReferenceException("result");
result.Clear();
if (root.children == null)
return;
Stack<TreeViewItem> stack = new Stack<TreeViewItem>();
for (int i = root.children.Count - 1; i >= 0; i--)
stack.Push(root.children[i]);
while (stack.Count > 0)
{
TreeViewItem current = stack.Pop();
result.Add(current);
if (current.hasChildren && current.children[0] != null)
{
for (int i = current.children.Count - 1; i >= 0; i--)
{
stack.Push(current.children[i]);
}
}
}
}
public AH_TreeViewWithTreeModel(TreeViewState state, MultiColumnHeader multicolumnHeader, TreeModel<AH_TreeviewElement> model) : base(state, multicolumnHeader, model)
{
Assert.AreEqual(m_SortOptions.Length, Enum.GetValues(typeof(MyColumns)).Length, "Ensure number of sort options are in sync with number of MyColumns enum values");
// Custom setup
rowHeight = kRowHeights;
columnIndexForTreeFoldouts = 1;
showAlternatingRowBackgrounds = true;
showBorder = true;
customFoldoutYOffset = (kRowHeights - EditorGUIUtility.singleLineHeight) * 0.5f; // center foldout in the row since we also center content. See RowGUI
extraSpaceBeforeIconAndLabel = kToggleWidth;
multicolumnHeader.sortingChanged += OnSortingChanged;
//Make sure we show the correct columns
updateColumns();
//IF we want to start expanded one level
if (model.root.hasChildren)
SetExpanded(model.root.children[0].id, true);
Reload();
}
protected override bool RequiresSorting()
{
//Show as list if base requires sorting OR if we chose sortedList
return base.RequiresSorting() || ((AH_MultiColumnHeader)multiColumnHeader).mode == AH_MultiColumnHeader.Mode.SortedList;
}
protected override void AddChildrenRecursive(AH_TreeviewElement parent, int depth, IList<TreeViewItem> newRows)
{
AH_MultiColumnHeader.AssetShowMode showMode = ((AH_MultiColumnHeader)multiColumnHeader).ShowMode;
foreach (AH_TreeviewElement child in parent.children)
{
bool isFolder = child.hasChildren;
bool isFolderWithValidChildren = isFolder && (showMode == AH_MultiColumnHeader.AssetShowMode.All || child.HasChildrenThatMatchesState(showMode));
//TODO EXTRACT THIS TO BE ABLE TO Ignore TYPES ETC OR MAYBE THAT SHOULD BE DONE WHEN POPULATING THE FIRST TIME...maybe better
//If its a folder or an asset that matches showmode
if (isFolderWithValidChildren || child.AssetMatchesState(showMode))
{
//Add new row
var item = new TreeViewItem<AH_TreeviewElement>(child.id, depth, child.Name, child);
newRows.Add(item);
if (isFolder)
{
if (IsExpanded(child.id))
{
AddChildrenRecursive(child, depth + 1, newRows);
}
else
{
item.children = CreateChildListForCollapsedParent();
}
}
}
}
}
public override void OnGUI(Rect rect)
{
Rect treeViewRect = rect;
//If we have selection, we want to make room in the rect to show that
if (treeviewSelectionInfo.HasSelection)
treeViewRect.height -= AH_TreeViewSelectionInfo.Height;
base.OnGUI(treeViewRect);
Rect SelectionRect = new Rect(treeViewRect.x, treeViewRect.yMax, treeViewRect.width, AH_TreeViewSelectionInfo.Height);
if (treeviewSelectionInfo.HasSelection/* && ((AH_MultiColumnHeader)multiColumnHeader).ShowMode == AH_MultiColumnHeader.AssetShowMode.Unused*/)
treeviewSelectionInfo.OnGUISelectionInfo(SelectionRect);
}
// Note we We only build the visible rows, only the backend has the full tree information.
// The treeview only creates info for the row list.
protected override IList<TreeViewItem> BuildRows(TreeViewItem root)
{
var rows = base.BuildRows(root);
SortIfNeeded(root, rows);
return rows;
}
void GetAllChildren(TreeViewItem root, ref List<TreeViewItem<AH_TreeviewElement>> children)
{
if (root != null && root.hasChildren && root.children != null)
{
foreach (var child in root.children)
{
children.AddRange(root.children.Cast<TreeViewItem<AH_TreeviewElement>>());
GetAllChildren(child, ref children);
}
}
}
void OnSortingChanged(MultiColumnHeader multiColumnHeader)
{
ModelChanged();
SortIfNeeded(rootItem, GetRows());
}
void SortIfNeeded(TreeViewItem root, IList<TreeViewItem> rows)
{
if (rows.Count <= 1)
return;
if (multiColumnHeader.sortedColumnIndex == -1)
{
return; // No column to sort for (just use the order the data are in)
}
SortByMultipleColumns();
TreeToList(root, rows);
Repaint();
}
private void deselectItems()
{
treeviewSelectionInfo.Reset();
IList<int> emptyList = new List<int>();
this.SetSelection(emptyList);
}
void SortByMultipleColumns()
{
var sortedColumns = multiColumnHeader.state.sortedColumns;
if (sortedColumns.Length == 0)
return;
var myTypes = rootItem.children.Cast<TreeViewItem<AH_TreeviewElement>>();
//Try to visualize to user that we are getting asset sizes
if (AH_SettingsManager.Instance.EstimateAssetSize)
{
int counter = 0;
int typeCount = myTypes.Count();
foreach (var item in myTypes)
{
counter++;
if (item == null)
continue;
EditorUtility.DisplayProgressBar($"Getting asset sizes", $"{ counter} / {typeCount} : {item.data.Name} : {item.data.AssetSizeStringRepresentation}", (float)counter / (float)typeCount);
}
EditorUtility.ClearProgressBar();
}
var orderedQuery = InitialOrder(myTypes, sortedColumns);
for (int i = 1; i < sortedColumns.Length; i++)
{
SortOption sortOption = m_SortOptions[sortedColumns[i]];
bool ascending = multiColumnHeader.IsSortedAscending(sortedColumns[i]);
switch (sortOption)
{
case SortOption.Name:
orderedQuery = orderedQuery.ThenBy(l => l.data.Name, ascending);
break;
case SortOption.AssetSize:
orderedQuery = orderedQuery.ThenBy(l => l.data.AssetSize, ascending);
break;
case SortOption.FileSize:
orderedQuery = orderedQuery.ThenBy(l => l.data.FileSize, ascending);
break;
case SortOption.AssetType:
//Make sure that type is not null, if it is null then its a folder and we sort by path
orderedQuery = orderedQuery.ThenBy(l => ((l.data.AssetType != null) ? l.data.AssetType.ToString() : l.data.RelativePath), ascending);//.ThenBy(x => x.Pet != null ? x.Pet.Name : "");
break;
case SortOption.LevelRefs:
orderedQuery = orderedQuery.ThenBy(l => l.data.SceneRefCount, ascending);
break;
case SortOption.Used:
orderedQuery = orderedQuery.ThenBy(l => l.data.UsedInBuild, ascending);
break;
default:
Assert.IsTrue(false, "Unhandled enum");
break;
}
}
rootItem.children = orderedQuery.Cast<TreeViewItem>().ToList();
}
internal long GetCombinedUnusedSize()
{
return treeModel.root.GetFileSizeRecursively(AH_MultiColumnHeader.AssetShowMode.Unused);
}
internal void DrawDeleteAllButton(GUIContent content, GUIStyle style, params GUILayoutOption[] layout)
{
treeviewSelectionInfo.DrawDeleteFolderButton(content, treeModel.root, style, content.tooltip, "This will delete ALL assets not used in last build. Its recommended to review the unused asset list and to backup before proceding", layout);
}
internal void ShowTreeMode()
{
searchString = "";
multiColumnHeader.sortedColumnIndex = -1;
((AH_MultiColumnHeader)multiColumnHeader).mode = AH_MultiColumnHeader.Mode.Treeview;
//Make sure we dont cause an endless loop
ModelChanged();
}
//Check if it matches state and searchstring
protected override bool IsValidElement(TreeElement element, string searchString)
{
AH_TreeviewElement ahElement = (element as AH_TreeviewElement);
AH_MultiColumnHeader.AssetShowMode hShowMode = ((AH_MultiColumnHeader)multiColumnHeader).ShowMode;
bool validAsset = !ahElement.IsFolder && ahElement.AssetMatchesState(hShowMode);
//Only show folders if we are in treeview mode and the folder has children that matches state
bool validFolder = searchString == "" && (ahElement.IsFolder && (((AH_MultiColumnHeader)multiColumnHeader).mode == AH_MultiColumnHeader.Mode.Treeview) && ahElement.HasChildrenThatMatchesState(hShowMode));
return (validAsset || validFolder) && base.IsValidElement(element, searchString);
}
internal void AssetSelectionToolBarGUI()
{
EditorGUI.BeginChangeCheck();
((AH_MultiColumnHeader)multiColumnHeader).ShowMode = (AH_MultiColumnHeader.AssetShowMode)GUILayout.Toolbar((int)((AH_MultiColumnHeader)multiColumnHeader).ShowMode, guiContents_toolbarShowSelection/*, GUILayout.MaxWidth(AH_SettingsManager.Instance.ShowMenuText ? 290 : 128)*/);
if (EditorGUI.EndChangeCheck())
{
showModeChanged();
}
}
private void showModeChanged()
{
updateColumns();
deselectItems();
ModelChanged();
}
private void updateColumns()
{
#region Add or remove the "asset size" column as required
bool currentlyShowsAssetSize = (multiColumnHeader.state.visibleColumns.ToList().Contains((int)MyColumns.AssetSize));
bool couldHaveAssetSize = ((AH_MultiColumnHeader)multiColumnHeader).ShowMode != AH_MultiColumnHeader.AssetShowMode.Unused;
if (AH_SettingsManager.Instance.EstimateAssetSize)
{
if (!currentlyShowsAssetSize && couldHaveAssetSize)
{
var tmpList = multiColumnHeader.state.visibleColumns.ToList();
tmpList.Add((int)MyColumns.AssetSize);
multiColumnHeader.state.visibleColumns = tmpList.ToArray();
Array.Sort(multiColumnHeader.state.visibleColumns);
}
}
else
{
if (currentlyShowsAssetSize)
{
var tmpList = multiColumnHeader.state.visibleColumns.ToList();
tmpList.Remove((int)MyColumns.AssetSize);
multiColumnHeader.state.visibleColumns = tmpList.ToArray();
}
}
#endregion
}
IOrderedEnumerable<TreeViewItem<AH_TreeviewElement>> InitialOrder(IEnumerable<TreeViewItem<AH_TreeviewElement>> myTypes, int[] history)
{
SortOption sortOption = m_SortOptions[history[0]];
bool ascending = multiColumnHeader.IsSortedAscending(history[0]);
switch (sortOption)
{
case SortOption.Name:
return myTypes.Order(l => l.data.Name, ascending);
case SortOption.AssetSize:
return myTypes.Order(l => l.data.AssetSize, ascending);
case SortOption.FileSize:
return myTypes.Order(l => l.data.FileSize, ascending);
case SortOption.AssetType:
return myTypes.Order(l => l.data.AssetTypeSerialized, ascending);
case SortOption.LevelRefs:
return myTypes.Order(l => l.data.ScenesReferencingAsset?.Count, ascending);
case SortOption.Used:
return myTypes.Order(l => l.data.UsedInBuild, ascending);
default:
Assert.IsTrue(false, "Unhandled enum");
break;
}
// default
return myTypes.Order(l => l.data.Name, ascending);
}
protected override void RowGUI(RowGUIArgs args)
{
var item = (TreeViewItem<AH_TreeviewElement>)args.item;
for (int i = 0; i < args.GetNumVisibleColumns(); ++i)
{
CellGUI(args.GetCellRect(i), item, (MyColumns)args.GetColumn(i), ref args);
}
}
void CellGUI(Rect cellRect, TreeViewItem<AH_TreeviewElement> item, MyColumns column, ref RowGUIArgs args)
{
// Center cell rect vertically (makes it easier to place controls, icons etc in the cells)
CenterRectUsingSingleLineHeight(ref cellRect);
AH_TreeviewElement element = (AH_TreeviewElement)item.data;
switch (column)
{
case MyColumns.Icon:
{
if (item.data.AssetType != null)
GUI.DrawTexture(cellRect, AH_TreeviewElement.GetIcon(item.data.AssetType), ScaleMode.ScaleToFit);
}
break;
case MyColumns.Name:
{
Rect nameRect = cellRect;
nameRect.x += GetContentIndent(item);
DefaultGUI.Label(nameRect, item.data.m_Name, args.selected, args.focused);
}
break;
case MyColumns.AssetSize:
case MyColumns.FileSize:
{
string value = "";
if (column == MyColumns.AssetSize && element.AssetSize > 0)
value = element.AssetSizeStringRepresentation;
if (column == MyColumns.FileSize && element.FileSize > 0)
value = element.FileSizeStringRepresentation;
if (element.IsFolder && column == MyColumns.FileSize/*&& !IsExpanded(element.id)*/)
{
value = "{"+AH_Utils.BytesToString(element.GetFileSizeRecursively(((AH_MultiColumnHeader)multiColumnHeader).ShowMode))+"}";
DefaultGUI.Label(cellRect, value, args.selected, args.focused);
}
else
DefaultGUI.LabelRightAligned(cellRect, value, args.selected, args.focused);
}
break;
case MyColumns.UsedInBuild:
{
if (item.data.UsedInBuild)
DefaultGUI.LabelRightAligned(cellRect, "\u2713", args.selected, args.focused);
}
break;
case MyColumns.LevelUsage:
{
if (item.data.UsedInBuild && item.data.ScenesReferencingAsset != null)
{
if (item.data.ScenesReferencingAsset.Count > 0)
{
string cellString = String.Format("Usage: {0}", item.data.ScenesReferencingAsset.Count.ToString());
if (args.selected && args.focused)
{
if (GUI.Button(cellRect, cellString))
{
UnityEngine.Object[] sceneAssets = new UnityEngine.Object[item.data.ScenesReferencingAsset.Count];
string message = "";
string path = "";
for (int i = 0; i < item.data.ScenesReferencingAsset.Count; i++)
{
path = AssetDatabase.GUIDToAssetPath(item.data.ScenesReferencingAsset[i]);
message += (path + Environment.NewLine);
sceneAssets[i] = AssetDatabase.LoadMainAssetAtPath(path);
}
Selection.objects = sceneAssets;
EditorUtility.DisplayDialog("Scenes referencing " + path, message, "OK");
}
}
else
DefaultGUI.LabelRightAligned(cellRect, cellString, args.selected, args.focused);
}
}
}
break;
}
}
protected override void SelectionChanged(IList<int> selectedIds)
{
base.SelectionChanged(selectedIds);
//Only set selection if we are in "Unused Mode"
//if (((AH_MultiColumnHeader)multiColumnHeader).ShowMode == AH_MultiColumnHeader.AssetShowMode.Unused)
treeviewSelectionInfo.SetSelection(this, selectedIds);
}
protected override bool CanMultiSelect(TreeViewItem item)
{
return true;
}
public static MultiColumnHeaderState CreateDefaultMultiColumnHeaderState(float treeViewWidth)
{
var columns = new[]
{
new MultiColumnHeaderState.Column
{
headerContent = new GUIContent(EditorGUIUtility.FindTexture("FilterByType"), "Type of asset"),
contextMenuText = "Type",
headerTextAlignment = TextAlignment.Center,
sortedAscending = true,
sortingArrowAlignment = TextAlignment.Right,
width = 30,
minWidth = 30,
maxWidth = 30,
autoResize = false,
allowToggleVisibility = true
},
new MultiColumnHeaderState.Column
{
headerContent = new GUIContent("Name"),
headerTextAlignment = TextAlignment.Left,
sortedAscending = true,
sortingArrowAlignment = TextAlignment.Center,
width = 280,
minWidth = 175,
autoResize = true,
allowToggleVisibility = false
},
new MultiColumnHeaderState.Column
{
headerContent = new GUIContent("Asset size", "Size of the asset in project"),
headerTextAlignment = TextAlignment.Left,
sortedAscending = true,
sortingArrowAlignment = TextAlignment.Center,
width = 80,
minWidth = 80,
maxWidth = 130,
autoResize = true
},
new MultiColumnHeaderState.Column
{
headerContent = new GUIContent("Disc size", "Size of the file on disc"),
headerTextAlignment = TextAlignment.Left,
sortedAscending = true,
sortingArrowAlignment = TextAlignment.Center,
width = 80,
minWidth = 80,
maxWidth = 130,
autoResize = true,
allowToggleVisibility = true
},
new MultiColumnHeaderState.Column
{
headerContent = new GUIContent("Used", "If this asset is used in build"),
headerTextAlignment = TextAlignment.Left,
sortedAscending = true,
sortingArrowAlignment = TextAlignment.Center,
width = 45,
minWidth = 30,
maxWidth = 45,
autoResize = true
},
new MultiColumnHeaderState.Column
{
headerContent = new GUIContent("Level refs", "How many scenes are using this asset"),
headerTextAlignment = TextAlignment.Left,
sortedAscending = true,
sortingArrowAlignment = TextAlignment.Center,
width = 70,
minWidth = 70,
maxWidth = 100,
autoResize = true
}
};
Assert.AreEqual(columns.Length, Enum.GetValues(typeof(MyColumns)).Length, "Number of columns should match number of enum values: You probably forgot to update one of them.");
var state = new MultiColumnHeaderState(columns);
return state;
}
}
static class MyExtensionMethods
{
public static IOrderedEnumerable<T> Order<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector, bool ascending)
{
if (ascending)
{
return source.OrderBy(selector);
}
else
{
return source.OrderByDescending(selector);
}
}
public static IOrderedEnumerable<T> ThenBy<T, TKey>(this IOrderedEnumerable<T> source, Func<T, TKey> selector, bool ascending)
{
if (ascending)
{
return source.ThenBy(selector);
}
else
{
return source.ThenByDescending(selector);
}
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: e00d20ba7c1f4d446a34ea24d8b82c4e
timeCreated: 1464348051
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,309 @@
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.Profiling;
namespace HeurekaGames.AssetHunterPRO.BaseTreeviewImpl.AssetTreeView
{
[System.Serializable]
public class AH_TreeviewElement : TreeElement, ISerializationCallbackReceiver
{
#region Fields
[SerializeField]
private string absPath;
//[SerializeField]
private string relativePath;
[SerializeField]
private string guid;
//[SerializeField]
private Type assetType;
[SerializeField]
private string assetTypeSerialized;
private long assetSize;
//private string assestSizeStringRepresentation;
//[SerializeField]
private long fileSize;
//[SerializeField]
//private string fileSizeStringRepresentation;
[SerializeField]
private List<string> scenesReferencingAsset;
[SerializeField]
private bool usedInBuild;
[SerializeField]
private bool isFolder;
[SerializeField]
private Dictionary<AH_MultiColumnHeader.AssetShowMode, long> combinedAssetSizeInFolder = new Dictionary<AH_MultiColumnHeader.AssetShowMode, long>();
//Dictionary of asset types and their icons (Cant be serialized)
private static Dictionary<Type, Texture> iconDictionary = new Dictionary<Type, Texture>();
#endregion
#region Properties
public string RelativePath
{
get
{
if(!string.IsNullOrEmpty(relativePath))
return relativePath;
else
return relativePath = UnityEditor.AssetDatabase.GUIDToAssetPath(GUID);
}
}
public string GUID
{
get
{
return guid;
}
}
public Type AssetType
{
get
{
return assetType;
}
}
public string AssetTypeSerialized
{
get
{
if(String.IsNullOrEmpty(assetTypeSerialized) && assetType!=null)
assetTypeSerialized = Heureka_Serializer.SerializeType(assetType);
return assetTypeSerialized;
}
}
public long AssetSize
{
get
{
if(UsedInBuild && assetSize == 0)
{
UnityEngine.Object asset = UnityEditor.AssetDatabase.LoadMainAssetAtPath(RelativePath);
//#if UNITY_2017_1_OR_NEWER
if (asset != null)
return this.assetSize = Profiler.GetRuntimeMemorySizeLong(asset) / 2;
else
return -1;
}
else
return assetSize;
}
}
public string AssetSizeStringRepresentation
{
get
{
return AH_Utils.GetSizeAsString(AssetSize);
}
}
public long FileSize
{
get
{
if (fileSize != 0)
return fileSize;
else
{
var fileInfo = new System.IO.FileInfo(absPath);
if (fileInfo.Exists)
return fileSize = fileInfo != null ? fileInfo.Length : 0;
else
return -1;
}
}
}
public string FileSizeStringRepresentation
{
get
{
return AH_Utils.GetSizeAsString(fileSize);
}
}
public List<string> ScenesReferencingAsset
{
get { return scenesReferencingAsset; }
}
public int SceneRefCount
{
get { return (scenesReferencingAsset != null) ? scenesReferencingAsset.Count : 0; }
}
public bool UsedInBuild
{
get { return usedInBuild; }
}
public bool IsFolder
{
get { return isFolder; }
}
#endregion
public AH_TreeviewElement(string absPath, int depth, int id, string relativepath, string assetID, List<string> scenesReferencing, bool isUsedInBuild) : base(System.IO.Path.GetFileName(absPath), depth, id)
{
this.absPath = absPath;
var assetPath = relativepath;
this.guid = UnityEditor.AssetDatabase.AssetPathToGUID(assetPath);
this.scenesReferencingAsset = scenesReferencing;
this.usedInBuild = isUsedInBuild;
//Return if its a folder
if (isFolder = UnityEditor.AssetDatabase.IsValidFolder(assetPath))
return;
//Return if its not an asset
if (!string.IsNullOrEmpty(this.guid))
{
this.assetType = UnityEditor.AssetDatabase.GetMainAssetTypeAtPath(assetPath);
updateIconDictEntry();
}
}
internal long GetFileSizeRecursively(AH_MultiColumnHeader.AssetShowMode showMode)
{
if (combinedAssetSizeInFolder == null)
combinedAssetSizeInFolder = new Dictionary<AH_MultiColumnHeader.AssetShowMode, long>();
if (combinedAssetSizeInFolder.ContainsKey(showMode))
return combinedAssetSizeInFolder[showMode];
//TODO store these values instead of calculating each and every time?
long combinedChildrenSize = 0;
//Combine the size of all the children
if (hasChildren)
foreach (AH_TreeviewElement item in children)
{
bool validAsset = (showMode == AH_MultiColumnHeader.AssetShowMode.All) ||
((showMode == AH_MultiColumnHeader.AssetShowMode.Unused && !item.usedInBuild) ||
(showMode == AH_MultiColumnHeader.AssetShowMode.Used && item.usedInBuild));
//Loop thropugh folders and assets thats used not in build
if (validAsset || item.isFolder)
combinedChildrenSize += item.GetFileSizeRecursively(showMode);
}
combinedChildrenSize += this.FileSize;
//Cache the value
combinedAssetSizeInFolder.Add(showMode, combinedChildrenSize);
return combinedChildrenSize;
}
#region Serialization callbacks
//TODO Maybe we can store type infos in BuildInfoTreeView instead of on each individual element, might be performance heavy
//Store serializable string so we can retrieve type after serialization
public void OnBeforeSerialize()
{
if (assetType != null)
assetTypeSerialized = Heureka_Serializer.SerializeType(assetType);
}
//Set type from serialized property
public void OnAfterDeserialize()
{
if (!string.IsNullOrEmpty(AssetTypeSerialized))
{
this.assetType = Heureka_Serializer.DeSerializeType(AssetTypeSerialized);
//assetTypeSerialized = "";
}
}
#endregion
internal bool AssetMatchesState(AH_MultiColumnHeader.AssetShowMode showMode)
{
//Test if we want to add this element (We dont want to show "used" when window searches for "unused"
return (AssetType != null && ((showMode == AH_MultiColumnHeader.AssetShowMode.All) || ((showMode == AH_MultiColumnHeader.AssetShowMode.Used && usedInBuild) || (showMode == AH_MultiColumnHeader.AssetShowMode.Unused && !usedInBuild))));
}
internal bool HasChildrenThatMatchesState(AH_MultiColumnHeader.AssetShowMode showMode)
{
if (!hasChildren)
return false;
//Check if a valid child exit somewhere in this branch
foreach (AH_TreeviewElement child in children)
{
if (child.AssetMatchesState(showMode))
return true;
else if (child.HasChildrenThatMatchesState(showMode))
return true;
else
continue;
}
return false;
}
internal List<string> GetUnusedPathsRecursively()
{
List<string> unusedAssetsInFolder = new List<string>();
//Combine the size of all the children
if (hasChildren)
foreach (AH_TreeviewElement item in children)
{
if (item.isFolder)
unusedAssetsInFolder.AddRange(item.GetUnusedPathsRecursively());
//Loop thropugh folders and assets thats used not in build
else if (!item.usedInBuild)
unusedAssetsInFolder.Add(item.RelativePath);
}
return unusedAssetsInFolder;
}
internal static List<string> GetStoredIconTypes()
{
List<string> iconTypesSerialized = new List<string>();
foreach (var item in iconDictionary)
{
iconTypesSerialized.Add(Heureka_Serializer.SerializeType(item.Key));
}
return iconTypesSerialized;
}
internal static List<Texture> GetStoredIconTextures()
{
List<Texture> iconTexturesSerialized = new List<Texture>();
foreach (var item in iconDictionary)
{
iconTexturesSerialized.Add(item.Value);
}
return iconTexturesSerialized;
}
private void updateIconDictEntry()
{
if (assetType != null && !iconDictionary.ContainsKey(assetType))
iconDictionary.Add(assetType, UnityEditor.EditorGUIUtility.ObjectContent(null, assetType).image);
}
internal static void UpdateIconDictAfterSerialization(List<string> serializationHelperListIconTypes, List<Texture> serializationHelperListIconTextures)
{
iconDictionary = new Dictionary<Type, Texture>();
for (int i = 0; i < serializationHelperListIconTypes.Count; i++)
{
Type deserializedType = Heureka_Serializer.DeSerializeType(serializationHelperListIconTypes[i]);
if (deserializedType != null)
iconDictionary.Add(Heureka_Serializer.DeSerializeType(serializationHelperListIconTypes[i]), serializationHelperListIconTextures[i]);
}
}
internal static Texture GetIcon(Type assetType)
{
return iconDictionary[assetType];
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cc7d76ef034ec884fac439e301715bd7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace HeurekaGames.AssetHunterPRO.BaseTreeviewImpl
{
[Serializable]
public class TreeElement
{
[SerializeField] internal int m_ID;
[SerializeField] internal string m_Name;
[SerializeField] internal int m_Depth;
[SerializeField] bool m_Enabled;
[NonSerialized] TreeElement m_Parent;
[NonSerialized] List<TreeElement> m_Children;
public int depth
{
get { return m_Depth; }
set { m_Depth = value; }
}
public TreeElement parent
{
get { return m_Parent; }
set { m_Parent = value; }
}
public List<TreeElement> children
{
get { return m_Children; }
set { m_Children = value; }
}
public bool hasChildren
{
get { return children != null && children.Count > 0; }
}
public string Name
{
get { return m_Name; }
set { m_Name = value; }
}
public int id
{
get { return m_ID; }
set { m_ID = value; }
}
public bool Enabled
{
get
{
return m_Enabled;
}
set
{
m_Enabled = value;
}
}
public TreeElement()
{
}
public TreeElement(string name, int depth, int id)
{
m_Name = name;
m_ID = id;
m_Depth = depth;
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 69be32fe4d27dde489209c5885c1e5dc
timeCreated: 1472024155
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,173 @@
using System;
using System.Collections.Generic;
//using System.Linq;
//using NUnit.Framework;
//using UnityEditor;
namespace HeurekaGames.AssetHunterPRO.BaseTreeviewImpl
{
// TreeElementUtility and TreeElement are useful helper classes for backend tree data structures.
// See tests at the bottom for examples of how to use.
public static class TreeElementUtility
{
public static void TreeToList<T>(T root, IList<T> result) where T : TreeElement
{
if (result == null)
throw new NullReferenceException("The input 'IList<T> result' list is null");
result.Clear();
Stack<T> stack = new Stack<T>();
stack.Push(root);
while (stack.Count > 0)
{
T current = stack.Pop();
result.Add(current);
if (current.children != null && current.children.Count > 0)
{
for (int i = current.children.Count - 1; i >= 0; i--)
{
stack.Push((T)current.children[i]);
}
}
}
}
// Returns the root of the tree parsed from the list (always the first element).
// Important: the first item and is required to have a depth value of -1.
// The rest of the items should have depth >= 0.
public static T ListToTree<T>(IList<T> list) where T : TreeElement
{
// Validate input
ValidateDepthValues(list);
// Clear old states
foreach (var element in list)
{
element.parent = null;
element.children = null;
}
// Set child and parent references using depth info
for (int parentIndex = 0; parentIndex < list.Count; parentIndex++)
{
var parent = list[parentIndex];
bool alreadyHasValidChildren = parent.children != null;
if (alreadyHasValidChildren)
continue;
int parentDepth = parent.depth;
int childCount = 0;
// Count children based depth value, we are looking at children until it's the same depth as this object
for (int i = parentIndex + 1; i < list.Count; i++)
{
if (list[i].depth == parentDepth + 1)
childCount++;
if (list[i].depth <= parentDepth)
break;
}
// Fill child array
List<TreeElement> childList = null;
if (childCount != 0)
{
childList = new List<TreeElement>(childCount); // Allocate once
childCount = 0;
for (int i = parentIndex + 1; i < list.Count; i++)
{
if (list[i].depth == parentDepth + 1)
{
list[i].parent = parent;
childList.Add(list[i]);
childCount++;
}
if (list[i].depth <= parentDepth)
break;
}
}
parent.children = childList;
}
return list[0];
}
// Check state of input list
public static void ValidateDepthValues<T>(IList<T> list) where T : TreeElement
{
if (list.Count == 0)
throw new ArgumentException("list should have items, count is 0, check before calling ValidateDepthValues", "list");
if (list[0].depth != -1)
throw new ArgumentException("list item at index 0 should have a depth of -1 (since this should be the hidden root of the tree). Depth is: " + list[0].depth, "list");
for (int i = 0; i < list.Count - 1; i++)
{
int depth = list[i].depth;
int nextDepth = list[i + 1].depth;
if (nextDepth > depth && nextDepth - depth > 1)
throw new ArgumentException(string.Format("Invalid depth info in input list. Depth cannot increase more than 1 per row. Index {0} has depth {1} while index {2} has depth {3}", i, depth, i + 1, nextDepth));
}
for (int i = 1; i < list.Count; ++i)
if (list[i].depth < 0)
throw new ArgumentException("Invalid depth value for item at index " + i + ". Only the first item (the root) should have depth below 0.");
if (list.Count > 1 && list[1].depth != 0)
throw new ArgumentException("Input list item at index 1 is assumed to have a depth of 0", "list");
}
// For updating depth values below any given element e.g after reparenting elements
public static void UpdateDepthValues<T>(T root) where T : TreeElement
{
if (root == null)
throw new ArgumentNullException("root", "The root is null");
if (!root.hasChildren)
return;
Stack<TreeElement> stack = new Stack<TreeElement>();
stack.Push(root);
while (stack.Count > 0)
{
TreeElement current = stack.Pop();
if (current.children != null)
{
foreach (var child in current.children)
{
child.depth = current.depth + 1;
stack.Push(child);
}
}
}
}
// Returns true if there is an ancestor of child in the elements list
static bool IsChildOf<T>(T child, IList<T> elements) where T : TreeElement
{
while (child != null)
{
child = (T)child.parent;
if (elements.Contains(child))
return true;
}
return false;
}
public static IList<T> FindCommonAncestorsWithinList<T>(IList<T> elements) where T : TreeElement
{
if (elements.Count == 1)
return new List<T>(elements);
List<T> result = new List<T>(elements);
result.RemoveAll(g => IsChildOf(g, elements));
return result;
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: fd65e8f324e17a344a97ddcf5a8d89d2
timeCreated: 1471616285
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,295 @@
//#define runtest
using System;
using System.Collections.Generic;
using System.Linq;
//If this fails, its because you have an assembly definition file in your project that is not set to allow unit tests
//Solution, either allow assembly definition to run test, of comment out the top line
#if runtest
using NUnit.Framework;
#endif
namespace HeurekaGames.AssetHunterPRO.BaseTreeviewImpl
{
// The TreeModel is a utility class working on a list of serializable TreeElements where the order and the depth of each TreeElement define
// the tree structure. Note that the TreeModel itself is not serializable (in Unity we are currently limited to serializing lists/arrays) but the
// input list is.
// The tree representation (parent and children references) are then build internally using TreeElementUtility.ListToTree (using depth
// values of the elements).
// The first element of the input list is required to have depth == -1 (the hiddenroot) and the rest to have
// depth >= 0 (otherwise an exception will be thrown)
public class TreeModel<T> where T : TreeElement
{
IList<T> m_Data;
T m_Root;
int m_MaxID;
public T root { get { return m_Root; } set { m_Root = value; } }
public event Action modelChanged;
public int numberOfDataElements
{
get { return m_Data.Count; }
}
public TreeModel(IList<T> data)
{
SetData(data);
}
public T Find(int id)
{
return m_Data.FirstOrDefault(element => element.id == id);
}
public void SetData(IList<T> data)
{
Init(data);
}
void Init(IList<T> data)
{
if (data == null)
throw new ArgumentNullException("data", "Input data is null. Ensure input is a non-null list.");
m_Data = data;
if (m_Data.Count > 0)
m_Root = TreeElementUtility.ListToTree(data);
m_MaxID = m_Data.Max(e => e.id);
}
public int GenerateUniqueID()
{
return ++m_MaxID;
}
public IList<int> GetAncestors(int id)
{
var parents = new List<int>();
TreeElement T = Find(id);
if (T != null)
{
while (T.parent != null)
{
parents.Add(T.parent.id);
T = T.parent;
}
}
return parents;
}
public IList<int> GetDescendantsThatHaveChildren(int id)
{
T searchFromThis = Find(id);
if (searchFromThis != null)
{
return GetParentsBelowStackBased(searchFromThis);
}
return new List<int>();
}
IList<int> GetParentsBelowStackBased(TreeElement searchFromThis)
{
Stack<TreeElement> stack = new Stack<TreeElement>();
stack.Push(searchFromThis);
var parentsBelow = new List<int>();
while (stack.Count > 0)
{
TreeElement current = stack.Pop();
if (current.hasChildren)
{
parentsBelow.Add(current.id);
foreach (var T in current.children)
{
stack.Push(T);
}
}
}
return parentsBelow;
}
public void RemoveElements(IList<int> elementIDs)
{
IList<T> elements = m_Data.Where(element => elementIDs.Contains(element.id)).ToArray();
RemoveElements(elements);
}
public void RemoveElements(IList<T> elements)
{
foreach (var element in elements)
if (element == m_Root)
throw new ArgumentException("It is not allowed to remove the root element");
var commonAncestors = TreeElementUtility.FindCommonAncestorsWithinList(elements);
foreach (var element in commonAncestors)
{
element.parent.children.Remove(element);
element.parent = null;
}
TreeElementUtility.TreeToList(m_Root, m_Data);
Changed();
}
public void AddElements(IList<T> elements, TreeElement parent, int insertPosition)
{
if (elements == null)
throw new ArgumentNullException("elements", "elements is null");
if (elements.Count == 0)
throw new ArgumentNullException("elements", "elements Count is 0: nothing to add");
if (parent == null)
throw new ArgumentNullException("parent", "parent is null");
if (parent.children == null)
parent.children = new List<TreeElement>();
parent.children.InsertRange(insertPosition, elements.Cast<TreeElement>());
foreach (var element in elements)
{
element.parent = parent;
element.depth = parent.depth + 1;
TreeElementUtility.UpdateDepthValues(element);
}
TreeElementUtility.TreeToList(m_Root, m_Data);
Changed();
}
public void AddRoot(T root)
{
if (root == null)
throw new ArgumentNullException("root", "root is null");
if (m_Data == null)
throw new InvalidOperationException("Internal Error: data list is null");
if (m_Data.Count != 0)
throw new InvalidOperationException("AddRoot is only allowed on empty data list");
root.id = GenerateUniqueID();
root.depth = -1;
m_Data.Add(root);
}
public void AddElement(T element, TreeElement parent, int insertPosition)
{
if (element == null)
throw new ArgumentNullException("element", "element is null");
if (parent == null)
throw new ArgumentNullException("parent", "parent is null");
if (parent.children == null)
parent.children = new List<TreeElement>();
parent.children.Insert(insertPosition, element);
element.parent = parent;
TreeElementUtility.UpdateDepthValues(parent);
TreeElementUtility.TreeToList(m_Root, m_Data);
Changed();
}
public void MoveElements(TreeElement parentElement, int insertionIndex, List<TreeElement> elements)
{
if (insertionIndex < 0)
throw new ArgumentException("Invalid input: insertionIndex is -1, client needs to decide what index elements should be reparented at");
// Invalid reparenting input
if (parentElement == null)
return;
// We are moving items so we adjust the insertion index to accomodate that any items above the insertion index is removed before inserting
if (insertionIndex > 0)
insertionIndex -= parentElement.children.GetRange(0, insertionIndex).Count(elements.Contains);
// Remove draggedItems from their parents
foreach (var draggedItem in elements)
{
draggedItem.parent.children.Remove(draggedItem); // remove from old parent
draggedItem.parent = parentElement; // set new parent
}
if (parentElement.children == null)
parentElement.children = new List<TreeElement>();
// Insert dragged items under new parent
parentElement.children.InsertRange(insertionIndex, elements);
TreeElementUtility.UpdateDepthValues(root);
TreeElementUtility.TreeToList(m_Root, m_Data);
Changed();
}
void Changed()
{
if (modelChanged != null)
modelChanged();
}
}
#if runtest
#region Tests
class TreeModelTests
{
[Test]
public static void TestTreeModelCanAddElements()
{
var root = new TreeElement { Name = "Root", depth = -1 };
var listOfElements = new List<TreeElement>();
listOfElements.Add(root);
var model = new TreeModel<TreeElement>(listOfElements);
model.AddElement(new TreeElement { Name = "Element" }, root, 0);
model.AddElement(new TreeElement { Name = "Element " + root.children.Count }, root, 0);
model.AddElement(new TreeElement { Name = "Element " + root.children.Count }, root, 0);
model.AddElement(new TreeElement { Name = "Sub Element" }, root.children[1], 0);
// Assert order is correct
string[] namesInCorrectOrder = { "Root", "Element 2", "Element 1", "Sub Element", "Element" };
Assert.AreEqual(namesInCorrectOrder.Length, listOfElements.Count, "Result count does not match");
for (int i = 0; i < namesInCorrectOrder.Length; ++i)
Assert.AreEqual(namesInCorrectOrder[i], listOfElements[i].Name);
// Assert depths are valid
TreeElementUtility.ValidateDepthValues(listOfElements);
}
[Test]
public static void TestTreeModelCanRemoveElements()
{
var root = new TreeElement { Name = "Root", depth = -1 };
var listOfElements = new List<TreeElement>();
listOfElements.Add(root);
var model = new TreeModel<TreeElement>(listOfElements);
model.AddElement(new TreeElement { Name = "Element" }, root, 0);
model.AddElement(new TreeElement { Name = "Element " + root.children.Count }, root, 0);
model.AddElement(new TreeElement { Name = "Element " + root.children.Count }, root, 0);
model.AddElement(new TreeElement { Name = "Sub Element" }, root.children[1], 0);
model.RemoveElements(new[] { root.children[1].children[0], root.children[1] });
// Assert order is correct
string[] namesInCorrectOrder = { "Root", "Element 2", "Element" };
Assert.AreEqual(namesInCorrectOrder.Length, listOfElements.Count, "Result count does not match");
for (int i = 0; i < namesInCorrectOrder.Length; ++i)
Assert.AreEqual(namesInCorrectOrder[i], listOfElements[i].Name);
// Assert depths are valid
TreeElementUtility.ValidateDepthValues(listOfElements);
}
}
#endregion
#endif
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 6f9fab1cf2636a6439c644bf08108abb
timeCreated: 1472122507
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,252 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace HeurekaGames.AssetHunterPRO.BaseTreeviewImpl
{
internal class TreeViewItem<T> : TreeViewItem where T : TreeElement
{
//Data storage
public T data { get; set; }
public TreeViewItem(int id, int depth, string displayName, T data) : base(id, depth, displayName)
{
this.data = data;
}
}
internal class TreeViewWithTreeModel<T> : TreeView where T : TreeElement
{
TreeModel<T> m_TreeModel;
protected readonly List<TreeViewItem> m_Rows = new List<TreeViewItem>(100);
public event Action treeChanged;
public TreeModel<T> treeModel { get { return m_TreeModel; } }
public event Action<IList<TreeViewItem>> beforeDroppingDraggedItems;
public TreeViewWithTreeModel(TreeViewState state, TreeModel<T> model) : base(state)
{
Init(model);
}
public TreeViewWithTreeModel(TreeViewState state, MultiColumnHeader multiColumnHeader, TreeModel<T> model)
: base(state, multiColumnHeader)
{
Init(model);
}
void Init(TreeModel<T> model)
{
m_TreeModel = model;
m_TreeModel.modelChanged += ModelChanged;
}
protected void ModelChanged()
{
if (treeChanged != null)
treeChanged();
Reload();
}
protected override TreeViewItem BuildRoot()
{
int depthForHiddenRoot = -1;
return new TreeViewItem<T>(m_TreeModel.root.id, depthForHiddenRoot, m_TreeModel.root.Name, m_TreeModel.root);
}
protected override IList<TreeViewItem> BuildRows(TreeViewItem root)
{
if (m_TreeModel.root == null)
{
Debug.LogError("tree model root is null. did you call SetData()?");
}
m_Rows.Clear();
if (RequiresSorting()) //TODO MAYBE JUST CHECK HERE IS WE ARE SORTING OR NOT, IF SORTING JUST USE THE SEARCH
{
Search(m_TreeModel.root, searchString, m_Rows, IsValidElement);
}
else
{
if (m_TreeModel.root.hasChildren)
AddChildrenRecursive(m_TreeModel.root, 0, m_Rows);
}
// We still need to setup the child parent information for the rows since this
// information is used by the TreeView internal logic (navigation, dragging etc)
SetupParentsAndChildrenFromDepths(root, m_Rows);
return m_Rows;
}
//Override if we need to show lists instead of tree
protected virtual bool RequiresSorting()
{
return !string.IsNullOrEmpty(searchString);
}
protected virtual void AddChildrenRecursive(T parent, int depth, IList<TreeViewItem> newRows)
{
foreach (T child in parent.children)
{
var item = new TreeViewItem<T>(child.id, depth, child.Name, child);
newRows.Add(item);
if (child.hasChildren)
{
if (IsExpanded(child.id))
{
AddChildrenRecursive(child, depth + 1, newRows);
}
else
{
item.children = CreateChildListForCollapsedParent();
}
}
}
}
//Override this to add additional requirements
protected virtual bool IsValidElement(TreeElement element, string searchString)
{
return (string.IsNullOrEmpty(searchString) || element.Name.IndexOf(searchString, StringComparison.OrdinalIgnoreCase) >= 0);
}
protected virtual void Search(T searchFromThis, string search, List<TreeViewItem> result, Func<T, string, bool> IsValidElement)
{
const int kItemDepth = 0; // tree is flattened when searching
Stack<T> stack = new Stack<T>();
if (searchFromThis?.children != null)
{
foreach (var element in searchFromThis.children)
stack.Push((T)element);
}
while (stack.Count > 0)
{
T current = stack.Pop();
// Matches search?
if (IsValidElement(current, search))
{
result.Add(new TreeViewItem<T>(current.id, kItemDepth, current.Name, current));
}
if (current.children != null && current.children.Count > 0)
{
foreach (var element in current.children)
{
stack.Push((T)element);
}
}
}
SortSearchResult(result);
}
protected virtual void SortSearchResult(List<TreeViewItem> rows)
{
rows.Sort((x, y) => EditorUtility.NaturalCompare(x.displayName, y.displayName)); // sort by displayName by default, can be overriden for multicolumn solutions
}
protected override IList<int> GetAncestors(int id)
{
return m_TreeModel.GetAncestors(id);
}
protected override IList<int> GetDescendantsThatHaveChildren(int id)
{
return m_TreeModel.GetDescendantsThatHaveChildren(id);
}
// Dragging
//-----------
const string k_GenericDragID = "GenericDragColumnDragging";
protected override bool CanStartDrag(CanStartDragArgs args)
{
return true;
}
protected override void SetupDragAndDrop(SetupDragAndDropArgs args)
{
if (hasSearch)
return;
DragAndDrop.PrepareStartDrag();
var draggedRows = GetRows().Where(item => args.draggedItemIDs.Contains(item.id)).ToList();
DragAndDrop.SetGenericData(k_GenericDragID, draggedRows);
DragAndDrop.objectReferences = new UnityEngine.Object[] { }; // this IS required for dragging to work
string title = draggedRows.Count == 1 ? draggedRows[0].displayName : "< Multiple >";
DragAndDrop.StartDrag(title);
}
protected override DragAndDropVisualMode HandleDragAndDrop(DragAndDropArgs args)
{
// Check if we can handle the current drag data (could be dragged in from other areas/windows in the editor)
var draggedRows = DragAndDrop.GetGenericData(k_GenericDragID) as List<TreeViewItem>;
if (draggedRows == null)
return DragAndDropVisualMode.None;
// Parent item is null when dragging outside any tree view items.
switch (args.dragAndDropPosition)
{
case DragAndDropPosition.UponItem:
case DragAndDropPosition.BetweenItems:
{
bool validDrag = ValidDrag(args.parentItem, draggedRows);
if (args.performDrop && validDrag)
{
T parentData = ((TreeViewItem<T>)args.parentItem).data;
OnDropDraggedElementsAtIndex(draggedRows, parentData, args.insertAtIndex == -1 ? 0 : args.insertAtIndex);
}
return validDrag ? DragAndDropVisualMode.Move : DragAndDropVisualMode.None;
}
case DragAndDropPosition.OutsideItems:
{
if (args.performDrop)
OnDropDraggedElementsAtIndex(draggedRows, m_TreeModel.root, m_TreeModel.root.children.Count);
return DragAndDropVisualMode.Move;
}
default:
Debug.LogError("Unhandled enum " + args.dragAndDropPosition);
return DragAndDropVisualMode.None;
}
}
public virtual void OnDropDraggedElementsAtIndex(List<TreeViewItem> draggedRows, T parent, int insertIndex)
{
if (beforeDroppingDraggedItems != null)
beforeDroppingDraggedItems(draggedRows);
var draggedElements = new List<TreeElement>();
foreach (var x in draggedRows)
draggedElements.Add(((TreeViewItem<T>)x).data);
var selectedIDs = draggedElements.Select(x => x.id).ToArray();
m_TreeModel.MoveElements(parent, insertIndex, draggedElements);
SetSelection(selectedIDs, TreeViewSelectionOptions.RevealAndFrame);
}
bool ValidDrag(TreeViewItem parent, List<TreeViewItem> draggedItems)
{
TreeViewItem currentParent = parent;
while (currentParent != null)
{
if (draggedItems.Contains(currentParent))
return false;
currentParent = currentParent.parent;
}
return true;
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 68fe63fd42552e7418aac450b41b8afb
timeCreated: 1472481611
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 680d2cdf6f918204cae2e0f840b95888
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Profiling;
namespace HeurekaGames.AssetHunterPRO.BaseTreeviewImpl.DependencyGraph
{
[System.Serializable]
public class AH_DepGraphElement : TreeElement, ISerializationCallbackReceiver
{
#region Fields
[SerializeField]
private string relativePath;
/*[SerializeField]
private string assetName;*/
[SerializeField]
private Type assetType;
private Texture icon;
[SerializeField]
private string assetTypeSerialized;
#endregion
#region Properties
public string RelativePath
{
get
{
return relativePath;
}
}
public string AssetName
{
get
{
return m_Name;
}
}
public Type AssetType
{
get
{
return assetType;
}
}
public Texture Icon
{
get
{
return icon;
}
}
public string AssetTypeSerialized
{
get
{
return assetTypeSerialized;
}
}
#endregion
public AH_DepGraphElement(string name, int depth, int id, string relativepath) : base(name, depth, id)
{
this.relativePath = relativepath;
var stringSplit = relativepath.Split('/');
//this.assetName = stringSplit.Last();
this.assetType = UnityEditor.AssetDatabase.GetMainAssetTypeAtPath(relativepath);
if (this.assetType != null)
this.assetTypeSerialized = Heureka_Serializer.SerializeType(assetType);
this.icon = UnityEditor.EditorGUIUtility.ObjectContent(null, assetType).image;
}
#region Serialization callbacks
//TODO Maybe we can store type infos in BuildInfoTreeView instead of on each individual element, might be performance heavy
//Store serializable string so we can retrieve type after serialization
public void OnBeforeSerialize()
{
if (assetType != null)
assetTypeSerialized = Heureka_Serializer.SerializeType(assetType);
}
//Set type from serialized property
public void OnAfterDeserialize()
{
if (!string.IsNullOrEmpty(AssetTypeSerialized))
{
this.assetType = Heureka_Serializer.DeSerializeType(AssetTypeSerialized);
}
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c926e474704fe51438030a9808922819
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,56 @@

using System;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace HeurekaGames.AssetHunterPRO.BaseTreeviewImpl.DependencyGraph
{
internal class AH_DepGraphHeader : MultiColumnHeader
{
Mode m_Mode;
public enum Mode
{
Treeview,
SortedList
}
public AH_DepGraphHeader(MultiColumnHeaderState state) : base(state)
{
mode = Mode.Treeview;
}
public Mode mode
{
get
{
return m_Mode;
}
set
{
m_Mode = value;
switch (m_Mode)
{
case Mode.Treeview:
canSort = true;
height = DefaultGUI.minimumHeight;
break;
case Mode.SortedList:
canSort = true;
height = DefaultGUI.defaultHeight;
break;
}
}
}
protected override void ColumnHeaderClicked(MultiColumnHeaderState.Column column, int columnIndex)
{
if (mode == Mode.Treeview)
{
mode = Mode.SortedList;
}
base.ColumnHeaderClicked(column, columnIndex);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7b7689f8140a16040a570611162bd75e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,305 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using UnityEngine.Assertions;
namespace HeurekaGames.AssetHunterPRO.BaseTreeviewImpl.DependencyGraph
{
internal class AH_DepGraphTreeviewWithModel : TreeViewWithTreeModel<AH_DepGraphElement>
{
const float kRowHeights = 20f;
const float kToggleWidth = 18f;
// All columns
enum MyColumns
{
Icon,
Name
}
public enum SortOption
{
AssetType,
Name
}
// Sort options per column
SortOption[] m_SortOptions =
{
SortOption.AssetType,
SortOption.Name
};
public static void TreeToList(TreeViewItem root, IList<TreeViewItem> result)
{
if (root == null)
throw new NullReferenceException("root");
if (result == null)
throw new NullReferenceException("result");
result.Clear();
if (root.children == null)
return;
Stack<TreeViewItem> stack = new Stack<TreeViewItem>();
for (int i = root.children.Count - 1; i >= 0; i--)
stack.Push(root.children[i]);
while (stack.Count > 0)
{
TreeViewItem current = stack.Pop();
result.Add(current);
if (current.hasChildren && current.children[0] != null)
{
for (int i = current.children.Count - 1; i >= 0; i--)
{
stack.Push(current.children[i]);
}
}
}
}
public AH_DepGraphTreeviewWithModel(TreeViewState state, MultiColumnHeader multicolumnHeader, TreeModel<AH_DepGraphElement> model) : base(state, multicolumnHeader, model)
{
Assert.AreEqual(m_SortOptions.Length, Enum.GetValues(typeof(MyColumns)).Length, "Ensure number of sort options are in sync with number of MyColumns enum values");
// Custom setup
rowHeight = kRowHeights;
columnIndexForTreeFoldouts = 1;
showAlternatingRowBackgrounds = true;
showBorder = true;
customFoldoutYOffset = (kRowHeights - EditorGUIUtility.singleLineHeight) * 0.5f; // center foldout in the row since we also center content. See RowGUI
extraSpaceBeforeIconAndLabel = kToggleWidth;
multicolumnHeader.sortingChanged += OnSortingChanged;
//IF we want to start expanded one level
if (model.root.hasChildren)
SetExpanded(model.root.children[0].id, true);
Reload();
}
protected override void SingleClickedItem(int id)
{
base.SingleClickedItem(id);
var clickedObject = getObjectFromID(id);
EditorGUIUtility.PingObject(clickedObject);
}
protected override void DoubleClickedItem(int id)
{
base.DoubleClickedItem(id);
var clickedObject = getObjectFromID(id);
Selection.activeObject = clickedObject;
}
private UnityEngine.Object getObjectFromID(int id)
{
var refGraphElement = FindItem(id, rootItem) as TreeViewItem<AH_DepGraphElement>;
return AssetDatabase.LoadMainAssetAtPath(refGraphElement.data.RelativePath);
}
protected override bool RequiresSorting()
{
//Show as list if base requires sorting OR if we chose sortedList
return base.RequiresSorting() || ((AH_DepGraphHeader)multiColumnHeader).mode == AH_DepGraphHeader.Mode.SortedList;
}
// Note we We only build the visible rows, only the backend has the full tree information.
// The treeview only creates info for the row list.
protected override IList<TreeViewItem> BuildRows(TreeViewItem root)
{
var rows = base.BuildRows(root);
SortIfNeeded(root, rows);
return rows;
}
void OnSortingChanged(MultiColumnHeader multiColumnHeader)
{
ModelChanged();
SortIfNeeded(rootItem, GetRows());
}
void SortIfNeeded(TreeViewItem root, IList<TreeViewItem> rows)
{
if (rows.Count <= 1)
return;
if (multiColumnHeader.sortedColumnIndex == -1)
{
return; // No column to sort for (just use the order the data are in)
}
SortByMultipleColumns();
TreeToList(root, rows);
Repaint();
}
void SortByMultipleColumns()
{
var sortedColumns = multiColumnHeader.state.sortedColumns;
if (sortedColumns.Length == 0)
return;
var myTypes = rootItem.children.Cast<TreeViewItem<AH_DepGraphElement>>();
var orderedQuery = InitialOrder(myTypes, sortedColumns);
for (int i = 1; i < sortedColumns.Length; i++)
{
SortOption sortOption = m_SortOptions[sortedColumns[i]];
bool ascending = multiColumnHeader.IsSortedAscending(sortedColumns[i]);
switch (sortOption)
{
case SortOption.Name:
orderedQuery = orderedQuery.ThenBy(l => l.data.AssetName, ascending);
break;
case SortOption.AssetType:
orderedQuery = orderedQuery.ThenBy(l => l.data.AssetType, ascending);
break;
/*case SortOption.Path:
orderedQuery = orderedQuery.ThenBy(l => l.data.RelativePath, ascending);
break;*/
default:
Assert.IsTrue(false, "Unhandled enum");
break;
}
}
rootItem.children = orderedQuery.Cast<TreeViewItem>().ToList();
}
IOrderedEnumerable<TreeViewItem<AH_DepGraphElement>> InitialOrder(IEnumerable<TreeViewItem<AH_DepGraphElement>> myTypes, int[] history)
{
SortOption sortOption = m_SortOptions[history[0]];
bool ascending = multiColumnHeader.IsSortedAscending(history[0]);
switch (sortOption)
{
case SortOption.Name:
return myTypes.Order(l => l.data.AssetName, ascending);
case SortOption.AssetType:
return myTypes.Order(l => l.data.AssetTypeSerialized, ascending);
default:
Assert.IsTrue(false, "Unhandled enum");
break;
}
// default
return myTypes.Order(l => l.data.AssetName, ascending);
}
protected override void RowGUI(RowGUIArgs args)
{
var item = (TreeViewItem<AH_DepGraphElement>)args.item;
for (int i = 0; i < args.GetNumVisibleColumns(); ++i)
{
CellGUI(args.GetCellRect(i), item, (MyColumns)args.GetColumn(i), ref args);
}
}
void CellGUI(Rect cellRect, TreeViewItem<AH_DepGraphElement> item, MyColumns column, ref RowGUIArgs args)
{
// Center cell rect vertically (makes it easier to place controls, icons etc in the cells)
CenterRectUsingSingleLineHeight(ref cellRect);
AH_DepGraphElement element = (AH_DepGraphElement)item.data;
switch (column)
{
case MyColumns.Icon:
{
if (item.data.AssetType != null)
GUI.DrawTexture(cellRect, item.data.Icon, ScaleMode.ScaleToFit);
}
break;
case MyColumns.Name:
{
Rect nameRect = cellRect;
nameRect.x += GetContentIndent(item);
DefaultGUI.Label(nameRect, item.data.AssetName, args.selected, args.focused);
}
break;
/*case MyColumns.Path:
{
Rect nameRect = cellRect;
nameRect.x += GetContentIndent(item);
DefaultGUI.Label(nameRect, item.data.RelativePath, args.selected, args.focused);
}
break;*/
}
}
protected override bool CanMultiSelect(TreeViewItem item)
{
return true;
}
public static MultiColumnHeaderState CreateDefaultMultiColumnHeaderState(float treeViewWidth)
{
var columns = new[]
{
new MultiColumnHeaderState.Column
{
headerContent = new GUIContent(EditorGUIUtility.FindTexture("FilterByType"), "Type of asset"),
contextMenuText = "Type",
headerTextAlignment = TextAlignment.Center,
sortedAscending = true,
sortingArrowAlignment = TextAlignment.Right,
width = 30,
minWidth = 30,
maxWidth = 30,
autoResize = false,
allowToggleVisibility = true
},
new MultiColumnHeaderState.Column
{
headerContent = new GUIContent("Name"),
headerTextAlignment = TextAlignment.Left,
sortedAscending = true,
sortingArrowAlignment = TextAlignment.Center,
//width = 320,
minWidth = 200,
autoResize = true,
allowToggleVisibility = false
}
};
Assert.AreEqual(columns.Length, Enum.GetValues(typeof(MyColumns)).Length, "Number of columns should match number of enum values: You probably forgot to update one of them.");
var state = new MultiColumnHeaderState(columns);
return state;
}
}
static class MyExtensionMethods
{
public static IOrderedEnumerable<T> Order<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector, bool ascending)
{
if (ascending)
{
return source.OrderBy(selector);
}
else
{
return source.OrderByDescending(selector);
}
}
public static IOrderedEnumerable<T> ThenBy<T, TKey>(this IOrderedEnumerable<T> source, Func<T, TKey> selector, bool ascending)
{
if (ascending)
{
return source.ThenBy(selector);
}
else
{
return source.ThenByDescending(selector);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 897092982376e0c4886fc38c96e04090
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,12 @@
using System;
using UnityEditor;
using UnityEngine;
namespace HeurekaGames
{
[Obsolete("AHPRO: This file is obsolete. You can safely delete")]
public class ObsoleteWindowStyler
{
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 88440adf44e52134b85213e96b875a00
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 121e2829a526f4545b854a74a073fd70
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a73555d1db7c7ec88a39e4e9f98238cd59a46da74545bbd8703fa8d20050971b
size 26960

View File

@ -0,0 +1,80 @@
fileFormatVersion: 2
guid: e3ad9951a783ce1458596c95375a0e05
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 4
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 0
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -1
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 512
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Standalone
maxTextureSize: 512
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show More