ProjectWindow.cs 43 KB

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