1+ using System . Collections . Generic ;
2+ using System . IO ;
3+ using System . Linq ;
4+ using UnityEditor ;
5+ using UnityEngine ;
6+
7+ namespace Nomnom . ProjectWindowExtensions . Editor {
8+ internal static class CopyPaste {
9+ private const int PRIORITY = 0 ;
10+
11+ private static Dictionary < string , List < AssetItem > > _tmpBuffer ;
12+ private static Dictionary < string , List < AssetItem > > _copyBuffer ;
13+ private static Dictionary < string , List < AssetItem > > _cutBuffer ;
14+ private static List < string > _selectedThings ;
15+
16+ static CopyPaste ( ) {
17+ EditorApplication . projectWindowItemOnGUI += OnProjectGUI ;
18+ }
19+
20+ [ MenuItem ( "Assets/IO/Copy %c" , false , PRIORITY ) ]
21+ private static void DoCopy ( ) {
22+ // store all assets into a buffer
23+ // copy tmp buffer to copy buffer
24+ _copyBuffer = new Dictionary < string , List < AssetItem > > ( _tmpBuffer ) ;
25+ _cutBuffer = null ;
26+
27+ // now in copy state
28+ }
29+
30+ [ MenuItem ( "Assets/IO/Copy %c" , true ) ]
31+ private static bool DoCopyValidate ( ) {
32+ return DefaultValidation ( ) ;
33+ }
34+
35+ [ MenuItem ( "Assets/IO/Paste %v" , false , PRIORITY ) ]
36+ private static void DoPaste ( ) {
37+ // get new folder
38+ var activeObject = Selection . activeObject ;
39+ string objPath = AssetDatabase . GetAssetPath ( activeObject ) . Replace ( '/' , '\\ ' ) ;
40+ string objFolder = Path . HasExtension ( objPath ) ? Path . GetDirectoryName ( objPath ) : objPath ;
41+
42+ // go through copy buffer and copy files
43+
44+ // check if we are only dealing with files
45+ bool inCopyMode = _copyBuffer != null && _copyBuffer . Count > 0 ;
46+ var buffer = inCopyMode ? _copyBuffer : _cutBuffer ;
47+ if ( buffer . FirstOrDefault ( parent => parent . Value . FirstOrDefault ( folder => folder . IsFolder ) != null ) . Value == null ) {
48+ foreach ( var pair in buffer ) {
49+ foreach ( AssetItem assetItem in pair . Value ) {
50+ if ( inCopyMode ) {
51+ AssetDatabase . CopyAsset ( assetItem . Path , $ "{ objFolder } \\ { Path . GetFileName ( assetItem . Path ) } ") ;
52+ } else {
53+ AssetDatabase . MoveAsset ( assetItem . Path , $ "{ objFolder } \\ { Path . GetFileName ( assetItem . Path ) } ") ;
54+ }
55+ }
56+ }
57+
58+ AssetDatabase . SaveAssets ( ) ;
59+ AssetDatabase . Refresh ( ) ;
60+
61+ buffer . Clear ( ) ;
62+ _tmpBuffer . Clear ( ) ;
63+ return ;
64+ }
65+
66+ // dealing with folders too
67+ foreach ( var pair in buffer ) {
68+ foreach ( AssetItem assetItem in pair . Value ) {
69+ if ( inCopyMode ) {
70+ AssetDatabase . CopyAsset ( assetItem . Path , $ "{ objFolder } \\ { Path . GetFileName ( assetItem . Path ) } ") ;
71+ } else {
72+ AssetDatabase . MoveAsset ( assetItem . Path , $ "{ objFolder } \\ { Path . GetFileName ( assetItem . Path ) } ") ;
73+ }
74+ }
75+ }
76+
77+ AssetDatabase . SaveAssets ( ) ;
78+ AssetDatabase . Refresh ( ) ;
79+
80+ buffer . Clear ( ) ;
81+ _tmpBuffer . Clear ( ) ;
82+ }
83+
84+ [ MenuItem ( "Assets/IO/Paste %v" , true ) ]
85+ private static bool DoPasteValidate ( ) {
86+ return DefaultValidation ( ) && ( _copyBuffer != null && _copyBuffer . Count > 0 ) || ( _cutBuffer != null && _cutBuffer . Count > 0 ) ;
87+ }
88+
89+ [ MenuItem ( "Assets/IO/Cut %x" , false , PRIORITY ) ]
90+ private static void DoCut ( ) {
91+ // store all assets into a buffer
92+ // copy tmp buffer to cut buffer
93+ _copyBuffer = null ;
94+ _cutBuffer = new Dictionary < string , List < AssetItem > > ( _tmpBuffer ) ;
95+
96+ // now in cut state
97+ }
98+
99+ [ MenuItem ( "Assets/IO/Cut %x" , true ) ]
100+ private static bool DoCutValidate ( ) {
101+ return DefaultValidation ( ) ;
102+ }
103+
104+ [ MenuItem ( "Assets/IO/Copy - Cancel %#c" , false , PRIORITY ) ]
105+ private static void DoCopyCancel ( ) {
106+ _tmpBuffer . Clear ( ) ;
107+ _copyBuffer . Clear ( ) ;
108+ }
109+
110+ [ MenuItem ( "Assets/IO/Copy - Cancel %#c" , true ) ]
111+ private static bool DoCopyCancelValidate ( ) {
112+ return DefaultValidation ( ) && _copyBuffer != null && _copyBuffer . Count > 0 ;
113+ }
114+
115+ [ MenuItem ( "Assets/IO/Cut - Cancel %#x" , false , PRIORITY ) ]
116+ private static void DoCutCancel ( ) {
117+ _tmpBuffer . Clear ( ) ;
118+ _cutBuffer . Clear ( ) ;
119+ }
120+
121+ [ MenuItem ( "Assets/IO/Cut - Cancel %#x" , true ) ]
122+ private static bool DoCutCancelValidate ( ) {
123+ return DefaultValidation ( ) && _cutBuffer != null && _cutBuffer . Count > 0 ;
124+ }
125+
126+ private static bool DefaultValidation ( ) {
127+ return ValidateNotRestrictedPath ( ) && CollectAssetItems ( ref _tmpBuffer ) ;
128+ }
129+
130+ private static bool ValidateNotRestrictedPath ( ) {
131+ string path = AssetDatabase . GetAssetPath ( Selection . activeObject ) ;
132+
133+ return ! string . IsNullOrEmpty ( path ) && ! path . Equals ( "Assets" ) && ! path . StartsWith ( "Packages" ) ;
134+ }
135+
136+ private static void OnProjectGUI ( string guid , Rect selectionRect ) {
137+ bool inCopyMode = _copyBuffer != null && _copyBuffer . Count > 0 ;
138+ bool inCutMode = _cutBuffer != null && _cutBuffer . Count > 0 ;
139+
140+ if ( _selectedThings == null || _selectedThings . Count == 0 ) {
141+ return ;
142+ }
143+
144+ if ( _selectedThings . Contains ( guid ) ) {
145+ if ( inCopyMode ) {
146+ EditorGUI . DrawRect ( selectionRect , new Color ( 1 , 0.92f , 0.016f , 0.3f ) ) ;
147+ } else if ( inCutMode ) {
148+ EditorGUI . DrawRect ( selectionRect , new Color ( 1 , 0 , 0 , 0.3f ) ) ;
149+ }
150+ }
151+ }
152+
153+ private static bool CollectAssetItems ( ref Dictionary < string , List < AssetItem > > outputItems ) {
154+ _tmpBuffer ??= new Dictionary < string , List < AssetItem > > ( ) ;
155+ _tmpBuffer . Clear ( ) ;
156+
157+ _selectedThings ??= new List < string > ( ) ;
158+ _selectedThings . Clear ( ) ;
159+
160+ var objects = Selection . objects ;
161+ outputItems = new Dictionary < string , List < AssetItem > > ( ) ;
162+ bool hasFolderSelected = false ;
163+
164+ foreach ( Object o in objects ) {
165+ string path = AssetDatabase . GetAssetPath ( o ) . Replace ( '/' , '\\ ' ) ;
166+ string [ ] parentSplit = path . Split ( '\\ ' ) ;
167+ string parent = Path . GetDirectoryName ( path ) ;
168+ AssetItem item = new AssetItem ( path ) ;
169+
170+ if ( item . IsFolder ) {
171+ hasFolderSelected = true ;
172+ }
173+
174+ _selectedThings . Add ( AssetDatabase . GUIDFromAssetPath ( AssetDatabase . GetAssetPath ( o ) ) . ToString ( ) ) ;
175+
176+ string currentParentPath = string . Empty ;
177+ for ( int i = 0 ; i < parentSplit . Length - 1 ; i ++ ) {
178+ string s = parentSplit [ i ] ;
179+ currentParentPath += $ "{ s } ";
180+ if ( ! outputItems . TryGetValue ( currentParentPath , out List < AssetItem > items ) ) {
181+ items = new List < AssetItem > ( ) ;
182+ outputItems [ currentParentPath ] = items ;
183+ }
184+
185+ currentParentPath += "\\ " ;
186+ }
187+
188+ outputItems [ parent ] . Add ( item ) ;
189+ }
190+
191+ if ( hasFolderSelected ) {
192+ string wantedParent = null ;
193+ foreach ( var pair in outputItems ) {
194+ foreach ( AssetItem assetItem in pair . Value ) {
195+ string parent = Path . GetDirectoryName ( assetItem . Path ) ;
196+
197+ if ( assetItem . IsFolder ) {
198+ if ( string . IsNullOrEmpty ( wantedParent ) ) {
199+ wantedParent = parent ;
200+ continue ;
201+ }
202+ }
203+
204+ if ( parent != wantedParent ) {
205+ outputItems . Clear ( ) ;
206+ return false ;
207+ }
208+ }
209+ }
210+ }
211+
212+ return true ;
213+ }
214+
215+ private class AssetItem {
216+ public string Path ;
217+ public bool IsFolder ;
218+
219+ public AssetItem ( string path ) {
220+ Path = path ;
221+ IsFolder = AssetDatabase . IsValidFolder ( path ) ;
222+ }
223+ }
224+ }
225+ }
0 commit comments