ProjectWindow.cs 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297
  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[entries.Count - 1].path;
  529. break;
  530. case MoveDirection.Right:
  531. case MoveDirection.Down:
  532. newPath = entries[0].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 < entries.Count)
  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. Rect2I windowEntryBounds = entryBounds;
  743. windowEntryBounds.x += contentBounds.x;
  744. windowEntryBounds.y += contentBounds.y;
  745. Rect2I scrollAreaBounds = contentScrollArea.Bounds;
  746. bool requiresScroll = windowEntryBounds.y < scrollAreaBounds.y ||
  747. (windowEntryBounds.y + windowEntryBounds.height) > (scrollAreaBounds.y + scrollAreaBounds.height);
  748. if (!requiresScroll)
  749. return;
  750. int scrollableSize = contentBounds.height - scrollAreaBounds.height;
  751. float percent = (((entryBounds.y + entryBounds.height * 0.5f) - scrollAreaBounds.height * 0.5f) / (float)scrollableSize);
  752. percent = MathEx.Clamp01(percent);
  753. contentScrollArea.VerticalScroll = percent;
  754. }
  755. private void Refresh()
  756. {
  757. LibraryEntry[] entriesToDisplay = new LibraryEntry[0];
  758. if (IsSearchActive)
  759. {
  760. entriesToDisplay = ProjectLibrary.Search(searchQuery);
  761. }
  762. else
  763. {
  764. DirectoryEntry entry = ProjectLibrary.GetEntry(currentDirectory) as DirectoryEntry;
  765. if (entry == null)
  766. {
  767. currentDirectory = ProjectLibrary.Root.Path;
  768. entry = ProjectLibrary.GetEntry(currentDirectory) as DirectoryEntry;
  769. }
  770. if(entry != null)
  771. entriesToDisplay = entry.Children;
  772. }
  773. if (scrollAreaPanel != null)
  774. scrollAreaPanel.Destroy();
  775. entries.Clear();
  776. entryLookup.Clear();
  777. scrollAreaPanel = contentScrollArea.Layout.AddPanel();
  778. if (entriesToDisplay.Length == 0)
  779. return;
  780. contentInfo = new ContentInfo(this, viewType, entriesToDisplay.Length);
  781. if (viewType == ProjectViewType.List16)
  782. {
  783. for (int i = 0; i < entriesToDisplay.Length; i++)
  784. {
  785. ElementEntry guiEntry = new ElementEntry(contentInfo, contentInfo.main, entriesToDisplay[i], i);
  786. entries.Add(guiEntry);
  787. entryLookup[guiEntry.path] = guiEntry;
  788. if (i != entriesToDisplay.Length - 1)
  789. contentInfo.main.AddSpace(LIST_ENTRY_SPACING);
  790. }
  791. contentInfo.main.AddFlexibleSpace();
  792. }
  793. else
  794. {
  795. int tileSize = 64;
  796. switch (viewType)
  797. {
  798. case ProjectViewType.Grid64: tileSize = 64; break;
  799. case ProjectViewType.Grid48: tileSize = 48; break;
  800. case ProjectViewType.Grid32: tileSize = 32; break;
  801. }
  802. contentInfo.main.AddSpace(GRID_ENTRY_SPACING / 2);
  803. GUILayoutX rowLayout = contentInfo.main.AddLayoutX();
  804. contentInfo.main.AddSpace(GRID_ENTRY_SPACING);
  805. rowLayout.AddFlexibleSpace();
  806. int elemsInRow = 0;
  807. for (int i = 0; i < entriesToDisplay.Length; i++)
  808. {
  809. if (elemsInRow == contentInfo.elementsPerRow && elemsInRow > 0)
  810. {
  811. rowLayout = contentInfo.main.AddLayoutX();
  812. contentInfo.main.AddSpace(GRID_ENTRY_SPACING);
  813. rowLayout.AddFlexibleSpace();
  814. elemsInRow = 0;
  815. }
  816. ElementEntry guiEntry = new ElementEntry(contentInfo, rowLayout, entriesToDisplay[i], i);
  817. entries.Add(guiEntry);
  818. entryLookup[guiEntry.path] = guiEntry;
  819. rowLayout.AddFlexibleSpace();
  820. elemsInRow++;
  821. }
  822. int extraElements = contentInfo.elementsPerRow - elemsInRow;
  823. for (int i = 0; i < extraElements; i++)
  824. {
  825. rowLayout.AddSpace(contentInfo.labelWidth);
  826. rowLayout.AddFlexibleSpace();
  827. }
  828. contentInfo.main.AddFlexibleSpace();
  829. }
  830. for (int i = 0; i < entries.Count; i++)
  831. {
  832. ElementEntry guiEntry = entries[i];
  833. guiEntry.Initialize();
  834. if (cutPaths.Contains(guiEntry.path))
  835. guiEntry.MarkAsCut(true);
  836. if (selectionPaths.Contains(guiEntry.path))
  837. guiEntry.MarkAsSelected(true);
  838. else if (pingPath == guiEntry.path)
  839. guiEntry.MarkAsPinged(true);
  840. }
  841. Rect2I contentBounds = contentInfo.main.Bounds;
  842. Rect2I minimalBounds = GetScrollAreaBounds();
  843. contentBounds.height = Math.Max(contentBounds.height, minimalBounds.height);
  844. GUIButton catchAll = new GUIButton("", EditorStyles.Blank);
  845. catchAll.Bounds = contentBounds;
  846. catchAll.OnClick += OnCatchAllClicked;
  847. catchAll.SetContextMenu(entryContextMenu);
  848. catchAll.OnFocusChanged += OnContentsFocusChanged;
  849. contentInfo.underlay.AddElement(catchAll);
  850. RefreshDirectoryBar();
  851. }
  852. private void RefreshDirectoryBar()
  853. {
  854. if (folderListLayout != null)
  855. {
  856. folderListLayout.Destroy();
  857. folderListLayout = null;
  858. }
  859. folderListLayout = folderBarLayout.AddLayoutX();
  860. string[] folders = null;
  861. string[] fullPaths = null;
  862. if (IsSearchActive)
  863. {
  864. folders = new[] {searchQuery};
  865. fullPaths = new[] {""};
  866. }
  867. else
  868. {
  869. folders = currentDirectory.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar },
  870. StringSplitOptions.RemoveEmptyEntries);
  871. fullPaths = new string[folders.Length];
  872. }
  873. for (int i = 0; i < folders.Length; i++)
  874. {
  875. if (i == 0)
  876. fullPaths[i] = folders[i];
  877. else
  878. fullPaths[i] = Path.Combine(fullPaths[i - 1], folders[i]);
  879. }
  880. int availableWidth = folderBarLayout.Bounds.width - FOLDER_BUTTON_WIDTH * 2;
  881. int numFolders = 0;
  882. for (int i = folders.Length - 1; i >= 0; i--)
  883. {
  884. GUIButton folderButton = new GUIButton(folders[i]);
  885. if (!IsSearchActive)
  886. {
  887. string fullPath = fullPaths[i];
  888. folderButton.OnClick += () => OnFolderButtonClicked(fullPath);
  889. }
  890. GUILabel separator = new GUILabel("/", GUIOption.FixedWidth(FOLDER_SEPARATOR_WIDTH));
  891. folderListLayout.InsertElement(0, separator);
  892. folderListLayout.InsertElement(0, folderButton);
  893. numFolders++;
  894. Rect2I folderListBounds = folderListLayout.Bounds;
  895. if (folderListBounds.width > availableWidth)
  896. {
  897. if (numFolders > 2)
  898. {
  899. separator.Destroy();
  900. folderButton.Destroy();
  901. break;
  902. }
  903. }
  904. }
  905. }
  906. private void OnFolderButtonClicked(string path)
  907. {
  908. EnterDirectory(path);
  909. }
  910. private void OnContentsFocusChanged(bool focus)
  911. {
  912. hasContentFocus = focus;
  913. }
  914. private void OnEntryClicked(string path)
  915. {
  916. Select(path);
  917. }
  918. private void OnEntryDoubleClicked(string path)
  919. {
  920. LibraryEntry entry = ProjectLibrary.GetEntry(path);
  921. if (entry != null && entry.Type == LibraryEntryType.Directory)
  922. {
  923. EnterDirectory(path);
  924. }
  925. }
  926. private void OnCatchAllClicked()
  927. {
  928. DeselectAll();
  929. }
  930. private void OnHomeClicked()
  931. {
  932. currentDirectory = ProjectLibrary.Root.Path;
  933. Refresh();
  934. }
  935. private void OnUpClicked()
  936. {
  937. string parent = Path.GetDirectoryName(currentDirectory);
  938. if (!string.IsNullOrEmpty(parent))
  939. {
  940. currentDirectory = parent;
  941. Refresh();
  942. }
  943. }
  944. private void CutSelection()
  945. {
  946. if (selectionPaths.Count > 0)
  947. Cut(selectionPaths);
  948. }
  949. private void CopySelection()
  950. {
  951. if (selectionPaths.Count > 0)
  952. Copy(selectionPaths);
  953. }
  954. private void DuplicateSelection()
  955. {
  956. if (selectionPaths.Count > 0)
  957. Duplicate(selectionPaths);
  958. }
  959. private void PasteToSelection()
  960. {
  961. DirectoryEntry selectedDirectory = null;
  962. if (selectionPaths.Count == 1)
  963. {
  964. LibraryEntry entry = ProjectLibrary.GetEntry(selectionPaths[0]);
  965. if (entry != null && entry.Type == LibraryEntryType.Directory)
  966. selectedDirectory = (DirectoryEntry) entry;
  967. }
  968. if(selectedDirectory != null)
  969. Paste(selectedDirectory.Path);
  970. else
  971. Paste(currentDirectory);
  972. }
  973. private void OnSearchChanged(string newValue)
  974. {
  975. searchQuery = newValue;
  976. Refresh();
  977. }
  978. private void ClearSearch()
  979. {
  980. searchQuery = "";
  981. Refresh();
  982. }
  983. private void OpenOptionsWindow()
  984. {
  985. Vector2I openPosition;
  986. Rect2I buttonBounds = GUILayoutUtility.CalculateBounds(optionsButton, GUI);
  987. openPosition.x = buttonBounds.x + buttonBounds.width / 2;
  988. openPosition.y = buttonBounds.y + buttonBounds.height / 2;
  989. ProjectDropDown dropDown = DropDownWindow.Open<ProjectDropDown>(this, openPosition);
  990. dropDown.SetParent(this);
  991. }
  992. private void Reset()
  993. {
  994. currentDirectory = ProjectLibrary.Root.Path;
  995. selectionAnchorStart = -1;
  996. selectionAnchorEnd = -1;
  997. selectionPaths.Clear();
  998. pingPath = "";
  999. hoverHighlightPath = "";
  1000. Refresh();
  1001. }
  1002. private Rect2I GetScrollAreaBounds()
  1003. {
  1004. Rect2I bounds = GUI.Bounds;
  1005. Rect2I searchBarBounds = searchBarLayout.Bounds;
  1006. bounds.y += searchBarBounds.height;
  1007. bounds.height -= searchBarBounds.height;
  1008. return bounds;
  1009. }
  1010. protected override void WindowResized(int width, int height)
  1011. {
  1012. base.WindowResized(width, height);
  1013. Refresh();
  1014. dropTarget.Bounds = contentScrollArea.Bounds;
  1015. }
  1016. }
  1017. internal class ProjectDropDown : DropDownWindow
  1018. {
  1019. private ProjectWindow parent;
  1020. public ProjectDropDown()
  1021. :base(150, 30)
  1022. { }
  1023. internal void SetParent(ProjectWindow parent)
  1024. {
  1025. this.parent = parent;
  1026. GUIToggleGroup group = new GUIToggleGroup();
  1027. GUIToggle list16 = new GUIToggle("16", group, EditorStyles.Button, GUIOption.FixedWidth(30));
  1028. GUIToggle grid32 = new GUIToggle("32", group, EditorStyles.Button, GUIOption.FixedWidth(30));
  1029. GUIToggle grid48 = new GUIToggle("48", group, EditorStyles.Button, GUIOption.FixedWidth(30));
  1030. GUIToggle grid64 = new GUIToggle("64", group, EditorStyles.Button, GUIOption.FixedWidth(30));
  1031. ProjectViewType activeType = parent.ViewType;
  1032. switch (activeType)
  1033. {
  1034. case ProjectViewType.List16:
  1035. list16.ToggleOn();
  1036. break;
  1037. case ProjectViewType.Grid32:
  1038. grid32.ToggleOn();
  1039. break;
  1040. case ProjectViewType.Grid48:
  1041. grid48.ToggleOn();
  1042. break;
  1043. case ProjectViewType.Grid64:
  1044. grid64.ToggleOn();
  1045. break;
  1046. }
  1047. list16.OnToggled += (active) =>
  1048. {
  1049. if (active)
  1050. ChangeViewType(ProjectViewType.List16);
  1051. };
  1052. grid32.OnToggled += (active) =>
  1053. {
  1054. if (active)
  1055. ChangeViewType(ProjectViewType.Grid32);
  1056. };
  1057. grid48.OnToggled += (active) =>
  1058. {
  1059. if (active)
  1060. ChangeViewType(ProjectViewType.Grid48);
  1061. };
  1062. grid64.OnToggled += (active) =>
  1063. {
  1064. if (active)
  1065. ChangeViewType(ProjectViewType.Grid64);
  1066. };
  1067. GUILayoutY vertLayout = GUI.AddLayoutY();
  1068. vertLayout.AddFlexibleSpace();
  1069. GUILayoutX contentLayout = vertLayout.AddLayoutX();
  1070. contentLayout.AddFlexibleSpace();
  1071. contentLayout.AddElement(list16);
  1072. contentLayout.AddElement(grid32);
  1073. contentLayout.AddElement(grid48);
  1074. contentLayout.AddElement(grid64);
  1075. contentLayout.AddFlexibleSpace();
  1076. vertLayout.AddFlexibleSpace();
  1077. }
  1078. private void ChangeViewType(ProjectViewType viewType)
  1079. {
  1080. parent.ViewType = viewType;
  1081. }
  1082. }
  1083. }