LibraryGUIContent.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Diagnostics;
  6. using System.IO;
  7. using bs;
  8. namespace bs.Editor
  9. {
  10. /** @addtogroup Library
  11. * @{
  12. */
  13. /// <summary>
  14. /// Manages GUI for the content area of the library window. Content area displays resources as a grid or list of
  15. /// resource icons.
  16. /// </summary>
  17. internal class LibraryGUIContent
  18. {
  19. internal const int TOP_MARGIN = 8;
  20. internal const int LIST_ENTRY_SPACING = 6;
  21. private GUIPanel mainPanel;
  22. private GUILayout main;
  23. private GUIPanel overlay;
  24. private GUIPanel underlay;
  25. private GUIPanel deepUnderlay;
  26. private GUIPanel renameOverlay;
  27. private LibraryWindow window;
  28. private GUIScrollArea parent;
  29. private int tileSize;
  30. private bool gridLayout;
  31. private int elementsPerRow;
  32. private int paddingLeft;
  33. private int paddingRight;
  34. private List<LibraryGUIEntry> entries = new List<LibraryGUIEntry>();
  35. private Dictionary<string, LibraryGUIEntry> entryLookup = new Dictionary<string, LibraryGUIEntry>();
  36. /// <summary>
  37. /// Area of the content area relative to the parent window.
  38. /// </summary>
  39. public Rect2I Bounds
  40. {
  41. get { return main.Bounds; }
  42. }
  43. /// <summary>
  44. /// Number of elements per row. Only relevant for grid layouts.
  45. /// </summary>
  46. public int ElementsPerRow
  47. {
  48. get { return elementsPerRow; }
  49. }
  50. /// <summary>
  51. /// Returns the padding to the left of the first element in a row of elements in a grid layout.
  52. /// </summary>
  53. public int PaddingLeft
  54. {
  55. get { return paddingLeft; }
  56. }
  57. /// <summary>
  58. /// Returns the padding to the right of the last element in a row of elements in a grid layout.
  59. /// </summary>
  60. public int PaddingRight
  61. {
  62. get { return paddingRight; }
  63. }
  64. /// <summary>
  65. /// Determines is the content display in a grid (true) or list (false) layout.
  66. /// </summary>
  67. public bool GridLayout
  68. {
  69. get { return gridLayout; }
  70. }
  71. /// <summary>
  72. /// Sizes of a single resource tile in grid layout. Size in list layout is fixed.
  73. /// </summary>
  74. public int TileSize
  75. {
  76. get { return tileSize; }
  77. }
  78. /// <summary>
  79. /// Returns objects representing each of the displayed resource icons.
  80. /// </summary>
  81. public List<LibraryGUIEntry> Entries
  82. {
  83. get { return entries; }
  84. }
  85. /// <summary>
  86. /// Returns parent window the content area is part of.
  87. /// </summary>
  88. public LibraryWindow Window
  89. {
  90. get { return window; }
  91. }
  92. /// <summary>
  93. /// Returns a GUI panel that can be used for displaying elements underneath the resource tiles.
  94. /// </summary>
  95. public GUIPanel Underlay
  96. {
  97. get { return underlay; }
  98. }
  99. /// <summary>
  100. /// Returns a GUI panel that can be used for displaying elements underneath the resource tiles. Displays under
  101. /// <see cref="Underlay"/>.
  102. /// </summary>
  103. public GUIPanel DeepUnderlay
  104. {
  105. get { return deepUnderlay; }
  106. }
  107. /// <summary>
  108. /// Returns a GUI panel that can be used for displaying elements above the resource tiles.
  109. /// </summary>
  110. public GUIPanel Overlay
  111. {
  112. get { return overlay; }
  113. }
  114. /// <summary>
  115. /// Returns a GUI panel that can be used for displaying rename input box. Displayed on top of the resource tiles
  116. /// and the standard overlay.
  117. /// </summary>
  118. public GUIPanel RenameOverlay
  119. {
  120. get { return renameOverlay; }
  121. }
  122. /// <summary>
  123. /// Constructs a new GUI library content object.
  124. /// </summary>
  125. /// <param name="window">Parent window the content area is part of.</param>
  126. /// <param name="parent">Scroll area the content area is part of.</param>
  127. public LibraryGUIContent(LibraryWindow window, GUIScrollArea parent)
  128. {
  129. this.window = window;
  130. this.parent = parent;
  131. }
  132. /// <summary>
  133. /// Refreshes the contents of the content area. Must be called at least once after construction.
  134. /// </summary>
  135. /// <param name="viewType">Determines how to display the resource tiles.</param>
  136. /// <param name="entriesToDisplay">Project library entries to display.</param>
  137. /// <param name="bounds">Bounds within which to lay out the content entries.</param>
  138. public void Refresh(ProjectViewType viewType, LibraryEntry[] entriesToDisplay, Rect2I bounds)
  139. {
  140. if (mainPanel != null)
  141. mainPanel.Destroy();
  142. entries.Clear();
  143. entryLookup.Clear();
  144. mainPanel = parent.Layout.AddPanel();
  145. GUIPanel contentPanel = mainPanel.AddPanel(1);
  146. overlay = mainPanel.AddPanel(0);
  147. underlay = mainPanel.AddPanel(2);
  148. deepUnderlay = mainPanel.AddPanel(3);
  149. renameOverlay = mainPanel.AddPanel(-1);
  150. main = contentPanel.AddLayoutY();
  151. List<ResourceToDisplay> resourcesToDisplay = new List<ResourceToDisplay>();
  152. foreach (var entry in entriesToDisplay)
  153. {
  154. if (entry.Type == LibraryEntryType.Directory)
  155. resourcesToDisplay.Add(new ResourceToDisplay(entry.Path, LibraryGUIEntryType.Single));
  156. else
  157. {
  158. FileEntry fileEntry = (FileEntry)entry;
  159. ResourceMeta[] metas = fileEntry.ResourceMetas;
  160. if (metas.Length > 0)
  161. {
  162. if (metas.Length == 1)
  163. resourcesToDisplay.Add(new ResourceToDisplay(entry.Path, LibraryGUIEntryType.Single));
  164. else
  165. {
  166. resourcesToDisplay.Add(new ResourceToDisplay(entry.Path, LibraryGUIEntryType.MultiFirst));
  167. for (int i = 1; i < metas.Length - 1; i++)
  168. {
  169. string path = Path.Combine(entry.Path, metas[i].SubresourceName);
  170. resourcesToDisplay.Add(new ResourceToDisplay(path, LibraryGUIEntryType.MultiElement));
  171. }
  172. string lastPath = Path.Combine(entry.Path, metas[metas.Length - 1].SubresourceName);
  173. resourcesToDisplay.Add(new ResourceToDisplay(lastPath, LibraryGUIEntryType.MultiLast));
  174. }
  175. }
  176. }
  177. }
  178. int minHorzElemSpacing = 0;
  179. if (viewType == ProjectViewType.List16)
  180. {
  181. tileSize = 16;
  182. gridLayout = false;
  183. elementsPerRow = 1;
  184. minHorzElemSpacing = 0;
  185. int elemWidth = bounds.width;
  186. int elemHeight = tileSize;
  187. main.AddSpace(TOP_MARGIN);
  188. for (int i = 0; i < resourcesToDisplay.Count; i++)
  189. {
  190. ResourceToDisplay entry = resourcesToDisplay[i];
  191. LibraryGUIEntry guiEntry = new LibraryGUIEntry(this, main, entry.path, i, elemWidth, elemHeight, 0,
  192. entry.type);
  193. entries.Add(guiEntry);
  194. entryLookup[guiEntry.path] = guiEntry;
  195. if (i != resourcesToDisplay.Count - 1)
  196. main.AddSpace(LIST_ENTRY_SPACING);
  197. }
  198. main.AddFlexibleSpace();
  199. }
  200. else
  201. {
  202. int elemWidth = 0;
  203. int elemHeight = 0;
  204. int vertElemSpacing = 0;
  205. switch (viewType)
  206. {
  207. case ProjectViewType.Grid64:
  208. tileSize = 64;
  209. elemWidth = tileSize;
  210. elemHeight = tileSize + 36;
  211. minHorzElemSpacing = 10;
  212. vertElemSpacing = 12;
  213. break;
  214. case ProjectViewType.Grid48:
  215. tileSize = 48;
  216. elemWidth = tileSize;
  217. elemHeight = tileSize + 36;
  218. minHorzElemSpacing = 8;
  219. vertElemSpacing = 10;
  220. break;
  221. case ProjectViewType.Grid32:
  222. tileSize = 32;
  223. elemWidth = tileSize + 16;
  224. elemHeight = tileSize + 48;
  225. minHorzElemSpacing = 6;
  226. vertElemSpacing = 10;
  227. break;
  228. }
  229. gridLayout = true;
  230. int availableWidth = bounds.width;
  231. elementsPerRow = MathEx.FloorToInt((availableWidth - minHorzElemSpacing) / (float)(elemWidth + minHorzElemSpacing));
  232. int numRows = MathEx.CeilToInt(resourcesToDisplay.Count / (float)Math.Max(elementsPerRow, 1));
  233. int neededHeight = numRows * elemHeight + TOP_MARGIN;
  234. if (numRows > 0)
  235. neededHeight += (numRows - 1)* vertElemSpacing;
  236. bool requiresScrollbar = neededHeight > bounds.height;
  237. if (requiresScrollbar)
  238. {
  239. availableWidth -= parent.ScrollBarWidth;
  240. elementsPerRow = MathEx.FloorToInt((availableWidth - minHorzElemSpacing) / (float)(elemWidth + minHorzElemSpacing));
  241. }
  242. int extraRowSpace = availableWidth - minHorzElemSpacing * 2 - elementsPerRow * elemWidth;
  243. paddingLeft = minHorzElemSpacing;
  244. paddingRight = minHorzElemSpacing;
  245. float horzSpacing = 0.0f;
  246. // Distribute the spacing to the padding, as there are not in-between spaces to apply it to
  247. if (extraRowSpace > 0 && elementsPerRow < 2)
  248. {
  249. int extraPadding = extraRowSpace / 2;
  250. paddingLeft += extraPadding;
  251. paddingRight += (extraRowSpace - extraPadding);
  252. extraRowSpace = 0;
  253. }
  254. else
  255. horzSpacing = extraRowSpace / (float)(elementsPerRow - 1);
  256. elementsPerRow = Math.Max(elementsPerRow, 1);
  257. main.AddSpace(TOP_MARGIN);
  258. GUILayoutX rowLayout = main.AddLayoutX();
  259. rowLayout.AddSpace(paddingLeft);
  260. float spacingCounter = 0.0f;
  261. int elemsInRow = 0;
  262. for (int i = 0; i < resourcesToDisplay.Count; i++)
  263. {
  264. if (elemsInRow == elementsPerRow && elemsInRow > 0)
  265. {
  266. main.AddSpace(vertElemSpacing);
  267. rowLayout = main.AddLayoutX();
  268. rowLayout.AddSpace(paddingLeft);
  269. elemsInRow = 0;
  270. spacingCounter = 0.0f;
  271. }
  272. ResourceToDisplay entry = resourcesToDisplay[i];
  273. elemsInRow++;
  274. if (elemsInRow != elementsPerRow)
  275. spacingCounter += horzSpacing;
  276. int spacing = (int)spacingCounter;
  277. spacingCounter -= spacing;
  278. LibraryGUIEntry guiEntry = new LibraryGUIEntry(this, rowLayout, entry.path, i, elemWidth, elemHeight,
  279. spacing, entry.type);
  280. entries.Add(guiEntry);
  281. entryLookup[guiEntry.path] = guiEntry;
  282. if (elemsInRow == elementsPerRow)
  283. rowLayout.AddSpace(paddingRight);
  284. rowLayout.AddSpace(spacing);
  285. }
  286. int extraElements = elementsPerRow - elemsInRow;
  287. int extraSpacing = 0;
  288. if (extraElements > 1)
  289. {
  290. spacingCounter += (extraElements - 1) * horzSpacing;
  291. extraSpacing += (int)spacingCounter;
  292. }
  293. if (extraElements > 0)
  294. rowLayout.AddSpace(elemWidth * extraElements + extraSpacing + paddingRight);
  295. main.AddFlexibleSpace();
  296. }
  297. // Fix bounds as that makes GUI updates faster
  298. underlay.Bounds = main.Bounds;
  299. overlay.Bounds = main.Bounds;
  300. deepUnderlay.Bounds = main.Bounds;
  301. renameOverlay.Bounds = main.Bounds;
  302. for (int i = 0; i < entries.Count; i++)
  303. {
  304. LibraryGUIEntry guiEntry = entries[i];
  305. guiEntry.Initialize();
  306. }
  307. }
  308. /// <summary>
  309. /// Called every frame.
  310. /// </summary>
  311. public void Update()
  312. {
  313. for (int i = 0; i < entries.Count; i++)
  314. entries[i].Update();
  315. }
  316. /// <summary>
  317. /// Changes the visual representation of an element at the specified path as being hovered over.
  318. /// </summary>
  319. /// <param name="path">Project library path to the element to mark.</param>
  320. /// <param name="hovered">True if mark as hovered, false to reset to normal.</param>
  321. public void MarkAsHovered(string path, bool hovered)
  322. {
  323. if (!string.IsNullOrEmpty(path))
  324. {
  325. LibraryGUIEntry previousUnderCursorElem;
  326. if (entryLookup.TryGetValue(path, out previousUnderCursorElem))
  327. previousUnderCursorElem.MarkAsHovered(hovered);
  328. }
  329. }
  330. /// <summary>
  331. /// Changes the visual representation of an element at the specified path as being pinged.
  332. /// </summary>
  333. /// <param name="path">Project library path to the element to mark.</param>
  334. /// <param name="pinged">True if mark as pinged, false to reset to normal.</param>
  335. public void MarkAsPinged(string path, bool pinged)
  336. {
  337. if (!string.IsNullOrEmpty(path))
  338. {
  339. LibraryGUIEntry previousUnderCursorElem;
  340. if (entryLookup.TryGetValue(path, out previousUnderCursorElem))
  341. previousUnderCursorElem.MarkAsPinged(pinged);
  342. }
  343. }
  344. /// <summary>
  345. /// Changes the visual representation of an element at the specified path as being cut.
  346. /// </summary>
  347. /// <param name="path">Project library path to the element to mark.</param>
  348. /// <param name="cut">True if mark as cut, false to reset to normal.</param>
  349. public void MarkAsCut(string path, bool cut)
  350. {
  351. if (!string.IsNullOrEmpty(path))
  352. {
  353. LibraryGUIEntry previousUnderCursorElem;
  354. if (entryLookup.TryGetValue(path, out previousUnderCursorElem))
  355. previousUnderCursorElem.MarkAsCut(cut);
  356. }
  357. }
  358. /// <summary>
  359. /// Changes the visual representation of an element at the specified path as being selected.
  360. /// </summary>
  361. /// <param name="path">Project library path to the element to mark.</param>
  362. /// <param name="selected">True if mark as selected, false to reset to normal.</param>
  363. public void MarkAsSelected(string path, bool selected)
  364. {
  365. if (!string.IsNullOrEmpty(path))
  366. {
  367. LibraryGUIEntry previousUnderCursorElem;
  368. if (entryLookup.TryGetValue(path, out previousUnderCursorElem))
  369. previousUnderCursorElem.MarkAsSelected(selected);
  370. }
  371. }
  372. /// <summary>
  373. /// Attempts to find a resource tile element at the specified coordinates.
  374. /// </summary>
  375. /// <param name="scrollPos">Coordinates relative to the scroll area the content area is part of.</param>
  376. /// <returns>True if found an entry, false otherwise.</returns>
  377. public LibraryGUIEntry FindElementAt(Vector2I scrollPos)
  378. {
  379. foreach (var element in entries)
  380. {
  381. if (element.bounds.Contains(scrollPos))
  382. return element;
  383. }
  384. return null;
  385. }
  386. /// <summary>
  387. /// Attempts to find all resource tile elements overlapping the specified bounds.
  388. /// </summary>
  389. /// <param name="scrollBounds">Bounds to check for overlap, specified relative to the scroll area the content area
  390. /// is part of.</param>
  391. /// <returns>A list of found entries.</returns>
  392. public LibraryGUIEntry[] FindElementsOverlapping(Rect2I scrollBounds)
  393. {
  394. List<LibraryGUIEntry> elements = new List<LibraryGUIEntry>();
  395. foreach (var element in entries)
  396. {
  397. if (element.Bounds.Overlaps(scrollBounds))
  398. elements.Add(element);
  399. }
  400. return elements.ToArray();
  401. }
  402. /// <summary>
  403. /// Attempts to find a resource tile element with the specified path.
  404. /// </summary>
  405. /// <param name="path">Project library path to the element.</param>
  406. /// <param name="entry">Found element, or null if none found.</param>
  407. /// <returns>True if an element was found, false otherwise.</returns>
  408. public bool TryGetEntry(string path, out LibraryGUIEntry entry)
  409. {
  410. return entryLookup.TryGetValue(path, out entry);
  411. }
  412. /// <summary>
  413. /// Helper structure containing information about a single entry to display in the library.
  414. /// </summary>
  415. private struct ResourceToDisplay
  416. {
  417. public ResourceToDisplay(string path, LibraryGUIEntryType type)
  418. {
  419. this.path = path;
  420. this.type = type;
  421. }
  422. public string path;
  423. public LibraryGUIEntryType type;
  424. }
  425. }
  426. /** @} */
  427. }