Skip to content

Commit 56afe0b

Browse files
committed
Initial commit
0 parents  commit 56afe0b

15 files changed

Lines changed: 395 additions & 0 deletions

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Auto detect text files and perform LF normalization
2+
* text=auto

.gitignore

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
[Ll]ibrary/
2+
[Tt]emp/
3+
[Oo]bj/
4+
[Bb]uild/
5+
[Bb]uilds/
6+
Assets/AssetStoreTools*
7+
8+
# Visual Studio cache directory
9+
.vs/
10+
11+
# Autogenerated VS/MD/Consulo solution and project files
12+
ExportedObj/
13+
.consulo/
14+
*.csproj
15+
*.unityproj
16+
*.sln
17+
*.suo
18+
*.tmp
19+
*.user
20+
*.userprefs
21+
*.pidb
22+
*.booproj
23+
*.svd
24+
*.pdb
25+
*.opendb
26+
*.VC.db
27+
28+
# Unity3D generated meta files
29+
*.pidb.meta
30+
*.pdb.meta
31+
32+
# Unity3D Generated File On Crash Reports
33+
sysinfo.txt
34+
35+
# Builds
36+
*.apk
37+
*.unitypackage

Assets~/copy.gif

75.3 KB
Loading

Assets~/cut.gif

57.8 KB
Loading

Editor.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Editor/CopyPaste.cs

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
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+
}

Editor/CopyPaste.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "com.nomnom.project-window-extensions.Editor",
3+
"rootNamespace": "Nomnom.ProjectWindowExtensions",
4+
"references": [],
5+
"includePlatforms": [],
6+
"excludePlatforms": [],
7+
"allowUnsafeCode": false,
8+
"overrideReferences": false,
9+
"precompiledReferences": [],
10+
"autoReferenced": true,
11+
"defineConstraints": [],
12+
"versionDefines": [],
13+
"noEngineReferences": false
14+
}

Editor/com.nomnom.project-window-extensions.Editor.asmdef.meta

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

LICENSE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021 Andrew Burke
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

0 commit comments

Comments
 (0)