ProjectWindow.cs 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using BansheeEngine;
  5. namespace BansheeEditor
  6. {
  7. internal enum ProjectViewType
  8. {
  9. Grid64, Grid48, Grid32, List16
  10. }
  11. internal sealed class ProjectWindow : EditorWindow
  12. {
  13. private class ContentInfo
  14. {
  15. public ContentInfo(ProjectWindow window, ProjectViewType viewType)
  16. {
  17. GUIPanel parentPanel = window.scrollAreaPanel;
  18. GUIPanel contentPanel = parentPanel.AddPanel(1);
  19. overlay = parentPanel.AddPanel(0);
  20. underlay = parentPanel.AddPanel(2);
  21. main = contentPanel.AddLayoutY();
  22. if (viewType == ProjectViewType.List16)
  23. {
  24. tileSize = 16;
  25. gridLayout = false;
  26. }
  27. else
  28. {
  29. switch (viewType)
  30. {
  31. case ProjectViewType.Grid64:
  32. tileSize = 64;
  33. break;
  34. case ProjectViewType.Grid48:
  35. tileSize = 48;
  36. break;
  37. case ProjectViewType.Grid32:
  38. tileSize = 32;
  39. break;
  40. }
  41. gridLayout = true;
  42. }
  43. this.window = window;
  44. }
  45. public GUILayout main;
  46. public GUIPanel overlay;
  47. public GUIPanel underlay;
  48. public ProjectWindow window;
  49. public int tileSize;
  50. public bool gridLayout;
  51. }
  52. private class ElementEntry
  53. {
  54. // Note: Order of these is relevant
  55. enum UnderlayState
  56. {
  57. None, Hovered, Selected, Pinged
  58. }
  59. public int index;
  60. public string path;
  61. public GUITexture icon;
  62. public GUILabel label;
  63. public Rect2I bounds;
  64. private GUITexture underlay;
  65. private ContentInfo info;
  66. private UnderlayState underlayState;
  67. public ElementEntry(ContentInfo info, GUILayout parent, LibraryEntry entry, int index)
  68. {
  69. GUILayout entryLayout;
  70. if (info.gridLayout)
  71. entryLayout = parent.AddLayoutY();
  72. else
  73. entryLayout = parent.AddLayoutX();
  74. SpriteTexture iconTexture = GetIcon(entry);
  75. icon = new GUITexture(iconTexture, GUIImageScaleMode.ScaleToFit,
  76. true, GUIOption.FixedHeight(info.tileSize), GUIOption.FixedWidth(info.tileSize));
  77. label = null;
  78. if (info.gridLayout)
  79. {
  80. int labelWidth = info.tileSize + LABEL_EXTRA_WIDTH;
  81. label = new GUILabel(entry.Name, EditorStyles.MultiLineLabel,
  82. GUIOption.FixedWidth(labelWidth), GUIOption.FlexibleHeight(0, MAX_LABEL_HEIGHT));
  83. }
  84. else
  85. {
  86. label = new GUILabel(entry.Name);
  87. }
  88. entryLayout.AddElement(icon);
  89. entryLayout.AddElement(label);
  90. this.info = info;
  91. this.index = index;
  92. this.path = entry.Path;
  93. this.bounds = new Rect2I();
  94. this.underlay = null;
  95. }
  96. public void Initialize()
  97. {
  98. bounds = icon.Bounds;
  99. Rect2I labelBounds = label.Bounds;
  100. bounds.x = MathEx.Min(bounds.x, labelBounds.x);
  101. bounds.y = MathEx.Min(bounds.y, labelBounds.y);
  102. bounds.width = MathEx.Max(bounds.x + bounds.width,
  103. labelBounds.x + labelBounds.width) - bounds.x;
  104. bounds.height = MathEx.Max(bounds.y + bounds.height,
  105. labelBounds.y + labelBounds.height) - bounds.y;
  106. ProjectWindow hoistedWindow = info.window;
  107. string hoistedPath = path;
  108. GUIButton overlayBtn = new GUIButton("", EditorStyles.Blank);
  109. overlayBtn.Bounds = bounds;
  110. overlayBtn.OnClick += () => hoistedWindow.OnEntryClicked(hoistedPath);
  111. overlayBtn.OnDoubleClick += () => hoistedWindow.OnEntryDoubleClicked(hoistedPath);
  112. overlayBtn.SetContextMenu(info.window.entryContextMenu);
  113. info.overlay.AddElement(overlayBtn);
  114. }
  115. public Rect2I Bounds
  116. {
  117. get { return bounds; }
  118. }
  119. public void MarkAsCut(bool enable)
  120. {
  121. if (enable)
  122. icon.SetTint(CUT_COLOR);
  123. else
  124. icon.SetTint(Color.White);
  125. }
  126. public void MarkAsSelected(bool enable)
  127. {
  128. if ((int)underlayState > (int) UnderlayState.Selected)
  129. return;
  130. if (enable)
  131. {
  132. CreateUnderlay();
  133. underlay.SetTint(SELECTION_COLOR);
  134. }
  135. else
  136. ClearUnderlay();
  137. underlayState = UnderlayState.Selected;
  138. }
  139. public void MarkAsPinged(bool enable)
  140. {
  141. if ((int)underlayState > (int)UnderlayState.Pinged)
  142. return;
  143. if (enable)
  144. {
  145. CreateUnderlay();
  146. underlay.SetTint(PING_COLOR);
  147. }
  148. else
  149. ClearUnderlay();
  150. underlayState = UnderlayState.Pinged;
  151. }
  152. public void MarkAsHovered(bool enable)
  153. {
  154. if ((int)underlayState > (int)UnderlayState.Hovered)
  155. return;
  156. if (enable)
  157. {
  158. CreateUnderlay();
  159. underlay.SetTint(HOVER_COLOR);
  160. }
  161. else
  162. ClearUnderlay();
  163. underlayState = UnderlayState.Hovered;
  164. }
  165. private void ClearUnderlay()
  166. {
  167. if (underlay != null)
  168. {
  169. underlay.Destroy();
  170. underlay = null;
  171. }
  172. underlayState = UnderlayState.None;
  173. }
  174. private void CreateUnderlay()
  175. {
  176. if (underlay == null)
  177. {
  178. underlay = new GUITexture(Builtin.WhiteTexture);
  179. underlay.Bounds = Bounds;
  180. info.underlay.AddElement(underlay);
  181. }
  182. }
  183. private static SpriteTexture GetIcon(LibraryEntry entry)
  184. {
  185. if (entry.Type == LibraryEntryType.Directory)
  186. {
  187. return EditorBuiltin.FolderIcon;
  188. }
  189. else
  190. {
  191. FileEntry fileEntry = (FileEntry)entry;
  192. switch (fileEntry.ResType)
  193. {
  194. case ResourceType.Font:
  195. return EditorBuiltin.FontIcon;
  196. case ResourceType.Mesh:
  197. return EditorBuiltin.MeshIcon;
  198. case ResourceType.Texture:
  199. return EditorBuiltin.TextureIcon;
  200. case ResourceType.PlainText:
  201. return EditorBuiltin.PlainTextIcon;
  202. case ResourceType.ScriptCode:
  203. return EditorBuiltin.ScriptCodeIcon;
  204. case ResourceType.SpriteTexture:
  205. return EditorBuiltin.SpriteTextureIcon;
  206. case ResourceType.Shader:
  207. return EditorBuiltin.ShaderIcon;
  208. case ResourceType.Material:
  209. return EditorBuiltin.MaterialIcon;
  210. }
  211. }
  212. return null;
  213. }
  214. }
  215. enum MoveDirection
  216. {
  217. Up, Down, Left, Right
  218. }
  219. private const int GRID_ENTRY_SPACING = 15;
  220. private const int LIST_ENTRY_SPACING = 7;
  221. private const int MAX_LABEL_HEIGHT = 50;
  222. private const int LABEL_EXTRA_WIDTH = 10;
  223. private const int DRAG_SCROLL_HEIGHT = 20;
  224. private const int DRAG_SCROLL_AMOUNT_PER_SECOND = 100;
  225. private const int FOLDER_BUTTON_WIDTH = 20;
  226. private const int FOLDER_SEPARATOR_WIDTH = 7;
  227. private static readonly Color PING_COLOR = Color.BansheeOrange;
  228. private static readonly Color SELECTION_COLOR = Color.DarkCyan;
  229. private static readonly Color HOVER_COLOR = new Color(Color.DarkCyan.r, Color.DarkCyan.g, Color.DarkCyan.b, 0.5f);
  230. private static readonly Color CUT_COLOR = new Color(1.0f, 1.0f, 1.0f, 0.5f);
  231. private bool hasContentFocus = false;
  232. private bool HasContentFocus { get { return HasFocus && hasContentFocus; } }
  233. private string searchQuery;
  234. private bool IsSearchActive { get { return !string.IsNullOrEmpty(searchQuery); } }
  235. private ProjectViewType viewType = ProjectViewType.Grid32;
  236. private string currentDirectory = "";
  237. private List<string> selectionPaths = new List<string>();
  238. private int selectionAnchorStart = -1;
  239. private int selectionAnchorEnd = -1;
  240. private string pingPath = "";
  241. private string hoverHighlightPath = "";
  242. private GUIScrollArea contentScrollArea;
  243. private GUIPanel scrollAreaPanel;
  244. private GUILayoutX searchBarLayout;
  245. private GUIButton optionsButton;
  246. private GUILayout folderBarLayout;
  247. private GUILayout folderListLayout;
  248. private ContextMenu entryContextMenu;
  249. private ProjectDropTarget dropTarget;
  250. private List<ElementEntry> entries = new List<ElementEntry>();
  251. private Dictionary<string, ElementEntry> entryLookup = new Dictionary<string, ElementEntry>();
  252. private int elementsPerRow;
  253. private int autoScrollAmount;
  254. // Cut/Copy/Paste
  255. private List<string> copyPaths = new List<string>();
  256. private List<string> cutPaths = new List<string>();
  257. internal ProjectViewType ViewType
  258. {
  259. get { return viewType; }
  260. set { viewType = value; Refresh(); }
  261. }
  262. [MenuItem("Windows/Project", ButtonModifier.Ctrl, ButtonCode.P)]
  263. private static void OpenProjectWindow()
  264. {
  265. OpenWindow<ProjectWindow>();
  266. }
  267. private void OnInitialize()
  268. {
  269. ProjectLibrary.OnEntryAdded += OnEntryChanged;
  270. ProjectLibrary.OnEntryRemoved += OnEntryChanged;
  271. GUILayoutY contentLayout = GUI.AddLayoutY();
  272. searchBarLayout = contentLayout.AddLayoutX();
  273. GUITextField searchField = new GUITextField();
  274. searchField.OnChanged += OnSearchChanged;
  275. GUIButton clearSearchBtn = new GUIButton("C");
  276. clearSearchBtn.OnClick += ClearSearch;
  277. clearSearchBtn.SetWidth(40);
  278. optionsButton = new GUIButton("O");
  279. optionsButton.OnClick += OpenOptionsWindow;
  280. optionsButton.SetWidth(40);
  281. searchBarLayout.AddElement(searchField);
  282. searchBarLayout.AddElement(clearSearchBtn);
  283. searchBarLayout.AddElement(optionsButton);
  284. folderBarLayout = contentLayout.AddLayoutX();
  285. GUIButton homeButton = new GUIButton("H", GUIOption.FixedWidth(FOLDER_BUTTON_WIDTH));
  286. homeButton.OnClick += OnHomeClicked;
  287. GUIButton upButton = new GUIButton("U", GUIOption.FixedWidth(FOLDER_BUTTON_WIDTH));
  288. upButton.OnClick += OnUpClicked;
  289. folderBarLayout.AddElement(homeButton);
  290. folderBarLayout.AddElement(upButton);
  291. contentScrollArea = new GUIScrollArea(GUIOption.FlexibleWidth(), GUIOption.FlexibleHeight());
  292. contentLayout.AddElement(contentScrollArea);
  293. contentLayout.AddFlexibleSpace();
  294. entryContextMenu = new ContextMenu();
  295. entryContextMenu.AddItem("Cut", CutSelection, new ShortcutKey(ButtonModifier.Ctrl, ButtonCode.X));
  296. entryContextMenu.AddItem("Copy", CopySelection, new ShortcutKey(ButtonModifier.Ctrl, ButtonCode.C));
  297. entryContextMenu.AddItem("Duplicate", DuplicateSelection, new ShortcutKey(ButtonModifier.Ctrl, ButtonCode.D));
  298. entryContextMenu.AddItem("Paste", PasteToSelection, new ShortcutKey(ButtonModifier.Ctrl, ButtonCode.V));
  299. Reset();
  300. dropTarget = new ProjectDropTarget(this);
  301. dropTarget.Bounds = contentScrollArea.Bounds;
  302. dropTarget.OnStart += DoOnDragStart;
  303. dropTarget.OnDrag += DoOnDragMove;
  304. dropTarget.OnLeave += DoOnDragLeave;
  305. dropTarget.OnDrop += DoOnDragDropped;
  306. }
  307. private ElementEntry FindElementAt(Vector2I windowPos)
  308. {
  309. Rect2I scrollBounds = contentScrollArea.Layout.Bounds;
  310. Vector2I scrollPos = windowPos;
  311. scrollPos.x -= scrollBounds.x;
  312. scrollPos.y -= scrollBounds.y;
  313. foreach (var element in entries)
  314. {
  315. if (element.bounds.Contains(scrollPos))
  316. return element;
  317. }
  318. return null;
  319. }
  320. private void DoOnDragStart(Vector2I windowPos)
  321. {
  322. ElementEntry underCursorElem = FindElementAt(windowPos);
  323. if (underCursorElem == null)
  324. return;
  325. if (!selectionPaths.Contains(underCursorElem.path))
  326. Select(underCursorElem.path);
  327. ResourceDragDropData dragDropData = new ResourceDragDropData(selectionPaths.ToArray());
  328. DragDrop.StartDrag(dragDropData);
  329. }
  330. private void DoOnDragMove(Vector2I windowPos)
  331. {
  332. ElementEntry underCursorElem = FindElementAt(windowPos);
  333. if (underCursorElem == null)
  334. {
  335. ClearHoverHighlight();
  336. }
  337. else
  338. {
  339. if (underCursorElem.path != hoverHighlightPath)
  340. {
  341. ClearHoverHighlight();
  342. hoverHighlightPath = underCursorElem.path;
  343. underCursorElem.MarkAsHovered(true);
  344. }
  345. }
  346. Rect2I scrollAreaBounds = contentScrollArea.Bounds;
  347. int scrollAreaTop = scrollAreaBounds.y;
  348. int scrollAreaBottom = scrollAreaBounds.y + scrollAreaBounds.height;
  349. if (windowPos.y > scrollAreaTop && windowPos.y <= (scrollAreaTop + DRAG_SCROLL_HEIGHT))
  350. autoScrollAmount = -DRAG_SCROLL_AMOUNT_PER_SECOND;
  351. else if (windowPos.y >= (scrollAreaBottom - DRAG_SCROLL_HEIGHT) && windowPos.y < scrollAreaBottom)
  352. autoScrollAmount = DRAG_SCROLL_AMOUNT_PER_SECOND;
  353. else
  354. autoScrollAmount = 0;
  355. }
  356. private void DoOnDragLeave()
  357. {
  358. ClearHoverHighlight();
  359. autoScrollAmount = 0;
  360. }
  361. private void DoOnDragDropped(Vector2I windowPos, string[] paths)
  362. {
  363. string resourceDir = ProjectLibrary.ResourceFolder;
  364. string destinationFolder = Path.Combine(resourceDir, currentDirectory);
  365. ElementEntry underCursorElement = FindElementAt(windowPos);
  366. if (underCursorElement != null)
  367. {
  368. LibraryEntry entry = ProjectLibrary.GetEntry(underCursorElement.path);
  369. if (entry != null && entry.Type == LibraryEntryType.Directory)
  370. destinationFolder = Path.Combine(resourceDir, entry.Path);
  371. }
  372. if (paths != null)
  373. {
  374. foreach (var path in paths)
  375. {
  376. if (path == null)
  377. continue;
  378. string absolutePath = path;
  379. if (!Path.IsPathRooted(absolutePath))
  380. absolutePath = Path.Combine(resourceDir, path);
  381. if (string.IsNullOrEmpty(absolutePath))
  382. continue;
  383. if (PathEx.IsPartOf(destinationFolder, absolutePath) || PathEx.Compare(absolutePath, destinationFolder))
  384. continue;
  385. string destination = Path.Combine(destinationFolder, Path.GetFileName(absolutePath));
  386. if (ProjectLibrary.Exists(path))
  387. ProjectLibrary.Move(path, destination, true);
  388. else
  389. ProjectLibrary.Copy(path, destination, true);
  390. }
  391. }
  392. ClearHoverHighlight();
  393. autoScrollAmount = 0;
  394. }
  395. private void ClearHoverHighlight()
  396. {
  397. if (!string.IsNullOrEmpty(hoverHighlightPath))
  398. {
  399. ElementEntry previousUnderCursorElem;
  400. if (entryLookup.TryGetValue(hoverHighlightPath, out previousUnderCursorElem))
  401. previousUnderCursorElem.MarkAsHovered(false);
  402. }
  403. hoverHighlightPath = "";
  404. }
  405. public void Ping(Resource resource)
  406. {
  407. if (!string.IsNullOrEmpty(pingPath))
  408. {
  409. ElementEntry entry;
  410. if (entryLookup.TryGetValue(pingPath, out entry))
  411. entry.MarkAsPinged(false);
  412. }
  413. if (resource != null)
  414. pingPath = ProjectLibrary.GetPath(resource);
  415. else
  416. pingPath = "";
  417. if (!string.IsNullOrEmpty(pingPath))
  418. {
  419. ElementEntry entry;
  420. if (entryLookup.TryGetValue(pingPath, out entry))
  421. entry.MarkAsPinged(true);
  422. ScrollToEntry(pingPath);
  423. }
  424. }
  425. private void DeselectAll()
  426. {
  427. SetSelection(new List<string>());
  428. selectionAnchorStart = -1;
  429. selectionAnchorEnd = -1;
  430. }
  431. private void Select(string path)
  432. {
  433. ElementEntry entry;
  434. if (!entryLookup.TryGetValue(path, out entry))
  435. return;
  436. bool ctrlDown = Input.IsButtonHeld(ButtonCode.LeftControl) || Input.IsButtonHeld(ButtonCode.RightControl);
  437. bool shiftDown = Input.IsButtonHeld(ButtonCode.LeftShift) || Input.IsButtonHeld(ButtonCode.RightShift);
  438. if (shiftDown)
  439. {
  440. if (selectionAnchorStart != -1 && selectionAnchorStart < entries.Count)
  441. {
  442. int start = Math.Min(entry.index, selectionAnchorStart);
  443. int end = Math.Max(entry.index, selectionAnchorStart);
  444. List<string> newSelection = new List<string>();
  445. for(int i = start; i <= end; i++)
  446. newSelection.Add(entries[i].path);
  447. SetSelection(newSelection);
  448. selectionAnchorEnd = entry.index;
  449. }
  450. else
  451. {
  452. SetSelection(new List<string>() {path});
  453. selectionAnchorStart = entry.index;
  454. selectionAnchorEnd = entry.index;
  455. }
  456. }
  457. else if (ctrlDown)
  458. {
  459. List<string> newSelection = new List<string>(selectionPaths);
  460. if (selectionPaths.Contains(path))
  461. {
  462. newSelection.Remove(path);
  463. if (newSelection.Count == 0)
  464. DeselectAll();
  465. else
  466. {
  467. if (selectionAnchorStart == entry.index)
  468. {
  469. ElementEntry newAnchorEntry;
  470. if (!entryLookup.TryGetValue(newSelection[0], out newAnchorEntry))
  471. selectionAnchorStart = -1;
  472. else
  473. selectionAnchorStart = newAnchorEntry.index;
  474. }
  475. if (selectionAnchorEnd == entry.index)
  476. {
  477. ElementEntry newAnchorEntry;
  478. if (!entryLookup.TryGetValue(newSelection[newSelection.Count - 1], out newAnchorEntry))
  479. selectionAnchorEnd = -1;
  480. else
  481. selectionAnchorEnd = newAnchorEntry.index;
  482. }
  483. SetSelection(newSelection);
  484. }
  485. }
  486. else
  487. {
  488. newSelection.Add(path);
  489. SetSelection(newSelection);
  490. selectionAnchorEnd = entry.index;
  491. }
  492. }
  493. else
  494. {
  495. SetSelection(new List<string>() { path });
  496. selectionAnchorStart = entry.index;
  497. selectionAnchorEnd = entry.index;
  498. }
  499. }
  500. private void MoveSelection(MoveDirection dir)
  501. {
  502. string newPath = "";
  503. if (selectionPaths.Count == 0 || selectionAnchorEnd == -1)
  504. {
  505. // Nothing is selected so we arbitrarily select first or last element
  506. if (entries.Count > 0)
  507. {
  508. switch (dir)
  509. {
  510. case MoveDirection.Left:
  511. case MoveDirection.Up:
  512. newPath = entries[0].path;
  513. break;
  514. case MoveDirection.Right:
  515. case MoveDirection.Down:
  516. newPath = entries[entries.Count - 1].path;
  517. break;
  518. }
  519. }
  520. }
  521. else
  522. {
  523. switch (dir)
  524. {
  525. case MoveDirection.Left:
  526. if (selectionAnchorEnd - 1 > 0)
  527. newPath = entries[selectionAnchorEnd - 1].path;
  528. break;
  529. case MoveDirection.Up:
  530. if (selectionAnchorEnd - elementsPerRow > 0)
  531. newPath = entries[selectionAnchorEnd - elementsPerRow].path;
  532. break;
  533. case MoveDirection.Right:
  534. if (selectionAnchorEnd + 1 < entries.Count)
  535. newPath = entries[selectionAnchorEnd + 1].path;
  536. break;
  537. case MoveDirection.Down:
  538. if (selectionAnchorEnd + elementsPerRow > 0)
  539. newPath = entries[selectionAnchorEnd + elementsPerRow].path;
  540. break;
  541. }
  542. }
  543. if (!string.IsNullOrEmpty(newPath))
  544. {
  545. Select(newPath);
  546. ScrollToEntry(newPath);
  547. }
  548. }
  549. private void SetSelection(List<string> paths)
  550. {
  551. if (selectionPaths != null)
  552. {
  553. foreach (var path in selectionPaths)
  554. {
  555. ElementEntry entry;
  556. if (entryLookup.TryGetValue(path, out entry))
  557. entry.MarkAsSelected(false);
  558. }
  559. }
  560. selectionPaths = paths;
  561. if (selectionPaths != null)
  562. {
  563. foreach (var path in selectionPaths)
  564. {
  565. ElementEntry entry;
  566. if (entryLookup.TryGetValue(path, out entry))
  567. entry.MarkAsSelected(true);
  568. }
  569. }
  570. Ping(null);
  571. if (selectionPaths != null)
  572. Selection.resourcePaths = selectionPaths.ToArray();
  573. else
  574. Selection.resourcePaths = new string[0];
  575. }
  576. private void EnterDirectory(string directory)
  577. {
  578. currentDirectory = directory;
  579. DeselectAll();
  580. Refresh();
  581. }
  582. private void Cut(IEnumerable<string> sourcePaths)
  583. {
  584. foreach (var path in cutPaths)
  585. {
  586. ElementEntry entry;
  587. if (entryLookup.TryGetValue(path, out entry))
  588. entry.MarkAsCut(false);
  589. }
  590. cutPaths.Clear();
  591. cutPaths.AddRange(sourcePaths);
  592. foreach (var path in cutPaths)
  593. {
  594. ElementEntry entry;
  595. if (entryLookup.TryGetValue(path, out entry))
  596. entry.MarkAsCut(true);
  597. }
  598. copyPaths.Clear();
  599. }
  600. private void Copy(IEnumerable<string> sourcePaths)
  601. {
  602. copyPaths.Clear();
  603. copyPaths.AddRange(sourcePaths);
  604. foreach (var path in cutPaths)
  605. {
  606. ElementEntry entry;
  607. if (entryLookup.TryGetValue(path, out entry))
  608. entry.MarkAsCut(false);
  609. }
  610. cutPaths.Clear();
  611. }
  612. private void Duplicate(IEnumerable<string> sourcePaths)
  613. {
  614. foreach (var source in sourcePaths)
  615. {
  616. int idx = 0;
  617. string destination;
  618. do
  619. {
  620. destination = source + "_" + idx;
  621. idx++;
  622. } while (!ProjectLibrary.Exists(destination));
  623. ProjectLibrary.Copy(source, destination);
  624. }
  625. }
  626. private void Paste(string destinationFolder)
  627. {
  628. if (copyPaths.Count > 0)
  629. {
  630. for (int i = 0; i < copyPaths.Count; i++)
  631. {
  632. string destination = Path.Combine(destinationFolder, Path.GetFileName(copyPaths[i]));
  633. ProjectLibrary.Copy(copyPaths[i], destination, true);
  634. }
  635. Refresh();
  636. }
  637. else if (cutPaths.Count > 0)
  638. {
  639. for (int i = 0; i < cutPaths.Count; i++)
  640. {
  641. string destination = Path.Combine(destinationFolder, Path.GetFileName(cutPaths[i]));
  642. ProjectLibrary.Move(cutPaths[i], destination, true);
  643. }
  644. cutPaths.Clear();
  645. Refresh();
  646. }
  647. }
  648. private void EditorUpdate()
  649. {
  650. if (HasContentFocus)
  651. {
  652. if (Input.IsButtonHeld(ButtonCode.LeftControl) || Input.IsButtonHeld(ButtonCode.RightControl))
  653. {
  654. if (Input.IsButtonUp(ButtonCode.C))
  655. {
  656. CopySelection();
  657. }
  658. else if (Input.IsButtonUp(ButtonCode.X))
  659. {
  660. CutSelection();
  661. }
  662. else if (Input.IsButtonUp(ButtonCode.D))
  663. {
  664. DuplicateSelection();
  665. }
  666. else if (Input.IsButtonUp(ButtonCode.V))
  667. {
  668. PasteToSelection();
  669. }
  670. }
  671. if (Input.IsButtonDown(ButtonCode.Return))
  672. {
  673. if (selectionPaths.Count == 1)
  674. {
  675. LibraryEntry entry = ProjectLibrary.GetEntry(selectionPaths[0]);
  676. if (entry != null && entry.Type == LibraryEntryType.Directory)
  677. {
  678. EnterDirectory(entry.Path);
  679. }
  680. }
  681. }
  682. else if (Input.IsButtonDown(ButtonCode.Back))
  683. {
  684. LibraryEntry entry = ProjectLibrary.GetEntry(currentDirectory);
  685. if (entry != null && entry.Parent != null)
  686. {
  687. EnterDirectory(entry.Parent.Path);
  688. }
  689. }
  690. else if (Input.IsButtonDown(ButtonCode.Up))
  691. {
  692. MoveSelection(MoveDirection.Up);
  693. }
  694. else if (Input.IsButtonDown(ButtonCode.Down))
  695. {
  696. MoveSelection(MoveDirection.Down);
  697. }
  698. else if (Input.IsButtonDown(ButtonCode.Left))
  699. {
  700. MoveSelection(MoveDirection.Left);
  701. }
  702. else if (Input.IsButtonDown(ButtonCode.Right))
  703. {
  704. MoveSelection(MoveDirection.Right);
  705. }
  706. }
  707. if (autoScrollAmount != 0)
  708. {
  709. Rect2I contentBounds = contentScrollArea.ContentBounds;
  710. float scrollPct = autoScrollAmount / (float)contentBounds.height;
  711. contentScrollArea.VerticalScroll += scrollPct * Time.FrameDelta;
  712. }
  713. dropTarget.Update();
  714. }
  715. private void OnEntryChanged(string entry)
  716. {
  717. Refresh();
  718. }
  719. private void ScrollToEntry(string path)
  720. {
  721. ElementEntry entryGUI;
  722. if (!entryLookup.TryGetValue(path, out entryGUI))
  723. return;
  724. Rect2I entryBounds = entryGUI.Bounds;
  725. Rect2I contentBounds = contentScrollArea.Layout.Bounds;
  726. entryBounds.x += contentBounds.x;
  727. entryBounds.y += contentBounds.y;
  728. Rect2I scrollAreaBounds = scrollAreaPanel.Bounds;
  729. bool requiresScroll = entryBounds.y < scrollAreaBounds.y ||
  730. (entryBounds.y + entryBounds.height) > (scrollAreaBounds.y + scrollAreaBounds.height);
  731. if (!requiresScroll)
  732. return;
  733. float percent = (entryBounds.y - entryBounds.height * 0.5f - scrollAreaBounds.height * 0.5f) / contentBounds.height;
  734. percent = MathEx.Clamp01(percent);
  735. contentScrollArea.VerticalScroll = percent;
  736. }
  737. private void Refresh()
  738. {
  739. LibraryEntry[] entriesToDisplay = new LibraryEntry[0];
  740. if (IsSearchActive)
  741. {
  742. entriesToDisplay = ProjectLibrary.Search(searchQuery);
  743. }
  744. else
  745. {
  746. DirectoryEntry entry = ProjectLibrary.GetEntry(currentDirectory) as DirectoryEntry;
  747. if (entry == null)
  748. {
  749. currentDirectory = ProjectLibrary.Root.Path;
  750. entry = ProjectLibrary.GetEntry(currentDirectory) as DirectoryEntry;
  751. }
  752. if(entry != null)
  753. entriesToDisplay = entry.Children;
  754. }
  755. if (scrollAreaPanel != null)
  756. scrollAreaPanel.Destroy();
  757. entries.Clear();
  758. entryLookup.Clear();
  759. scrollAreaPanel = contentScrollArea.Layout.AddPanel();
  760. if (entriesToDisplay.Length == 0)
  761. return;
  762. ContentInfo contentInfo = new ContentInfo(this, viewType);
  763. Rect2I scrollBounds = contentScrollArea.Bounds;
  764. int availableWidth = scrollBounds.width;
  765. if (viewType == ProjectViewType.List16)
  766. {
  767. for (int i = 0; i < entriesToDisplay.Length; i++)
  768. {
  769. ElementEntry guiEntry = new ElementEntry(contentInfo, contentInfo.main, entriesToDisplay[i], i);
  770. entries.Add(guiEntry);
  771. entryLookup[guiEntry.path] = guiEntry;
  772. if (i != entriesToDisplay.Length - 1)
  773. contentInfo.main.AddSpace(LIST_ENTRY_SPACING);
  774. }
  775. contentInfo.main.AddFlexibleSpace();
  776. elementsPerRow = 1;
  777. }
  778. else
  779. {
  780. int tileSize = 64;
  781. switch (viewType)
  782. {
  783. case ProjectViewType.Grid64: tileSize = 64; break;
  784. case ProjectViewType.Grid48: tileSize = 48; break;
  785. case ProjectViewType.Grid32: tileSize = 32; break;
  786. }
  787. GUILayoutX rowLayout = contentInfo.main.AddLayoutX();
  788. rowLayout.AddFlexibleSpace();
  789. int elemSize = tileSize + GRID_ENTRY_SPACING;
  790. elementsPerRow = (availableWidth - GRID_ENTRY_SPACING * 2) / elemSize;
  791. int numRows = MathEx.CeilToInt(entriesToDisplay.Length / (float)elementsPerRow);
  792. int neededHeight = numRows*(elemSize);
  793. bool requiresScrollbar = neededHeight > scrollBounds.height;
  794. if (requiresScrollbar)
  795. {
  796. availableWidth -= contentScrollArea.ScrollBarWidth;
  797. elementsPerRow = (availableWidth - GRID_ENTRY_SPACING * 2) / elemSize;
  798. }
  799. int elemsInRow = 0;
  800. for (int i = 0; i < entriesToDisplay.Length; i++)
  801. {
  802. if (elemsInRow == elementsPerRow && elemsInRow > 0)
  803. {
  804. rowLayout = contentInfo.main.AddLayoutX();
  805. contentInfo.main.AddSpace(GRID_ENTRY_SPACING);
  806. rowLayout.AddFlexibleSpace();
  807. elemsInRow = 0;
  808. }
  809. ElementEntry guiEntry = new ElementEntry(contentInfo, rowLayout, entriesToDisplay[i], i);
  810. entries.Add(guiEntry);
  811. entryLookup[guiEntry.path] = guiEntry;
  812. rowLayout.AddFlexibleSpace();
  813. elemsInRow++;
  814. }
  815. int extraElements = elementsPerRow - elemsInRow;
  816. for (int i = 0; i < extraElements; i++)
  817. {
  818. rowLayout.AddSpace(tileSize);
  819. rowLayout.AddFlexibleSpace();
  820. }
  821. contentInfo.main.AddFlexibleSpace();
  822. }
  823. for (int i = 0; i < entries.Count; i++)
  824. {
  825. ElementEntry guiEntry = entries[i];
  826. guiEntry.Initialize();
  827. if (cutPaths.Contains(guiEntry.path))
  828. guiEntry.MarkAsCut(true);
  829. if (selectionPaths.Contains(guiEntry.path))
  830. guiEntry.MarkAsSelected(true);
  831. else if (pingPath == guiEntry.path)
  832. guiEntry.MarkAsPinged(true);
  833. }
  834. Rect2I contentBounds = contentInfo.main.Bounds;
  835. Rect2I minimalBounds = GetScrollAreaBounds();
  836. contentBounds.height = Math.Max(contentBounds.height, minimalBounds.height);
  837. GUIButton catchAll = new GUIButton("", EditorStyles.Blank);
  838. catchAll.Bounds = contentBounds;
  839. catchAll.OnClick += OnCatchAllClicked;
  840. catchAll.SetContextMenu(entryContextMenu);
  841. catchAll.OnFocusChanged += OnContentsFocusChanged;
  842. contentInfo.underlay.AddElement(catchAll);
  843. RefreshDirectoryBar();
  844. }
  845. private void RefreshDirectoryBar()
  846. {
  847. if (folderListLayout != null)
  848. {
  849. folderListLayout.Destroy();
  850. folderListLayout = null;
  851. }
  852. folderListLayout = folderBarLayout.AddLayoutX();
  853. string[] folders = null;
  854. string[] fullPaths = null;
  855. if (IsSearchActive)
  856. {
  857. folders = new[] {searchQuery};
  858. fullPaths = new[] {""};
  859. }
  860. else
  861. {
  862. folders = currentDirectory.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar },
  863. StringSplitOptions.RemoveEmptyEntries);
  864. fullPaths = new string[folders.Length];
  865. }
  866. for (int i = 0; i < folders.Length; i++)
  867. {
  868. if (i == 0)
  869. fullPaths[i] = folders[i];
  870. else
  871. fullPaths[i] = Path.Combine(fullPaths[i - 1], folders[i]);
  872. }
  873. int availableWidth = folderBarLayout.Bounds.width - FOLDER_BUTTON_WIDTH * 2;
  874. int numFolders = 0;
  875. for (int i = folders.Length - 1; i >= 0; i--)
  876. {
  877. GUIButton folderButton = new GUIButton(folders[i]);
  878. if (!IsSearchActive)
  879. {
  880. string fullPath = fullPaths[i];
  881. folderButton.OnClick += () => OnFolderButtonClicked(fullPath);
  882. }
  883. GUILabel separator = new GUILabel("/", GUIOption.FixedWidth(FOLDER_SEPARATOR_WIDTH));
  884. folderListLayout.InsertElement(0, separator);
  885. folderListLayout.InsertElement(0, folderButton);
  886. numFolders++;
  887. Rect2I folderListBounds = folderListLayout.Bounds;
  888. if (folderListBounds.width > availableWidth)
  889. {
  890. if (numFolders > 2)
  891. {
  892. separator.Destroy();
  893. folderButton.Destroy();
  894. break;
  895. }
  896. }
  897. }
  898. }
  899. private void OnFolderButtonClicked(string path)
  900. {
  901. EnterDirectory(path);
  902. }
  903. private void OnContentsFocusChanged(bool focus)
  904. {
  905. hasContentFocus = focus;
  906. }
  907. private void OnEntryClicked(string path)
  908. {
  909. Select(path);
  910. }
  911. private void OnEntryDoubleClicked(string path)
  912. {
  913. LibraryEntry entry = ProjectLibrary.GetEntry(path);
  914. if (entry != null && entry.Type == LibraryEntryType.Directory)
  915. {
  916. EnterDirectory(path);
  917. }
  918. }
  919. private void OnCatchAllClicked()
  920. {
  921. DeselectAll();
  922. }
  923. private void OnHomeClicked()
  924. {
  925. currentDirectory = ProjectLibrary.Root.Path;
  926. Refresh();
  927. }
  928. private void OnUpClicked()
  929. {
  930. string parent = Path.GetDirectoryName(currentDirectory);
  931. if (!string.IsNullOrEmpty(parent))
  932. {
  933. currentDirectory = parent;
  934. Refresh();
  935. }
  936. }
  937. private void CutSelection()
  938. {
  939. if (selectionPaths.Count > 0)
  940. Cut(selectionPaths);
  941. }
  942. private void CopySelection()
  943. {
  944. if (selectionPaths.Count > 0)
  945. Copy(selectionPaths);
  946. }
  947. private void DuplicateSelection()
  948. {
  949. if (selectionPaths.Count > 0)
  950. Duplicate(selectionPaths);
  951. }
  952. private void PasteToSelection()
  953. {
  954. DirectoryEntry selectedDirectory = null;
  955. if (selectionPaths.Count == 1)
  956. {
  957. LibraryEntry entry = ProjectLibrary.GetEntry(selectionPaths[0]);
  958. if (entry != null && entry.Type == LibraryEntryType.Directory)
  959. selectedDirectory = (DirectoryEntry) entry;
  960. }
  961. if(selectedDirectory != null)
  962. Paste(selectedDirectory.Path);
  963. else
  964. Paste(currentDirectory);
  965. }
  966. private void OnSearchChanged(string newValue)
  967. {
  968. searchQuery = newValue;
  969. Refresh();
  970. }
  971. private void ClearSearch()
  972. {
  973. searchQuery = "";
  974. Refresh();
  975. }
  976. private void OpenOptionsWindow()
  977. {
  978. Vector2I openPosition;
  979. Rect2I buttonBounds = GUILayoutUtility.CalculateBounds(optionsButton, GUI);
  980. openPosition.x = buttonBounds.x + buttonBounds.width / 2;
  981. openPosition.y = buttonBounds.y + buttonBounds.height / 2;
  982. ProjectDropDown dropDown = DropDownWindow.Open<ProjectDropDown>(this, openPosition);
  983. dropDown.SetParent(this);
  984. }
  985. private void Reset()
  986. {
  987. currentDirectory = ProjectLibrary.Root.Path;
  988. selectionAnchorStart = -1;
  989. selectionAnchorEnd = -1;
  990. selectionPaths.Clear();
  991. pingPath = "";
  992. hoverHighlightPath = "";
  993. Refresh();
  994. }
  995. private Rect2I GetScrollAreaBounds()
  996. {
  997. Rect2I bounds = GUI.Bounds;
  998. Rect2I searchBarBounds = searchBarLayout.Bounds;
  999. bounds.y += searchBarBounds.height;
  1000. bounds.height -= searchBarBounds.height;
  1001. return bounds;
  1002. }
  1003. protected override void WindowResized(int width, int height)
  1004. {
  1005. base.WindowResized(width, height);
  1006. Refresh();
  1007. dropTarget.Bounds = contentScrollArea.Bounds;
  1008. }
  1009. }
  1010. internal class ProjectDropDown : DropDownWindow
  1011. {
  1012. private ProjectWindow parent;
  1013. public ProjectDropDown()
  1014. :base(150, 30)
  1015. { }
  1016. internal void SetParent(ProjectWindow parent)
  1017. {
  1018. this.parent = parent;
  1019. GUIToggleGroup group = new GUIToggleGroup();
  1020. GUIToggle list16 = new GUIToggle("16", group, EditorStyles.Button, GUIOption.FixedWidth(30));
  1021. GUIToggle grid32 = new GUIToggle("32", group, EditorStyles.Button, GUIOption.FixedWidth(30));
  1022. GUIToggle grid48 = new GUIToggle("48", group, EditorStyles.Button, GUIOption.FixedWidth(30));
  1023. GUIToggle grid64 = new GUIToggle("64", group, EditorStyles.Button, GUIOption.FixedWidth(30));
  1024. ProjectViewType activeType = parent.ViewType;
  1025. switch (activeType)
  1026. {
  1027. case ProjectViewType.List16:
  1028. list16.ToggleOn();
  1029. break;
  1030. case ProjectViewType.Grid32:
  1031. grid32.ToggleOn();
  1032. break;
  1033. case ProjectViewType.Grid48:
  1034. grid48.ToggleOn();
  1035. break;
  1036. case ProjectViewType.Grid64:
  1037. grid64.ToggleOn();
  1038. break;
  1039. }
  1040. list16.OnToggled += (active) =>
  1041. {
  1042. if (active)
  1043. ChangeViewType(ProjectViewType.List16);
  1044. };
  1045. grid32.OnToggled += (active) =>
  1046. {
  1047. if (active)
  1048. ChangeViewType(ProjectViewType.Grid32);
  1049. };
  1050. grid48.OnToggled += (active) =>
  1051. {
  1052. if (active)
  1053. ChangeViewType(ProjectViewType.Grid48);
  1054. };
  1055. grid64.OnToggled += (active) =>
  1056. {
  1057. if (active)
  1058. ChangeViewType(ProjectViewType.Grid64);
  1059. };
  1060. GUILayoutY vertLayout = GUI.AddLayoutY();
  1061. vertLayout.AddFlexibleSpace();
  1062. GUILayoutX contentLayout = vertLayout.AddLayoutX();
  1063. contentLayout.AddFlexibleSpace();
  1064. contentLayout.AddElement(list16);
  1065. contentLayout.AddElement(grid32);
  1066. contentLayout.AddElement(grid48);
  1067. contentLayout.AddElement(grid64);
  1068. contentLayout.AddFlexibleSpace();
  1069. vertLayout.AddFlexibleSpace();
  1070. }
  1071. private void ChangeViewType(ProjectViewType viewType)
  1072. {
  1073. parent.ViewType = viewType;
  1074. }
  1075. }
  1076. }