LibraryGUIEntry.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. using System.IO;
  4. using BansheeEngine;
  5. namespace BansheeEditor
  6. {
  7. /// <summary>
  8. /// Represents GUI for a single resource tile used in <see cref="LibraryGUIContent"/>.
  9. /// </summary>
  10. internal class LibraryGUIEntry
  11. {
  12. private const int MAX_LABEL_HEIGHT = 50;
  13. private static readonly Color PING_COLOR = Color.BansheeOrange;
  14. private static readonly Color SELECTION_COLOR = Color.DarkCyan;
  15. private static readonly Color HOVER_COLOR = new Color(Color.DarkCyan.r, Color.DarkCyan.g, Color.DarkCyan.b, 0.5f);
  16. private static readonly Color CUT_COLOR = new Color(1.0f, 1.0f, 1.0f, 0.5f);
  17. private const int SELECTION_EXTRA_WIDTH = 3;
  18. /// <summary>
  19. /// Possible visual states for the resource tile.
  20. /// </summary>
  21. enum UnderlayState // Note: Order of these is relevant
  22. {
  23. None, Hovered, Selected, Pinged
  24. }
  25. public int index;
  26. public string path;
  27. public GUITexture icon;
  28. public GUILabel label;
  29. public Rect2I bounds;
  30. private GUITexture underlay;
  31. private GUITexture groupUnderlay;
  32. private LibraryGUIContent owner;
  33. private UnderlayState underlayState;
  34. private GUITextBox renameTextBox;
  35. private bool delayedSelect;
  36. private float delayedSelectTime;
  37. private ulong delayedOpenCodeEditorFrame = ulong.MaxValue;
  38. /// <summary>
  39. /// Bounds of the entry relative to part content area.
  40. /// </summary>
  41. public Rect2I Bounds
  42. {
  43. get { return bounds; }
  44. }
  45. /// <summary>
  46. /// Constructs a new resource tile entry.
  47. /// </summary>
  48. /// <param name="owner">Content area this entry is part of.</param>
  49. /// <param name="parent">Parent layout to add this entry's GUI elements to.</param>
  50. /// <param name="path">Path to the project library entry to display data for.</param>
  51. /// <param name="index">Sequential index of the entry in the conent area.</param>
  52. /// <param name="labelWidth">Width of the GUI labels that display the elements.</param>
  53. /// <param name="type">Type of the entry, which controls its style and/or behaviour.</param>
  54. public LibraryGUIEntry(LibraryGUIContent owner, GUILayout parent, string path, int index, int labelWidth,
  55. LibraryGUIEntryType type)
  56. {
  57. GUILayout entryLayout;
  58. if (owner.GridLayout)
  59. entryLayout = parent.AddLayoutY();
  60. else
  61. entryLayout = parent.AddLayoutX();
  62. SpriteTexture iconTexture = GetIcon(path, owner.TileSize);
  63. icon = new GUITexture(iconTexture, GUIImageScaleMode.ScaleToFit,
  64. true, GUIOption.FixedHeight(owner.TileSize), GUIOption.FixedWidth(owner.TileSize));
  65. label = null;
  66. string name = PathEx.GetTail(path);
  67. if (owner.GridLayout)
  68. {
  69. label = new GUILabel(name, EditorStyles.MultiLineLabelCentered,
  70. GUIOption.FixedWidth(labelWidth), GUIOption.FlexibleHeight(0, MAX_LABEL_HEIGHT));
  71. }
  72. else
  73. {
  74. label = new GUILabel(name);
  75. }
  76. entryLayout.AddElement(icon);
  77. entryLayout.AddElement(label);
  78. switch (type)
  79. {
  80. case LibraryGUIEntryType.Single:
  81. break;
  82. case LibraryGUIEntryType.MultiFirst:
  83. groupUnderlay = new GUITexture(null, EditorStyles.LibraryEntryFirstBg);
  84. break;
  85. case LibraryGUIEntryType.MultiElement:
  86. groupUnderlay = new GUITexture(null, EditorStyles.LibraryEntryBg);
  87. break;
  88. case LibraryGUIEntryType.MultiLast:
  89. groupUnderlay = new GUITexture(null, EditorStyles.LibraryEntryLastBg);
  90. break;
  91. }
  92. if (groupUnderlay != null)
  93. owner.DeepUnderlay.AddElement(groupUnderlay);
  94. this.owner = owner;
  95. this.index = index;
  96. this.path = path;
  97. this.bounds = new Rect2I();
  98. this.underlay = null;
  99. }
  100. /// <summary>
  101. /// Positions the GUI elements. Must be called after construction, but only after all content area entries have
  102. /// been constructed so that entry's final bounds are known.
  103. /// </summary>
  104. public void Initialize()
  105. {
  106. Rect2I iconBounds = icon.Bounds;
  107. bounds = iconBounds;
  108. Rect2I labelBounds = label.Bounds;
  109. bounds.x = MathEx.Min(bounds.x, labelBounds.x - SELECTION_EXTRA_WIDTH);
  110. bounds.y = MathEx.Min(bounds.y, labelBounds.y) - 5; // 5 - Just padding for better look
  111. bounds.width = MathEx.Max(bounds.x + bounds.width,
  112. labelBounds.x + labelBounds.width) - bounds.x + SELECTION_EXTRA_WIDTH;
  113. bounds.height = MathEx.Max(bounds.y + bounds.height,
  114. labelBounds.y + labelBounds.height) - bounds.y;
  115. string hoistedPath = path;
  116. GUIButton overlayBtn = new GUIButton("", EditorStyles.Blank);
  117. overlayBtn.Bounds = bounds;
  118. overlayBtn.OnClick += () => OnEntryClicked(hoistedPath);
  119. overlayBtn.OnDoubleClick += () => OnEntryDoubleClicked(hoistedPath);
  120. overlayBtn.SetContextMenu(owner.Window.ContextMenu);
  121. owner.Overlay.AddElement(overlayBtn);
  122. if (groupUnderlay != null)
  123. {
  124. if (owner.GridLayout)
  125. {
  126. int centerX = iconBounds.x + iconBounds.width/2;
  127. int left = centerX - owner.ElementWidth/2;
  128. Rect2I bgBounds = new Rect2I(left, bounds.y, owner.ElementWidth, bounds.height);
  129. groupUnderlay.Bounds = bgBounds;
  130. }
  131. else
  132. {
  133. // TODO
  134. }
  135. }
  136. }
  137. /// <summary>
  138. /// Called every frame.
  139. /// </summary>
  140. public void Update()
  141. {
  142. if (delayedSelect && Time.RealElapsed > delayedSelectTime)
  143. {
  144. owner.Window.Select(path);
  145. delayedSelect = false;
  146. }
  147. if (delayedOpenCodeEditorFrame == Time.FrameIdx)
  148. {
  149. LibraryEntry entry = ProjectLibrary.GetEntry(path);
  150. if (entry != null && entry.Type == LibraryEntryType.File)
  151. {
  152. FileEntry resEntry = (FileEntry) entry;
  153. CodeEditor.OpenFile(resEntry.Path, 0);
  154. }
  155. ProgressBar.Hide();
  156. }
  157. }
  158. /// <summary>
  159. /// Changes the visual representation of the element as being cut.
  160. /// </summary>
  161. /// <param name="enable">True if mark as cut, false to reset to normal.</param>
  162. public void MarkAsCut(bool enable)
  163. {
  164. if (enable)
  165. icon.SetTint(CUT_COLOR);
  166. else
  167. icon.SetTint(Color.White);
  168. }
  169. /// <summary>
  170. /// Changes the visual representation of the element as being selected.
  171. /// </summary>
  172. /// <param name="enable">True if mark as selected, false to reset to normal.</param>
  173. public void MarkAsSelected(bool enable)
  174. {
  175. if ((int)underlayState > (int)UnderlayState.Selected)
  176. return;
  177. if (enable)
  178. {
  179. CreateUnderlay();
  180. underlay.SetTint(SELECTION_COLOR);
  181. underlayState = UnderlayState.Selected;
  182. }
  183. else
  184. {
  185. ClearUnderlay();
  186. underlayState = UnderlayState.None;
  187. }
  188. }
  189. /// <summary>
  190. /// Changes the visual representation of the element as being pinged.
  191. /// </summary>
  192. /// <param name="enable">True if mark as pinged, false to reset to normal.</param>
  193. public void MarkAsPinged(bool enable)
  194. {
  195. if ((int)underlayState > (int)UnderlayState.Pinged)
  196. return;
  197. if (enable)
  198. {
  199. CreateUnderlay();
  200. underlay.SetTint(PING_COLOR);
  201. underlayState = UnderlayState.Pinged;
  202. }
  203. else
  204. {
  205. ClearUnderlay();
  206. underlayState = UnderlayState.None;
  207. }
  208. }
  209. /// <summary>
  210. /// Changes the visual representation of the element as being hovered over.
  211. /// </summary>
  212. /// <param name="enable">True if mark as hovered, false to reset to normal.</param>
  213. public void MarkAsHovered(bool enable)
  214. {
  215. if ((int)underlayState > (int)UnderlayState.Hovered)
  216. return;
  217. if (enable)
  218. {
  219. CreateUnderlay();
  220. underlay.SetTint(HOVER_COLOR);
  221. underlayState = UnderlayState.Hovered;
  222. }
  223. else
  224. {
  225. ClearUnderlay();
  226. underlayState = UnderlayState.None;
  227. }
  228. }
  229. /// <summary>
  230. /// Starts a rename operation over the entry, displaying the rename input box.
  231. /// </summary>
  232. public void StartRename()
  233. {
  234. if (renameTextBox != null)
  235. return;
  236. renameTextBox = new GUITextBox(true);
  237. Rect2I renameBounds = label.Bounds;
  238. // Rename box allows for less space for text than label, so adjust it slightly so it's more likely to be able
  239. // to display all visible text.
  240. renameBounds.x -= 4;
  241. renameBounds.width += 8;
  242. renameBounds.height += 8;
  243. renameTextBox.Bounds = renameBounds;
  244. owner.RenameOverlay.AddElement(renameTextBox);
  245. string name = Path.GetFileNameWithoutExtension(PathEx.GetTail(path));
  246. renameTextBox.Text = name;
  247. renameTextBox.Focus = true;
  248. }
  249. /// <summary>
  250. /// Stops a rename operation over the entry, hiding the rename input box.
  251. /// </summary>
  252. public void StopRename()
  253. {
  254. if (renameTextBox != null)
  255. {
  256. renameTextBox.Destroy();
  257. renameTextBox = null;
  258. }
  259. }
  260. /// <summary>
  261. /// Gets the new name of the entry. Only valid while a rename operation is in progress.
  262. /// </summary>
  263. /// <returns>New name of the entry currently entered in the rename input box.</returns>
  264. public string GetRenamedName()
  265. {
  266. if (renameTextBox != null)
  267. return renameTextBox.Text;
  268. return "";
  269. }
  270. /// <summary>
  271. /// Clears the underlay GUI element (for example ping, hover, select).
  272. /// </summary>
  273. private void ClearUnderlay()
  274. {
  275. if (underlay != null)
  276. {
  277. underlay.Destroy();
  278. underlay = null;
  279. }
  280. underlayState = UnderlayState.None;
  281. }
  282. /// <summary>
  283. /// Creates a GUI elements that may be used for underlay effects (for example ping, hover, select).
  284. /// </summary>
  285. private void CreateUnderlay()
  286. {
  287. if (underlay == null)
  288. {
  289. underlay = new GUITexture(Builtin.WhiteTexture);
  290. underlay.Bounds = Bounds;
  291. owner.Underlay.AddElement(underlay);
  292. }
  293. }
  294. /// <summary>
  295. /// Triggered when the user clicks on the entry.
  296. /// </summary>
  297. /// <param name="path">Project library path of the clicked entry.</param>
  298. private void OnEntryClicked(string path)
  299. {
  300. LibraryEntry entry = ProjectLibrary.GetEntry(path);
  301. if (entry != null && entry.Type == LibraryEntryType.Directory)
  302. {
  303. // If entry is a directory delay selection as it might be a double-click, in which case we want to keep
  304. // whatever selection is active currently so that user can perform drag and drop with its inspector
  305. // from the folder he is browsing to.
  306. delayedSelect = true;
  307. delayedSelectTime = Time.RealElapsed + 0.5f;
  308. }
  309. else
  310. owner.Window.Select(path);
  311. }
  312. /// <summary>
  313. /// Triggered when the user double-clicked on the entry.
  314. /// </summary>
  315. /// <param name="path">Project library path of the double-clicked entry.</param>
  316. private void OnEntryDoubleClicked(string path)
  317. {
  318. delayedSelect = false;
  319. LibraryEntry entry = ProjectLibrary.GetEntry(path);
  320. if (entry != null)
  321. {
  322. if (entry.Type == LibraryEntryType.Directory)
  323. owner.Window.EnterDirectory(path);
  324. else
  325. {
  326. ResourceMeta meta = ProjectLibrary.GetMeta(path);
  327. FileEntry fileEntry = (FileEntry)entry;
  328. if (meta.ResType == ResourceType.Prefab)
  329. {
  330. EditorApplication.LoadScene(fileEntry.Path);
  331. }
  332. else if (meta.ResType == ResourceType.ScriptCode)
  333. {
  334. ProgressBar.Show("Opening external code editor...", 1.0f);
  335. delayedOpenCodeEditorFrame = Time.FrameIdx + 1;
  336. }
  337. }
  338. }
  339. }
  340. /// <summary>
  341. /// Returns an icon that can be used for displaying a resource of the specified type.
  342. /// </summary>
  343. /// <param name="path">Path to the project library entry to display data for.</param>
  344. /// <param name="size">Size of the icon to retrieve, in pixels.</param>
  345. /// <returns>Icon to display for the specified entry.</returns>
  346. private static SpriteTexture GetIcon(string path, int size)
  347. {
  348. LibraryEntry entry = ProjectLibrary.GetEntry(path);
  349. if (entry.Type == LibraryEntryType.Directory)
  350. {
  351. return EditorBuiltin.GetLibraryItemIcon(LibraryItemIcon.Folder, size);
  352. }
  353. else
  354. {
  355. ResourceMeta meta = ProjectLibrary.GetMeta(path);
  356. switch (meta.ResType)
  357. {
  358. case ResourceType.Font:
  359. return EditorBuiltin.GetLibraryItemIcon(LibraryItemIcon.Font, size);
  360. case ResourceType.Mesh:
  361. return EditorBuiltin.GetLibraryItemIcon(LibraryItemIcon.Mesh, size);
  362. case ResourceType.Texture:
  363. return EditorBuiltin.GetLibraryItemIcon(LibraryItemIcon.Texture, size);
  364. case ResourceType.PlainText:
  365. return EditorBuiltin.GetLibraryItemIcon(LibraryItemIcon.PlainText, size);
  366. case ResourceType.ScriptCode:
  367. return EditorBuiltin.GetLibraryItemIcon(LibraryItemIcon.ScriptCode, size);
  368. case ResourceType.SpriteTexture:
  369. return EditorBuiltin.GetLibraryItemIcon(LibraryItemIcon.SpriteTexture, size);
  370. case ResourceType.Shader:
  371. return EditorBuiltin.GetLibraryItemIcon(LibraryItemIcon.Shader, size);
  372. case ResourceType.ShaderInclude:
  373. return EditorBuiltin.GetLibraryItemIcon(LibraryItemIcon.Shader, size);
  374. case ResourceType.Material:
  375. return EditorBuiltin.GetLibraryItemIcon(LibraryItemIcon.Material, size);
  376. case ResourceType.Prefab:
  377. return EditorBuiltin.GetLibraryItemIcon(LibraryItemIcon.Prefab, size);
  378. case ResourceType.GUISkin:
  379. return EditorBuiltin.GetLibraryItemIcon(LibraryItemIcon.GUISkin, size);
  380. case ResourceType.PhysicsMaterial:
  381. return EditorBuiltin.GetLibraryItemIcon(LibraryItemIcon.PhysicsMaterial, size);
  382. case ResourceType.PhysicsMesh:
  383. return EditorBuiltin.GetLibraryItemIcon(LibraryItemIcon.PhysicsMesh, size);
  384. }
  385. }
  386. return null;
  387. }
  388. }
  389. /// <summary>
  390. /// Type of <see cref="LibraryGUIEntry"/> that controls its look.
  391. /// </summary>
  392. internal enum LibraryGUIEntryType
  393. {
  394. /// <summary>
  395. /// Represents a single resource.
  396. /// </summary>
  397. Single,
  398. /// <summary>
  399. /// Represents the first entry in a multi-resource group.
  400. /// </summary>
  401. MultiFirst,
  402. /// <summary>
  403. /// Represents one of the mid entries in a multi-resource group.
  404. /// </summary>
  405. MultiElement,
  406. /// <summary>
  407. /// Represents the last entry in a multi-resource group.
  408. /// </summary>
  409. MultiLast
  410. }
  411. }