LibraryGUIEntry.cs 18 KB

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