Merge branch 'Alexei' into Zakhar
# Conflicts: # Assets/Heureka.meta # Assets/Resources/1/VFX/Bonus/Attack/Mana.prefab.meta # Assets/Resources/1/VFX/Bonus/Attack/Power.prefab.meta # Assets/Resources/1/VFX/Bonus/Attack/Replace.prefab.meta # Assets/Resources/1/VFX/Bonus/Protection/Magnet.prefab.meta # Assets/Resources/1/VFX/Bonus/Protection/Shield.prefab.meta # Packages/manifest.json # Packages/packages-lock.json
This commit is contained in:
commit
b6510c0d30
9
.gitattributes
vendored
9
.gitattributes
vendored
@ -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
|
||||
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f95ce1e78df8b924084f24bcfa5a620d
|
||||
guid: 235876c72d1be064dba0c8362a7ffcb2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 89412f229cb2738409fadd58645601f8
|
||||
guid: 82438a27e3aa87a4ca1bbdca45986211
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fac25ad7b3a704a4a81526eaba5977ab
|
||||
guid: c424fdb6dffbc984d9ade90acf8770d8
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
392
Assets/Heureka/AssetHunterPRO/Editor/AHPRO_Readme.asset
Normal file
392
Assets/Heureka/AssetHunterPRO/Editor/AHPRO_Readme.asset
Normal 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
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 444d04ce94edee24d825017dccaa882b
|
||||
guid: 059cb120dafc78d4bbf6d0e96525b794
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 0
|
@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b4279a46ecf187080ec577b5bcea148c0f074b132533740ce494df0bf9a912c2
|
||||
size 762920
|
@ -1,7 +1,6 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e1c8dccfa1d4780469116bfbb759e34b
|
||||
folderAsset: yes
|
||||
timeCreated: 1448289097
|
||||
guid: 4be7ccf6efbafc94fa96a66c564aa3ff
|
||||
timeCreated: 1544715872
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e7666f67e37eb874daafc5d26307e6d2
|
||||
guid: 7220ae8106f289c4597ec7cfffc7a705
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
@ -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
|
@ -1,7 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8cab9569bb4f24849a60d15577229a50
|
||||
guid: a749fc7acdf105544864dc3d96bd8582
|
||||
timeCreated: 1498416873
|
||||
licenseType: Store
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f00057b856fac3d4faf8bf6593ee6795
|
||||
guid: 4c812440a5dcbb54d94816649c8007da
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ead473f396ac464a89cdfba8df1ef39
|
||||
PrefabImporter:
|
||||
guid: 4727f108aded7914e8616b79cb513bee
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7bf36f74ce449ea4a8d88a293fbc5f7c
|
||||
guid: d9fd5489ebed282448ffce56090fefd7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f0d48f26cc38ee4429ee281b828a3ee4
|
||||
guid: 49792b455df9c0341a51d3ed9126a9cc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
@ -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
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 598f6898dde0f7f45a9a41fa36a520e4
|
||||
guid: 173fd4f00e5547f4bb3c260d9cbb16d5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
@ -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
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2ebf4365394159248999b950922fef70
|
||||
guid: e55a84cb0bcf0414085a72914a3a9841
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
@ -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
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 12cee7fed09c12246ae59c3f225d3243
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 094127970d2184049b1ce37e1eb2896a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 38e00993adc35ec42a114a8fc65532f0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 932600e0df520a746a6b3c38609a0bc4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 32b3afe601d97d643bb716a62b16106c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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();
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f54e17d705a2894d88787e24190ebfa
|
||||
timeCreated: 1499439792
|
||||
guid: 200a2edfc52002c468b33af791f333c0
|
||||
timeCreated: 1498416726
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f678a0e10225c0a4faaf391cccd00d2b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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 = "");
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b539e63be4aca346ad27ff903abb802
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
303
Assets/Heureka/AssetHunterPRO/Editor/Scripts/AH_IgnoreList.cs
Normal file
303
Assets/Heureka/AssetHunterPRO/Editor/Scripts/AH_IgnoreList.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e666beed7750ac4438892d0bb9ec6257
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a1fe58bf2b20a944bfd906f72b39287
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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()));
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d0994f98ad87954690048c4b3e19047
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e664ab2ccf1df844bb835e85586b62d
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9499f6dadb6456a45ad8decc1c18deef
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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();
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d1ad9523d8b90e4aa519e18c20cc206
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 628ef9b0c7e4fd24db54868035e11237
|
||||
timeCreated: 1499558257
|
||||
guid: f4578e682bc79b847ba0187ca63670a5
|
||||
timeCreated: 1529398555
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 212dc98f02d51a3469c3d1f4dc517bc0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2611a0e0cb88863459b1f2cfb4b68112
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 34d0a9acdf4dbcb4f82cfd0156081719
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2dfbd02bf3ebeca47b215bc02773c500
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
349
Assets/Heureka/AssetHunterPRO/Editor/Scripts/AH_Utils.cs
Normal file
349
Assets/Heureka/AssetHunterPRO/Editor/Scripts/AH_Utils.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7faa64a828219b44cb0eb5bfbfa83524
|
||||
timeCreated: 1494264015
|
||||
guid: 4cdbc131ece9fb340865ffc0cc57f156
|
||||
timeCreated: 1529997872
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
@ -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
|
||||
}
|
||||
*/
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 137ae79880bd61047a9749429269f90c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
489
Assets/Heureka/AssetHunterPRO/Editor/Scripts/AH_Window.cs
Normal file
489
Assets/Heureka/AssetHunterPRO/Editor/Scripts/AH_Window.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c15a7cb4e3c84c141a3abe55fd9c73fe
|
||||
timeCreated: 1529997477
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 98f18890366e7f746afc4ba3fdb9b0a4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,7 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a4eb7b5939d1c0b42a2392aa1f5983dd
|
||||
guid: 5b0f914bfa5d220479ce3949edf63b61
|
||||
folderAsset: yes
|
||||
timeCreated: 1448289024
|
||||
timeCreated: 1544690523
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
171
Assets/Heureka/AssetHunterPRO/Editor/Scripts/External/Docker.cs
vendored
Normal file
171
Assets/Heureka/AssetHunterPRO/Editor/Scripts/External/Docker.cs
vendored
Normal 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
|
12
Assets/Heureka/AssetHunterPRO/Editor/Scripts/External/Docker.cs.meta
vendored
Normal file
12
Assets/Heureka/AssetHunterPRO/Editor/Scripts/External/Docker.cs.meta
vendored
Normal 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:
|
@ -1,7 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 736815ae537c2154ab0b8cb430bfd4bf
|
||||
guid: 7e8494b23d4cc3e42a812c1bc45860e7
|
||||
folderAsset: yes
|
||||
timeCreated: 1455389209
|
||||
timeCreated: 1472481428
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
@ -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
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a118fee61d7a50043b17a704f0909285
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: efad43b96d78a3940a3d101b069a04ff
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e00d20ba7c1f4d446a34ea24d8b82c4e
|
||||
timeCreated: 1464348051
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cc7d76ef034ec884fac439e301715bd7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 69be32fe4d27dde489209c5885c1e5dc
|
||||
timeCreated: 1472024155
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fd65e8f324e17a344a97ddcf5a8d89d2
|
||||
timeCreated: 1471616285
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6f9fab1cf2636a6439c644bf08108abb
|
||||
timeCreated: 1472122507
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 68fe63fd42552e7418aac450b41b8afb
|
||||
timeCreated: 1472481611
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a07adc33cd40ff04a9a41a0c3eeb0e2c
|
||||
guid: 680d2cdf6f918204cae2e0f840b95888
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
@ -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
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c926e474704fe51438030a9808922819
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7b7689f8140a16040a570611162bd75e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 897092982376e0c4886fc38c96e04090
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 88440adf44e52134b85213e96b875a00
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/Heureka/AssetHunterPRO/UI.meta
Normal file
8
Assets/Heureka/AssetHunterPRO/UI.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 121e2829a526f4545b854a74a073fd70
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
3
Assets/Heureka/AssetHunterPRO/UI/AH_Achievement.png
Normal file
3
Assets/Heureka/AssetHunterPRO/UI/AH_Achievement.png
Normal file
@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a73555d1db7c7ec88a39e4e9f98238cd59a46da74545bbd8703fa8d20050971b
|
||||
size 26960
|
@ -1,15 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b2644742c24b6ed468e76be51600e461
|
||||
timeCreated: 1521634853
|
||||
licenseType: Pro
|
||||
guid: e3ad9951a783ce1458596c95375a0e05
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
externalObjects: {}
|
||||
serializedVersion: 4
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 0
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
@ -32,15 +29,15 @@ TextureImporter:
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: -1
|
||||
aniso: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapU: -1
|
||||
wrapV: -1
|
||||
wrapW: -1
|
||||
nPOTScale: 1
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
@ -48,19 +45,26 @@ TextureImporter:
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
maxTextureSize: 512
|
||||
textureFormat: -1
|
||||
textureCompression: 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
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user