LibraryWindow.cs 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using BansheeEngine;
  5. namespace BansheeEditor
  6. {
  7. /// <summary>
  8. /// Types of resource tile display in the library window.
  9. /// </summary>
  10. internal enum ProjectViewType
  11. {
  12. Grid64, Grid48, Grid32, List16
  13. }
  14. /// <summary>
  15. /// Editor window that displays all resources in the project. Resources can be displayed as a grid or list of icons,
  16. /// with the ability to move, cut, copy, paste resources and folders, as well as supporting drag and drop and search
  17. /// operations.
  18. /// </summary>
  19. internal sealed class LibraryWindow : EditorWindow
  20. {
  21. /// <summary>
  22. /// Directions the selection cursor in library window can be moved in.
  23. /// </summary>
  24. internal enum MoveDirection
  25. {
  26. Up, Down, Left, Right
  27. }
  28. private const int DRAG_SCROLL_HEIGHT = 20;
  29. private const int DRAG_SCROLL_AMOUNT_PER_SECOND = 100;
  30. private const int FOLDER_BUTTON_WIDTH = 20;
  31. private const int FOLDER_SEPARATOR_WIDTH = 10;
  32. private bool hasContentFocus = false;
  33. private bool HasContentFocus { get { return HasFocus && hasContentFocus; } }
  34. private string searchQuery;
  35. private bool IsSearchActive { get { return !string.IsNullOrEmpty(searchQuery); } }
  36. private ProjectViewType viewType = ProjectViewType.Grid32;
  37. private bool requiresRefresh;
  38. private string currentDirectory = "";
  39. private List<string> selectionPaths = new List<string>();
  40. private int selectionAnchorStart = -1;
  41. private int selectionAnchorEnd = -1;
  42. private string pingPath = "";
  43. private string hoverHighlightPath = "";
  44. private LibraryGUIContent content;
  45. private GUIScrollArea contentScrollArea;
  46. private GUILayoutX searchBarLayout;
  47. private GUIButton optionsButton;
  48. private GUILayout folderBarLayout;
  49. private GUILayout folderListLayout;
  50. private GUITextField searchField;
  51. private GUITexture dragSelection;
  52. private ContextMenu entryContextMenu;
  53. private LibraryDropTarget dropTarget;
  54. private int autoScrollAmount;
  55. private bool isDraggingSelection;
  56. private Vector2I dragSelectionStart;
  57. private Vector2I dragSelectionEnd;
  58. private LibraryGUIEntry inProgressRenameElement;
  59. // Cut/Copy/Paste
  60. private List<string> copyPaths = new List<string>();
  61. private List<string> cutPaths = new List<string>();
  62. /// <summary>
  63. /// Determines how to display resource tiles in the library window.
  64. /// </summary>
  65. internal ProjectViewType ViewType
  66. {
  67. get { return viewType; }
  68. set { viewType = value; Refresh(); }
  69. }
  70. /// <summary>
  71. /// Returns a file or folder currently selected in the library window. If nothing is selected, returns the active
  72. /// folder. Returned path is relative to project library resources folder.
  73. /// </summary>
  74. public string SelectedEntry
  75. {
  76. get
  77. {
  78. if (selectionPaths.Count == 1)
  79. {
  80. LibraryEntry entry = ProjectLibrary.GetEntry(selectionPaths[0]);
  81. if (entry != null)
  82. return entry.Path;
  83. }
  84. return currentDirectory;
  85. }
  86. }
  87. /// <summary>
  88. /// Returns a folder currently selected in the library window. If no folder is selected, returns the active
  89. /// folder. Returned path is relative to project library resources folder.
  90. /// </summary>
  91. public string SelectedFolder
  92. {
  93. get
  94. {
  95. DirectoryEntry selectedDirectory = null;
  96. if (selectionPaths.Count == 1)
  97. {
  98. LibraryEntry entry = ProjectLibrary.GetEntry(selectionPaths[0]);
  99. if (entry != null && entry.Type == LibraryEntryType.Directory)
  100. selectedDirectory = (DirectoryEntry) entry;
  101. }
  102. if (selectedDirectory != null)
  103. return selectedDirectory.Path;
  104. return currentDirectory;
  105. }
  106. }
  107. /// <summary>
  108. /// Returns the path to the folder currently displayed in the library window. Returned path is relative to project
  109. /// library resources folder.
  110. /// </summary>
  111. public string CurrentFolder
  112. {
  113. get { return currentDirectory; }
  114. }
  115. /// <summary>
  116. /// Context menu that should open when user right clicks on the content area.
  117. /// </summary>
  118. internal ContextMenu ContextMenu
  119. {
  120. get { return entryContextMenu; }
  121. }
  122. /// <summary>
  123. /// Opens the library window if not already open.
  124. /// </summary>
  125. [MenuItem("Windows/Library", ButtonModifier.CtrlAlt, ButtonCode.L, 6000)]
  126. private static void OpenLibraryWindow()
  127. {
  128. OpenWindow<LibraryWindow>();
  129. }
  130. private void OnInitialize()
  131. {
  132. ProjectLibrary.OnEntryAdded += OnEntryChanged;
  133. ProjectLibrary.OnEntryRemoved += OnEntryChanged;
  134. GUILayoutY contentLayout = GUI.AddLayoutY();
  135. searchBarLayout = contentLayout.AddLayoutX();
  136. searchField = new GUITextField();
  137. searchField.OnChanged += OnSearchChanged;
  138. GUIButton clearSearchBtn = new GUIButton("C");
  139. clearSearchBtn.OnClick += ClearSearch;
  140. clearSearchBtn.SetWidth(40);
  141. optionsButton = new GUIButton("O");
  142. optionsButton.OnClick += OpenOptionsWindow;
  143. optionsButton.SetWidth(40);
  144. searchBarLayout.AddElement(searchField);
  145. searchBarLayout.AddElement(clearSearchBtn);
  146. searchBarLayout.AddElement(optionsButton);
  147. folderBarLayout = contentLayout.AddLayoutX();
  148. GUIButton homeButton = new GUIButton("H", GUIOption.FixedWidth(FOLDER_BUTTON_WIDTH));
  149. homeButton.OnClick += OnHomeClicked;
  150. GUIButton upButton = new GUIButton("U", GUIOption.FixedWidth(FOLDER_BUTTON_WIDTH));
  151. upButton.OnClick += OnUpClicked;
  152. folderBarLayout.AddElement(homeButton);
  153. folderBarLayout.AddElement(upButton);
  154. folderBarLayout.AddSpace(10);
  155. contentScrollArea = new GUIScrollArea(GUIOption.FlexibleWidth(), GUIOption.FlexibleHeight());
  156. contentLayout.AddElement(contentScrollArea);
  157. contentLayout.AddFlexibleSpace();
  158. entryContextMenu = LibraryMenu.CreateContextMenu(this);
  159. content = new LibraryGUIContent(this, contentScrollArea);
  160. Reset();
  161. dropTarget = new LibraryDropTarget(this);
  162. dropTarget.Bounds = contentScrollArea.Bounds;
  163. dropTarget.OnStart += OnDragStart;
  164. dropTarget.OnDrag += OnDragMove;
  165. dropTarget.OnLeave += OnDragLeave;
  166. dropTarget.OnDropResource += OnResourceDragDropped;
  167. dropTarget.OnDropSceneObject += OnSceneObjectDragDropped;
  168. dropTarget.OnEnd += OnDragEnd;
  169. Selection.OnSelectionChanged += OnSelectionChanged;
  170. Selection.OnResourcePing += OnPing;
  171. }
  172. private void OnDestroy()
  173. {
  174. Selection.OnSelectionChanged -= OnSelectionChanged;
  175. Selection.OnResourcePing -= OnPing;
  176. }
  177. private void OnEditorUpdate()
  178. {
  179. bool isRenameInProgress = inProgressRenameElement != null;
  180. if (HasContentFocus)
  181. {
  182. if (!isRenameInProgress)
  183. {
  184. if (Input.IsButtonHeld(ButtonCode.LeftControl) || Input.IsButtonHeld(ButtonCode.RightControl))
  185. {
  186. if (Input.IsButtonUp(ButtonCode.C))
  187. {
  188. CopySelection();
  189. }
  190. else if (Input.IsButtonUp(ButtonCode.X))
  191. {
  192. CutSelection();
  193. }
  194. else if (Input.IsButtonUp(ButtonCode.D))
  195. {
  196. DuplicateSelection();
  197. }
  198. else if (Input.IsButtonUp(ButtonCode.V))
  199. {
  200. PasteToSelection();
  201. }
  202. }
  203. if (Input.IsButtonDown(ButtonCode.Return))
  204. {
  205. if (selectionPaths.Count == 1)
  206. {
  207. LibraryEntry entry = ProjectLibrary.GetEntry(selectionPaths[0]);
  208. if (entry != null && entry.Type == LibraryEntryType.Directory)
  209. {
  210. EnterDirectory(entry.Path);
  211. }
  212. }
  213. }
  214. else if (Input.IsButtonDown(ButtonCode.Back))
  215. {
  216. LibraryEntry entry = ProjectLibrary.GetEntry(currentDirectory);
  217. if (entry != null && entry.Parent != null)
  218. {
  219. EnterDirectory(entry.Parent.Path);
  220. }
  221. }
  222. else if (Input.IsButtonDown(ButtonCode.Up))
  223. {
  224. MoveSelection(MoveDirection.Up);
  225. }
  226. else if (Input.IsButtonDown(ButtonCode.Down))
  227. {
  228. MoveSelection(MoveDirection.Down);
  229. }
  230. else if (Input.IsButtonDown(ButtonCode.Left))
  231. {
  232. MoveSelection(MoveDirection.Left);
  233. }
  234. else if (Input.IsButtonDown(ButtonCode.Right))
  235. {
  236. MoveSelection(MoveDirection.Right);
  237. }
  238. else if (Input.IsButtonDown(ButtonCode.F2))
  239. {
  240. RenameSelection();
  241. }
  242. else if (Input.IsButtonDown(ButtonCode.Delete))
  243. {
  244. DeleteSelection();
  245. }
  246. }
  247. else
  248. {
  249. if (Input.IsButtonDown(ButtonCode.Return))
  250. {
  251. string newName = inProgressRenameElement.GetRenamedName();
  252. string originalPath = inProgressRenameElement.path;
  253. originalPath = originalPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
  254. string newPath = Path.GetDirectoryName(originalPath);
  255. newPath = Path.Combine(newPath, newName + Path.GetExtension(originalPath));
  256. bool renameOK = true;
  257. if (!PathEx.IsValidFileName(newName))
  258. {
  259. DialogBox.Open(new LocEdString("Error"), new LocEdString("The name you specified is not a valid file name. Try another."), DialogBox.Type.OK);
  260. renameOK = false;
  261. }
  262. if (renameOK)
  263. {
  264. // Windows sees paths with dot at the end as if they didn't have it
  265. // so remove the dot to ensure the project library does the same
  266. string trimmedNewPath = newPath.TrimEnd('.');
  267. if (originalPath != trimmedNewPath && ProjectLibrary.Exists(trimmedNewPath))
  268. {
  269. DialogBox.Open(new LocEdString("Error"), new LocEdString("File/folder with that name already exists in this folder."), DialogBox.Type.OK);
  270. renameOK = false;
  271. }
  272. }
  273. if (renameOK)
  274. {
  275. ProjectLibrary.Rename(originalPath, newPath);
  276. StopRename();
  277. }
  278. }
  279. else if (Input.IsButtonDown(ButtonCode.Escape))
  280. {
  281. StopRename();
  282. }
  283. }
  284. }
  285. else
  286. {
  287. if (isRenameInProgress)
  288. StopRename();
  289. }
  290. if (autoScrollAmount != 0)
  291. {
  292. Rect2I contentBounds = contentScrollArea.ContentBounds;
  293. float scrollPct = autoScrollAmount / (float)contentBounds.height;
  294. contentScrollArea.VerticalScroll += scrollPct * Time.FrameDelta;
  295. }
  296. if (requiresRefresh)
  297. Refresh();
  298. dropTarget.Update();
  299. }
  300. /// <inheritdoc/>
  301. protected override LocString GetDisplayName()
  302. {
  303. return new LocEdString("Library");
  304. }
  305. /// <inheritdoc/>
  306. protected override void WindowResized(int width, int height)
  307. {
  308. base.WindowResized(width, height);
  309. Refresh();
  310. dropTarget.Bounds = contentScrollArea.Bounds;
  311. }
  312. /// <summary>
  313. /// Attempts to find a resource tile element at the specified coordinates.
  314. /// </summary>
  315. /// <param name="windowPos">Coordinates relative to the window.</param>
  316. /// <returns>True if found an entry, false otherwise.</returns>
  317. private LibraryGUIEntry FindElementAt(Vector2I windowPos)
  318. {
  319. Vector2I scrollPos = WindowToScrollAreaCoords(windowPos);
  320. return content.FindElementAt(scrollPos);
  321. }
  322. /// <summary>
  323. /// Clears hover highlight from the currently hovered over element.
  324. /// </summary>
  325. private void ClearHoverHighlight()
  326. {
  327. content.MarkAsHovered(hoverHighlightPath, false);
  328. hoverHighlightPath = "";
  329. }
  330. /// <summary>
  331. /// Pings an element at the specified path, displaying and highlighting it in the window.
  332. /// </summary>
  333. /// <param name="path">Project library path to the element.</param>
  334. public void Ping(string path)
  335. {
  336. content.MarkAsPinged(pingPath, false);
  337. pingPath = path;
  338. content.MarkAsPinged(pingPath, true);
  339. }
  340. /// <summary>
  341. /// Resets the library window to initial state.
  342. /// </summary>
  343. public void Reset()
  344. {
  345. currentDirectory = ProjectLibrary.Root.Path;
  346. selectionAnchorStart = -1;
  347. selectionAnchorEnd = -1;
  348. selectionPaths.Clear();
  349. pingPath = "";
  350. hoverHighlightPath = "";
  351. Refresh();
  352. }
  353. /// <summary>
  354. /// Deselects all selected elements.
  355. /// </summary>
  356. /// <param name="onlyInternal">If true, do not update the global <see cref="Selection"/>, instead the operation
  357. /// will be contained to the library window internally.</param>
  358. internal void DeselectAll(bool onlyInternal = false)
  359. {
  360. SetSelection(new List<string>(), onlyInternal);
  361. selectionAnchorStart = -1;
  362. selectionAnchorEnd = -1;
  363. }
  364. /// <summary>
  365. /// Select an element at the specified path. If control or shift keys are pressed during this operations multiple
  366. /// elements can be selected.
  367. /// </summary>
  368. /// <param name="path">Project library path to the element.</param>
  369. internal void Select(string path)
  370. {
  371. LibraryGUIEntry entry;
  372. if (!content.TryGetEntry(path, out entry))
  373. return;
  374. bool ctrlDown = Input.IsButtonHeld(ButtonCode.LeftControl) || Input.IsButtonHeld(ButtonCode.RightControl);
  375. bool shiftDown = Input.IsButtonHeld(ButtonCode.LeftShift) || Input.IsButtonHeld(ButtonCode.RightShift);
  376. if (shiftDown)
  377. {
  378. if (selectionAnchorStart != -1 && selectionAnchorStart < content.Entries.Length)
  379. {
  380. int start = Math.Min(entry.index, selectionAnchorStart);
  381. int end = Math.Max(entry.index, selectionAnchorStart);
  382. List<string> newSelection = new List<string>();
  383. for(int i = start; i <= end; i++)
  384. newSelection.Add(content.Entries[i].path);
  385. SetSelection(newSelection);
  386. selectionAnchorEnd = entry.index;
  387. }
  388. else
  389. {
  390. SetSelection(new List<string>() {path});
  391. selectionAnchorStart = entry.index;
  392. selectionAnchorEnd = entry.index;
  393. }
  394. }
  395. else if (ctrlDown)
  396. {
  397. List<string> newSelection = new List<string>(selectionPaths);
  398. if (selectionPaths.Contains(path))
  399. {
  400. newSelection.Remove(path);
  401. if (newSelection.Count == 0)
  402. DeselectAll();
  403. else
  404. {
  405. if (selectionAnchorStart == entry.index)
  406. {
  407. LibraryGUIEntry newAnchorEntry;
  408. if (!content.TryGetEntry(newSelection[0], out newAnchorEntry))
  409. selectionAnchorStart = -1;
  410. else
  411. selectionAnchorStart = newAnchorEntry.index;
  412. }
  413. if (selectionAnchorEnd == entry.index)
  414. {
  415. LibraryGUIEntry newAnchorEntry;
  416. if (!content.TryGetEntry(newSelection[newSelection.Count - 1], out newAnchorEntry))
  417. selectionAnchorEnd = -1;
  418. else
  419. selectionAnchorEnd = newAnchorEntry.index;
  420. }
  421. SetSelection(newSelection);
  422. }
  423. }
  424. else
  425. {
  426. newSelection.Add(path);
  427. SetSelection(newSelection);
  428. selectionAnchorEnd = entry.index;
  429. }
  430. }
  431. else
  432. {
  433. SetSelection(new List<string>() {path});
  434. selectionAnchorStart = entry.index;
  435. selectionAnchorEnd = entry.index;
  436. }
  437. }
  438. /// <summary>
  439. /// Selects a new element in the specified direction from the currently selected element. If shift or control are
  440. /// held during this operation, the selected object will be added to existing selection. If no element is selected
  441. /// the first or last element will be selected depending on direction.
  442. /// </summary>
  443. /// <param name="dir">Direction to move from the currently selected element.</param>
  444. internal void MoveSelection(MoveDirection dir)
  445. {
  446. string newPath = "";
  447. if (selectionPaths.Count == 0 || selectionAnchorEnd == -1)
  448. {
  449. // Nothing is selected so we arbitrarily select first or last element
  450. if (content.Entries.Length > 0)
  451. {
  452. switch (dir)
  453. {
  454. case MoveDirection.Left:
  455. case MoveDirection.Up:
  456. newPath = content.Entries[content.Entries.Length - 1].path;
  457. break;
  458. case MoveDirection.Right:
  459. case MoveDirection.Down:
  460. newPath = content.Entries[0].path;
  461. break;
  462. }
  463. }
  464. }
  465. else
  466. {
  467. switch (dir)
  468. {
  469. case MoveDirection.Left:
  470. if (selectionAnchorEnd - 1 >= 0)
  471. newPath = content.Entries[selectionAnchorEnd - 1].path;
  472. break;
  473. case MoveDirection.Up:
  474. if (selectionAnchorEnd - content.ElementsPerRow >= 0)
  475. newPath = content.Entries[selectionAnchorEnd - content.ElementsPerRow].path;
  476. break;
  477. case MoveDirection.Right:
  478. if (selectionAnchorEnd + 1 < content.Entries.Length)
  479. newPath = content.Entries[selectionAnchorEnd + 1].path;
  480. break;
  481. case MoveDirection.Down:
  482. if (selectionAnchorEnd + content.ElementsPerRow < content.Entries.Length)
  483. newPath = content.Entries[selectionAnchorEnd + content.ElementsPerRow].path;
  484. break;
  485. }
  486. }
  487. if (!string.IsNullOrEmpty(newPath))
  488. {
  489. Select(newPath);
  490. ScrollToEntry(newPath);
  491. }
  492. }
  493. /// <summary>
  494. /// Selects a set of elements based on the provided paths.
  495. /// </summary>
  496. /// <param name="paths">Project library paths of the elements to select.</param>
  497. /// <param name="onlyInternal">If true, do not update the global <see cref="Selection"/>, instead the operation
  498. /// will be contained to the library window internally.</param>
  499. internal void SetSelection(List<string> paths, bool onlyInternal = false)
  500. {
  501. if (selectionPaths != null)
  502. {
  503. foreach (var path in selectionPaths)
  504. content.MarkAsSelected(path, false);
  505. }
  506. selectionPaths = paths;
  507. if (selectionPaths != null)
  508. {
  509. foreach (var path in selectionPaths)
  510. content.MarkAsSelected(path, true);
  511. }
  512. Ping("");
  513. StopRename();
  514. if (!onlyInternal)
  515. {
  516. if (selectionPaths != null)
  517. Selection.resourcePaths = selectionPaths.ToArray();
  518. else
  519. Selection.resourcePaths = new string[0];
  520. }
  521. }
  522. /// <summary>
  523. /// Changes the active directory to the provided directory. Current contents of the window will be cleared and
  524. /// instead contents of the new directory will be displayed.
  525. /// </summary>
  526. /// <param name="directory">Project library path to the directory.</param>
  527. internal void EnterDirectory(string directory)
  528. {
  529. currentDirectory = directory;
  530. DeselectAll();
  531. Refresh();
  532. }
  533. /// <summary>
  534. /// Marks the provided set of elements for a cut operation. Cut elements can be moved to a new location by calling
  535. /// <see cref="Paste"/>.
  536. /// </summary>
  537. /// <param name="sourcePaths">Project library paths of the elements to cut.</param>
  538. internal void Cut(IEnumerable<string> sourcePaths)
  539. {
  540. foreach (var path in cutPaths)
  541. content.MarkAsCut(path, false);
  542. cutPaths.Clear();
  543. cutPaths.AddRange(sourcePaths);
  544. foreach (var path in cutPaths)
  545. content.MarkAsCut(path, true);
  546. copyPaths.Clear();
  547. }
  548. /// <summary>
  549. /// Marks the provided set of elements for a copy operation. You can copy the elements by calling <see cref="Paste"/>.
  550. /// </summary>
  551. /// <param name="sourcePaths">Project library paths of the elements to copy.</param>
  552. internal void Copy(IEnumerable<string> sourcePaths)
  553. {
  554. copyPaths.Clear();
  555. copyPaths.AddRange(sourcePaths);
  556. foreach (var path in cutPaths)
  557. content.MarkAsCut(path, false);
  558. cutPaths.Clear();
  559. }
  560. /// <summary>
  561. /// Duplicates the provided set of elements.
  562. /// </summary>
  563. /// <param name="sourcePaths">Project library paths of the elements to duplicate.</param>
  564. internal void Duplicate(IEnumerable<string> sourcePaths)
  565. {
  566. foreach (var source in sourcePaths)
  567. {
  568. if (Directory.Exists(source))
  569. DirectoryEx.Copy(source, LibraryUtility.GetUniquePath(source));
  570. else if (File.Exists(source))
  571. FileEx.Copy(source, LibraryUtility.GetUniquePath(source));
  572. ProjectLibrary.Refresh();
  573. }
  574. }
  575. /// <summary>
  576. /// Performs a cut or copy operations on the elements previously marked by calling <see cref="Cut"/> or
  577. /// <see cref="Copy"/>.
  578. /// </summary>
  579. /// <param name="destinationFolder">Project library folder into which to move/copy the elements.</param>
  580. internal void Paste(string destinationFolder)
  581. {
  582. if (copyPaths.Count > 0)
  583. {
  584. for (int i = 0; i < copyPaths.Count; i++)
  585. {
  586. string destination = Path.Combine(destinationFolder, PathEx.GetTail(copyPaths[i]));
  587. if (Directory.Exists(copyPaths[i]))
  588. DirectoryEx.Copy(copyPaths[i], LibraryUtility.GetUniquePath(destination));
  589. else if (File.Exists(copyPaths[i]))
  590. FileEx.Copy(copyPaths[i], LibraryUtility.GetUniquePath(destination));
  591. }
  592. ProjectLibrary.Refresh();
  593. }
  594. else if (cutPaths.Count > 0)
  595. {
  596. for (int i = 0; i < cutPaths.Count; i++)
  597. {
  598. string destination = Path.Combine(destinationFolder, PathEx.GetTail(cutPaths[i]));
  599. if (Directory.Exists(cutPaths[i]))
  600. DirectoryEx.Move(cutPaths[i], LibraryUtility.GetUniquePath(destination));
  601. else if (File.Exists(cutPaths[i]))
  602. FileEx.Move(cutPaths[i], LibraryUtility.GetUniquePath(destination));
  603. }
  604. cutPaths.Clear();
  605. ProjectLibrary.Refresh();
  606. }
  607. }
  608. /// <summary>
  609. /// Scrolls the contents GUI area so that the element at the specified path becomes visible.
  610. /// </summary>
  611. /// <param name="path">Project library path to the element.</param>
  612. private void ScrollToEntry(string path)
  613. {
  614. LibraryGUIEntry entryGUI;
  615. if (!content.TryGetEntry(path, out entryGUI))
  616. return;
  617. Rect2I entryBounds = entryGUI.Bounds;
  618. Rect2I contentBounds = contentScrollArea.Layout.Bounds;
  619. Rect2I windowEntryBounds = entryBounds;
  620. windowEntryBounds.x += contentBounds.x;
  621. windowEntryBounds.y += contentBounds.y;
  622. Rect2I scrollAreaBounds = contentScrollArea.Bounds;
  623. bool requiresScroll = windowEntryBounds.y < scrollAreaBounds.y ||
  624. (windowEntryBounds.y + windowEntryBounds.height) > (scrollAreaBounds.y + scrollAreaBounds.height);
  625. if (!requiresScroll)
  626. return;
  627. int scrollableSize = contentBounds.height - scrollAreaBounds.height;
  628. float percent = (((entryBounds.y + entryBounds.height * 0.5f) - scrollAreaBounds.height * 0.5f) / (float)scrollableSize);
  629. percent = MathEx.Clamp01(percent);
  630. contentScrollArea.VerticalScroll = percent;
  631. }
  632. /// <summary>
  633. /// Rebuilds the library window GUI. Should be called any time the active folder or contents change.
  634. /// </summary>
  635. private void Refresh()
  636. {
  637. requiresRefresh = false;
  638. LibraryEntry[] entriesToDisplay = new LibraryEntry[0];
  639. if (IsSearchActive)
  640. {
  641. entriesToDisplay = ProjectLibrary.Search("*" + searchQuery + "*");
  642. }
  643. else
  644. {
  645. DirectoryEntry entry = ProjectLibrary.GetEntry(currentDirectory) as DirectoryEntry;
  646. if (entry == null)
  647. {
  648. currentDirectory = ProjectLibrary.Root.Path;
  649. entry = ProjectLibrary.GetEntry(currentDirectory) as DirectoryEntry;
  650. }
  651. if(entry != null)
  652. entriesToDisplay = entry.Children;
  653. }
  654. inProgressRenameElement = null;
  655. RefreshDirectoryBar();
  656. SortEntries(entriesToDisplay);
  657. content.Refresh(viewType, entriesToDisplay);
  658. if (entriesToDisplay.Length == 0)
  659. return;
  660. foreach (var path in cutPaths)
  661. content.MarkAsCut(path, true);
  662. foreach (var path in selectionPaths)
  663. content.MarkAsSelected(path, true);
  664. content.MarkAsPinged(pingPath, true);
  665. Rect2I contentBounds = content.Bounds;
  666. Rect2I minimalBounds = GetScrollAreaBounds();
  667. contentBounds.height = Math.Max(contentBounds.height, minimalBounds.height);
  668. GUIButton catchAll = new GUIButton("", EditorStyles.Blank);
  669. catchAll.Bounds = contentBounds;
  670. catchAll.OnClick += OnCatchAllClicked;
  671. catchAll.SetContextMenu(entryContextMenu);
  672. content.Underlay.AddElement(catchAll);
  673. Rect2I focusBounds = contentBounds; // Contents + Folder bar
  674. Rect2I scrollBounds = contentScrollArea.Bounds;
  675. focusBounds.x += scrollBounds.x;
  676. focusBounds.y += scrollBounds.y;
  677. Rect2I folderBarBounds = folderListLayout.Bounds;
  678. focusBounds.y -= folderBarBounds.height;
  679. focusBounds.height += folderBarBounds.height;
  680. GUIButton focusCatcher = new GUIButton("", EditorStyles.Blank);
  681. focusCatcher.OnFocusChanged += OnContentsFocusChanged;
  682. focusCatcher.Bounds = focusBounds;
  683. GUIPanel focusPanel = GUI.AddPanel(3);
  684. focusPanel.AddElement(focusCatcher);
  685. UpdateDragSelection(dragSelectionEnd);
  686. }
  687. /// <summary>
  688. /// Converts coordinates relative to the window into coordinates relative to the contents scroll area.
  689. /// </summary>
  690. /// <param name="windowPos">Coordinates relative to the window.</param>
  691. /// <returns>Coordinates relative to the contents scroll area.</returns>
  692. private Vector2I WindowToScrollAreaCoords(Vector2I windowPos)
  693. {
  694. Rect2I scrollBounds = contentScrollArea.Layout.Bounds;
  695. Vector2I scrollPos = windowPos;
  696. scrollPos.x -= scrollBounds.x;
  697. scrollPos.y -= scrollBounds.y;
  698. return scrollPos;
  699. }
  700. /// <summary>
  701. /// Starts a drag operation that displays a selection outline allowing the user to select multiple entries at once.
  702. /// </summary>
  703. /// <param name="windowPos">Coordinates relative to the window where the drag originated.</param>
  704. private void StartDragSelection(Vector2I windowPos)
  705. {
  706. isDraggingSelection = true;
  707. dragSelectionStart = WindowToScrollAreaCoords(windowPos);
  708. dragSelectionEnd = dragSelectionStart;
  709. }
  710. /// <summary>
  711. /// Updates a selection outline drag operation by expanding the outline to the new location. Elements in the outline
  712. /// are selected.
  713. /// </summary>
  714. /// <param name="windowPos">Coordinates of the pointer relative to the window.</param>
  715. /// <returns>True if the selection outline drag is valid and was updated, false otherwise.</returns>
  716. private bool UpdateDragSelection(Vector2I windowPos)
  717. {
  718. if (!isDraggingSelection)
  719. return false;
  720. if (dragSelection == null)
  721. {
  722. dragSelection = new GUITexture(null, true, EditorStyles.SelectionArea);
  723. content.Overlay.AddElement(dragSelection);
  724. }
  725. dragSelectionEnd = WindowToScrollAreaCoords(windowPos);
  726. Rect2I selectionArea = CalculateSelectionArea();
  727. SelectInArea(selectionArea);
  728. dragSelection.Bounds = selectionArea;
  729. return true;
  730. }
  731. /// <summary>
  732. /// Ends the selection outline drag operation. Elements in the outline are selected.
  733. /// </summary>
  734. /// <returns>True if the selection outline drag is valid and was ended, false otherwise.</returns>
  735. private bool EndDragSelection()
  736. {
  737. if (!isDraggingSelection)
  738. return false;
  739. if (dragSelection != null)
  740. {
  741. dragSelection.Destroy();
  742. dragSelection = null;
  743. }
  744. Rect2I selectionArea = CalculateSelectionArea();
  745. SelectInArea(selectionArea);
  746. isDraggingSelection = false;
  747. return false;
  748. }
  749. /// <summary>
  750. /// Calculates bounds of the selection area used for selection overlay drag operation, depending on drag starting
  751. /// point coordinates and current drag coordinates.
  752. /// </summary>
  753. /// <returns>Bounds of the selection area, relative to the content scroll area.</returns>
  754. private Rect2I CalculateSelectionArea()
  755. {
  756. Rect2I selectionArea = new Rect2I();
  757. if (dragSelectionStart.x < dragSelectionEnd.x)
  758. {
  759. selectionArea.x = dragSelectionStart.x;
  760. selectionArea.width = dragSelectionEnd.x - dragSelectionStart.x;
  761. }
  762. else
  763. {
  764. selectionArea.x = dragSelectionEnd.x;
  765. selectionArea.width = dragSelectionStart.x - dragSelectionEnd.x;
  766. }
  767. if (dragSelectionStart.y < dragSelectionEnd.y)
  768. {
  769. selectionArea.y = dragSelectionStart.y;
  770. selectionArea.height = dragSelectionEnd.y - dragSelectionStart.y;
  771. }
  772. else
  773. {
  774. selectionArea.y = dragSelectionEnd.y;
  775. selectionArea.height = dragSelectionStart.y - dragSelectionEnd.y;
  776. }
  777. Rect2I maxBounds = contentScrollArea.Layout.Bounds;
  778. maxBounds.x = 0;
  779. maxBounds.y = 0;
  780. selectionArea.Clip(maxBounds);
  781. return selectionArea;
  782. }
  783. /// <summary>
  784. /// Selects all elements overlapping the specified bounds.
  785. /// </summary>
  786. /// <param name="scrollBounds">Bounds relative to the content scroll area.</param>
  787. private void SelectInArea(Rect2I scrollBounds)
  788. {
  789. LibraryGUIEntry[] foundElements = content.FindElementsOverlapping(scrollBounds);
  790. if (foundElements.Length > 0)
  791. {
  792. selectionAnchorStart = foundElements[0].index;
  793. selectionAnchorEnd = foundElements[foundElements.Length - 1].index;
  794. }
  795. else
  796. {
  797. selectionAnchorStart = -1;
  798. selectionAnchorEnd = -1;
  799. }
  800. List<string> elementPaths = new List<string>();
  801. foreach (var elem in foundElements)
  802. elementPaths.Add(elem.path);
  803. SetSelection(elementPaths);
  804. }
  805. /// <summary>
  806. /// Updates GUI for the directory bar. Should be called whenever the active folder changes.
  807. /// </summary>
  808. private void RefreshDirectoryBar()
  809. {
  810. if (folderListLayout != null)
  811. {
  812. folderListLayout.Destroy();
  813. folderListLayout = null;
  814. }
  815. folderListLayout = folderBarLayout.AddLayoutX();
  816. string[] folders = null;
  817. string[] fullPaths = null;
  818. if (IsSearchActive)
  819. {
  820. folders = new[] {searchQuery};
  821. fullPaths = new[] { searchQuery };
  822. }
  823. else
  824. {
  825. string currentDir = Path.Combine("Resources", currentDirectory);
  826. folders = currentDir.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar },
  827. StringSplitOptions.RemoveEmptyEntries);
  828. fullPaths = new string[folders.Length];
  829. for (int i = 0; i < folders.Length; i++)
  830. {
  831. if (i == 0)
  832. fullPaths[i] = "";
  833. else
  834. fullPaths[i] = Path.Combine(fullPaths[i - 1], folders[i]);
  835. }
  836. }
  837. int availableWidth = folderBarLayout.Bounds.width - FOLDER_BUTTON_WIDTH * 2;
  838. int numFolders = 0;
  839. for (int i = folders.Length - 1; i >= 0; i--)
  840. {
  841. GUIButton folderButton = new GUIButton(folders[i]);
  842. if (!IsSearchActive)
  843. {
  844. string fullPath = fullPaths[i];
  845. folderButton.OnClick += () => OnFolderButtonClicked(fullPath);
  846. }
  847. GUIButton separator = new GUIButton("/", GUIOption.FixedWidth(FOLDER_SEPARATOR_WIDTH));
  848. folderListLayout.InsertElement(0, separator);
  849. folderListLayout.InsertElement(0, folderButton);
  850. numFolders++;
  851. Rect2I folderListBounds = folderListLayout.Bounds;
  852. if (folderListBounds.width > availableWidth)
  853. {
  854. if (numFolders > 2)
  855. {
  856. separator.Destroy();
  857. folderButton.Destroy();
  858. break;
  859. }
  860. }
  861. }
  862. }
  863. /// <summary>
  864. /// Performs <see cref="Cut"/> operation on the currently selected elements.
  865. /// </summary>
  866. internal void CutSelection()
  867. {
  868. if (selectionPaths.Count > 0)
  869. Cut(selectionPaths);
  870. }
  871. /// <summary>
  872. /// Performs <see cref="Copy"/> operation on the currently selected elements.
  873. /// </summary>
  874. internal void CopySelection()
  875. {
  876. if (selectionPaths.Count > 0)
  877. Copy(selectionPaths);
  878. }
  879. /// <summary>
  880. /// Performs <see cref="Duplicate"/> operation on the currently selected elements.
  881. /// </summary>
  882. internal void DuplicateSelection()
  883. {
  884. if (selectionPaths.Count > 0)
  885. Duplicate(selectionPaths);
  886. }
  887. /// <summary>
  888. /// Performs <see cref="Paste"/> operation. Elements will be pasted in the currently selected directory (if any), or
  889. /// the active directory otherwise.
  890. /// </summary>
  891. internal void PasteToSelection()
  892. {
  893. Paste(SelectedFolder);
  894. }
  895. /// <summary>
  896. /// Starts a rename operation on the currently selected elements. If more than one elements are selected only the
  897. /// first one will be affected.
  898. /// </summary>
  899. internal void RenameSelection()
  900. {
  901. if (selectionPaths.Count == 0)
  902. return;
  903. if (selectionPaths.Count > 1)
  904. {
  905. DeselectAll();
  906. Select(selectionPaths[0]);
  907. }
  908. LibraryGUIEntry entry;
  909. if (content.TryGetEntry(selectionPaths[0], out entry))
  910. {
  911. entry.StartRename();
  912. inProgressRenameElement = entry;
  913. }
  914. }
  915. /// <summary>
  916. /// Deletes currently selected elements. User will be asked to confirm deletion via a dialog box.
  917. /// </summary>
  918. internal void DeleteSelection()
  919. {
  920. if (selectionPaths.Count == 0)
  921. return;
  922. DialogBox.Open(new LocEdString("Confirm deletion"), new LocEdString("Are you sure you want to delete the selected object(s)?"),
  923. DialogBox.Type.YesNo,
  924. type =>
  925. {
  926. if (type == DialogBox.ResultType.Yes)
  927. {
  928. foreach (var path in selectionPaths)
  929. {
  930. ProjectLibrary.Delete(path);
  931. }
  932. DeselectAll();
  933. Refresh();
  934. }
  935. });
  936. }
  937. /// <summary>
  938. /// Stops the rename operation, if one is in progress on any element.
  939. /// </summary>
  940. internal void StopRename()
  941. {
  942. if (inProgressRenameElement != null)
  943. {
  944. inProgressRenameElement.StopRename();
  945. inProgressRenameElement = null;
  946. }
  947. }
  948. /// <summary>
  949. /// Clears the search bar and refreshes the content area to display contents of the current directory.
  950. /// </summary>
  951. private void ClearSearch()
  952. {
  953. searchField.Value = "";
  954. searchQuery = "";
  955. Refresh();
  956. }
  957. /// <summary>
  958. /// Opens the drop down options window that allows you to customize library window look and feel.
  959. /// </summary>
  960. private void OpenOptionsWindow()
  961. {
  962. Vector2I openPosition;
  963. Rect2I buttonBounds = GUILayoutUtility.CalculateBounds(optionsButton, GUI);
  964. openPosition.x = buttonBounds.x + buttonBounds.width / 2;
  965. openPosition.y = buttonBounds.y + buttonBounds.height / 2;
  966. LibraryDropDown dropDown = DropDownWindow.Open<LibraryDropDown>(this, openPosition);
  967. dropDown.Initialize(this);
  968. }
  969. /// <summary>
  970. /// Returns the content scroll area bounds.
  971. /// </summary>
  972. /// <returns>Bounds of the content scroll area, relative to the window.</returns>
  973. private Rect2I GetScrollAreaBounds()
  974. {
  975. Rect2I bounds = GUI.Bounds;
  976. Rect2I folderListBounds = folderListLayout.Bounds;
  977. Rect2I searchBarBounds = searchBarLayout.Bounds;
  978. bounds.y += folderListBounds.height + searchBarBounds.height;
  979. bounds.height -= folderListBounds.height + searchBarBounds.height;
  980. return bounds;
  981. }
  982. /// <summary>
  983. /// Triggered when a project library entry was changed (added, modified, deleted).
  984. /// </summary>
  985. /// <param name="entry">Project library path of the changed entry.</param>
  986. private void OnEntryChanged(string entry)
  987. {
  988. requiresRefresh = true;
  989. }
  990. /// <summary>
  991. /// Triggered when the drag and drop operation is starting while over the content area. If drag operation is over
  992. /// an element, element will be dragged.
  993. /// </summary>
  994. /// <param name="windowPos">Coordinates where the drag operation started, relative to the window.</param>
  995. private void OnDragStart(Vector2I windowPos)
  996. {
  997. LibraryGUIEntry underCursorElem = FindElementAt(windowPos);
  998. if (underCursorElem == null)
  999. {
  1000. StartDragSelection(windowPos);
  1001. return;
  1002. }
  1003. string resourceDir = ProjectLibrary.ResourceFolder;
  1004. string[] dragPaths = null;
  1005. if (selectionPaths.Count > 0)
  1006. {
  1007. foreach (var path in selectionPaths)
  1008. {
  1009. if (path == underCursorElem.path)
  1010. {
  1011. dragPaths = new string[selectionPaths.Count];
  1012. for (int i = 0; i < selectionPaths.Count; i++)
  1013. {
  1014. dragPaths[i] = Path.Combine(resourceDir, selectionPaths[i]);
  1015. }
  1016. break;
  1017. }
  1018. }
  1019. }
  1020. if (dragPaths == null)
  1021. dragPaths = new[] { Path.Combine(resourceDir, underCursorElem.path) };
  1022. ResourceDragDropData dragDropData = new ResourceDragDropData(dragPaths);
  1023. DragDrop.StartDrag(dragDropData);
  1024. }
  1025. /// <summary>
  1026. /// Triggered when a pointer is moved while a drag operation is in progress.
  1027. /// </summary>
  1028. /// <param name="windowPos">Coordinates of the pointer relative to the window.</param>
  1029. private void OnDragMove(Vector2I windowPos)
  1030. {
  1031. // Auto-scroll
  1032. Rect2I scrollAreaBounds = contentScrollArea.Bounds;
  1033. int scrollAreaTop = scrollAreaBounds.y;
  1034. int scrollAreaBottom = scrollAreaBounds.y + scrollAreaBounds.height;
  1035. if (windowPos.y > scrollAreaTop && windowPos.y <= (scrollAreaTop + DRAG_SCROLL_HEIGHT))
  1036. autoScrollAmount = -DRAG_SCROLL_AMOUNT_PER_SECOND;
  1037. else if (windowPos.y >= (scrollAreaBottom - DRAG_SCROLL_HEIGHT) && windowPos.y < scrollAreaBottom)
  1038. autoScrollAmount = DRAG_SCROLL_AMOUNT_PER_SECOND;
  1039. else
  1040. autoScrollAmount = 0;
  1041. // Selection box
  1042. if (UpdateDragSelection(windowPos))
  1043. return;
  1044. // Drag and drop (hover element under cursor)
  1045. LibraryGUIEntry underCursorElem = FindElementAt(windowPos);
  1046. if (underCursorElem == null)
  1047. {
  1048. ClearHoverHighlight();
  1049. }
  1050. else
  1051. {
  1052. if (underCursorElem.path != hoverHighlightPath)
  1053. {
  1054. ClearHoverHighlight();
  1055. hoverHighlightPath = underCursorElem.path;
  1056. underCursorElem.MarkAsHovered(true);
  1057. }
  1058. }
  1059. }
  1060. /// <summary>
  1061. /// Triggered when a pointer leaves the drop targer while a drag operation is in progress.
  1062. /// </summary>
  1063. private void OnDragLeave()
  1064. {
  1065. ClearHoverHighlight();
  1066. autoScrollAmount = 0;
  1067. }
  1068. /// <summary>
  1069. /// Triggered when a resource drop operation finishes over the content area.
  1070. /// </summary>
  1071. /// <param name="windowPos">Coordinates of the pointer relative to the window where the drop operation finished
  1072. /// .</param>
  1073. /// <param name="paths">Paths of the dropped resources.</param>
  1074. private void OnResourceDragDropped(Vector2I windowPos, string[] paths)
  1075. {
  1076. ClearHoverHighlight();
  1077. autoScrollAmount = 0;
  1078. if (EndDragSelection())
  1079. return;
  1080. string resourceDir = ProjectLibrary.ResourceFolder;
  1081. string destinationFolder = Path.Combine(resourceDir, currentDirectory);
  1082. LibraryGUIEntry underCursorElement = FindElementAt(windowPos);
  1083. if (underCursorElement != null)
  1084. {
  1085. LibraryEntry entry = ProjectLibrary.GetEntry(underCursorElement.path);
  1086. if (entry != null && entry.Type == LibraryEntryType.Directory)
  1087. destinationFolder = Path.Combine(resourceDir, entry.Path);
  1088. }
  1089. if (paths != null)
  1090. {
  1091. foreach (var path in paths)
  1092. {
  1093. if (path == null)
  1094. continue;
  1095. string absolutePath = path;
  1096. if (!Path.IsPathRooted(absolutePath))
  1097. absolutePath = Path.Combine(resourceDir, path);
  1098. if (string.IsNullOrEmpty(absolutePath))
  1099. continue;
  1100. if (PathEx.IsPartOf(destinationFolder, absolutePath) || PathEx.Compare(absolutePath, destinationFolder))
  1101. continue;
  1102. string pathTail = PathEx.GetTail(absolutePath);
  1103. string destination = Path.Combine(destinationFolder, pathTail);
  1104. bool doCopy = !ProjectLibrary.Exists(path);
  1105. if (Directory.Exists(path))
  1106. {
  1107. if (doCopy)
  1108. DirectoryEx.Copy(path, LibraryUtility.GetUniquePath(destination));
  1109. else
  1110. DirectoryEx.Move(path, LibraryUtility.GetUniquePath(destination));
  1111. }
  1112. else if (File.Exists(path))
  1113. {
  1114. if (doCopy)
  1115. FileEx.Copy(path, LibraryUtility.GetUniquePath(destination));
  1116. else
  1117. FileEx.Move(path, LibraryUtility.GetUniquePath(destination));
  1118. }
  1119. ProjectLibrary.Refresh();
  1120. }
  1121. }
  1122. }
  1123. /// <summary>
  1124. /// Triggered when a scene object drop operation finishes over the content area.
  1125. /// </summary>
  1126. /// <param name="windowPos">Coordinates of the pointer relative to the window where the drop operation finished
  1127. /// .</param>
  1128. /// <param name="objects">Dropped scene objects.</param>
  1129. private void OnSceneObjectDragDropped(Vector2I windowPos, SceneObject[] objects)
  1130. {
  1131. ClearHoverHighlight();
  1132. autoScrollAmount = 0;
  1133. if (EndDragSelection())
  1134. return;
  1135. string destinationFolder = currentDirectory;
  1136. LibraryGUIEntry underCursorElement = FindElementAt(windowPos);
  1137. if (underCursorElement != null)
  1138. {
  1139. LibraryEntry entry = ProjectLibrary.GetEntry(underCursorElement.path);
  1140. if (entry != null && entry.Type == LibraryEntryType.Directory)
  1141. destinationFolder = entry.Path;
  1142. }
  1143. if (objects != null)
  1144. {
  1145. foreach (var so in objects)
  1146. {
  1147. if (so == null)
  1148. continue;
  1149. Prefab newPrefab = new Prefab(so);
  1150. string destination = LibraryUtility.GetUniquePath(Path.Combine(destinationFolder, so.Name + ".prefab"));
  1151. ProjectLibrary.Create(newPrefab, destination);
  1152. ProjectLibrary.Refresh();
  1153. }
  1154. }
  1155. }
  1156. /// <summary>
  1157. /// Triggered when a drag operation that originated from this window ends.
  1158. /// </summary>
  1159. /// <param name="windowPos">Coordinates of the pointer where the drag ended relative to the window </param>
  1160. private void OnDragEnd(Vector2I windowPos)
  1161. {
  1162. EndDragSelection();
  1163. autoScrollAmount = 0;
  1164. }
  1165. /// <summary>
  1166. /// Triggered when the global selection changes.
  1167. /// </summary>
  1168. /// <param name="sceneObjects">A set of newly selected scene objects.</param>
  1169. /// <param name="resourcePaths">A set of paths for newly selected resources.</param>
  1170. private void OnSelectionChanged(SceneObject[] sceneObjects, string[] resourcePaths)
  1171. {
  1172. if(sceneObjects.Length > 0)
  1173. DeselectAll(true);
  1174. }
  1175. /// <summary>
  1176. /// Triggered when a ping operation was triggered externally.
  1177. /// </summary>
  1178. /// <param name="path">Path to the resource to highlight.</param>
  1179. private void OnPing(string path)
  1180. {
  1181. Ping(path);
  1182. }
  1183. /// <summary>
  1184. /// Triggered when a folder on the directory bar was selected.
  1185. /// </summary>
  1186. /// <param name="path">Project library path to the folder to enter.</param>
  1187. private void OnFolderButtonClicked(string path)
  1188. {
  1189. EnterDirectory(path);
  1190. }
  1191. /// <summary>
  1192. /// Triggered when the content area receives or loses keyboard focus.
  1193. /// </summary>
  1194. /// <param name="focus">True if focus was received, false otherwise.</param>
  1195. private void OnContentsFocusChanged(bool focus)
  1196. {
  1197. hasContentFocus = focus;
  1198. }
  1199. /// <summary>
  1200. /// Triggered when the user clicks on empty space between elements.
  1201. /// </summary>
  1202. private void OnCatchAllClicked()
  1203. {
  1204. DeselectAll();
  1205. }
  1206. /// <summary>
  1207. /// Triggered when the user clicks on the home button on the directory bar, changing the active directory to
  1208. /// project library root.
  1209. /// </summary>
  1210. private void OnHomeClicked()
  1211. {
  1212. currentDirectory = ProjectLibrary.Root.Path;
  1213. Refresh();
  1214. }
  1215. /// <summary>
  1216. /// Triggered when the user clicks on the up button on the directory bar, changing the active directory to the
  1217. /// parent directory, unless already at project library root.
  1218. /// </summary>
  1219. private void OnUpClicked()
  1220. {
  1221. currentDirectory = currentDirectory.Trim(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
  1222. if (!string.IsNullOrEmpty(currentDirectory))
  1223. {
  1224. string parent = Path.GetDirectoryName(currentDirectory);
  1225. currentDirectory = parent;
  1226. Refresh();
  1227. }
  1228. }
  1229. /// <summary>
  1230. /// Triggered when the user inputs new values into the search input box. Refreshes the contents so they display
  1231. /// elements matching the search text.
  1232. /// </summary>
  1233. /// <param name="newValue">Search box text.</param>
  1234. private void OnSearchChanged(string newValue)
  1235. {
  1236. searchQuery = newValue;
  1237. Refresh();
  1238. }
  1239. /// <summary>
  1240. /// Sorts the specified set of project library entries by type (folder or resource), followed by name.
  1241. /// </summary>
  1242. /// <param name="input">Set of project library entries to sort.</param>
  1243. private static void SortEntries(LibraryEntry[] input)
  1244. {
  1245. Array.Sort(input, (x, y) =>
  1246. {
  1247. if (x.Type == y.Type)
  1248. return x.Name.CompareTo(y.Name);
  1249. else
  1250. return x.Type == LibraryEntryType.File ? 1 : -1;
  1251. });
  1252. }
  1253. }
  1254. }