//********************************** Banshee Engine (www.banshee3d.com) **************************************************// //**************** Copyright (c) 2016 Marko Pintera (marko.pintera@gmail.com). All rights reserved. **********************// using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using bs; namespace bs.Editor { /** @addtogroup Library * @{ */ /// /// Manages GUI for the content area of the library window. Content area displays resources as a grid or list of /// resource icons. /// internal class LibraryGUIContent { internal const int TOP_MARGIN = 8; internal const int LIST_ENTRY_SPACING = 6; private GUIPanel mainPanel; private GUILayout main; private GUIPanel overlay; private GUIPanel underlay; private GUIPanel deepUnderlay; private GUIPanel renameOverlay; private LibraryWindow window; private GUIScrollArea parent; private int tileSize; private bool gridLayout; private int elementsPerRow; private int paddingLeft; private int paddingRight; private List entries = new List(); private Dictionary entryLookup = new Dictionary(); /// /// Area of the content area relative to the parent window. /// public Rect2I Bounds { get { return main.Bounds; } } /// /// Number of elements per row. Only relevant for grid layouts. /// public int ElementsPerRow { get { return elementsPerRow; } } /// /// Returns the padding to the left of the first element in a row of elements in a grid layout. /// public int PaddingLeft { get { return paddingLeft; } } /// /// Returns the padding to the right of the last element in a row of elements in a grid layout. /// public int PaddingRight { get { return paddingRight; } } /// /// Determines is the content display in a grid (true) or list (false) layout. /// public bool GridLayout { get { return gridLayout; } } /// /// Sizes of a single resource tile in grid layout. Size in list layout is fixed. /// public int TileSize { get { return tileSize; } } /// /// Returns objects representing each of the displayed resource icons. /// public List Entries { get { return entries; } } /// /// Returns parent window the content area is part of. /// public LibraryWindow Window { get { return window; } } /// /// Returns a GUI panel that can be used for displaying elements underneath the resource tiles. /// public GUIPanel Underlay { get { return underlay; } } /// /// Returns a GUI panel that can be used for displaying elements underneath the resource tiles. Displays under /// . /// public GUIPanel DeepUnderlay { get { return deepUnderlay; } } /// /// Returns a GUI panel that can be used for displaying elements above the resource tiles. /// public GUIPanel Overlay { get { return overlay; } } /// /// Returns a GUI panel that can be used for displaying rename input box. Displayed on top of the resource tiles /// and the standard overlay. /// public GUIPanel RenameOverlay { get { return renameOverlay; } } /// /// Constructs a new GUI library content object. /// /// Parent window the content area is part of. /// Scroll area the content area is part of. public LibraryGUIContent(LibraryWindow window, GUIScrollArea parent) { this.window = window; this.parent = parent; } /// /// Refreshes the contents of the content area. Must be called at least once after construction. /// /// Determines how to display the resource tiles. /// Project library entries to display. /// Bounds within which to lay out the content entries. public void Refresh(ProjectViewType viewType, LibraryEntry[] entriesToDisplay, Rect2I bounds) { if (mainPanel != null) mainPanel.Destroy(); entries.Clear(); entryLookup.Clear(); mainPanel = parent.Layout.AddPanel(); GUIPanel contentPanel = mainPanel.AddPanel(1); overlay = mainPanel.AddPanel(0); underlay = mainPanel.AddPanel(2); deepUnderlay = mainPanel.AddPanel(3); renameOverlay = mainPanel.AddPanel(-1); main = contentPanel.AddLayoutY(); List resourcesToDisplay = new List(); foreach (var entry in entriesToDisplay) { if (entry.Type == LibraryEntryType.Directory) resourcesToDisplay.Add(new ResourceToDisplay(entry.Path, LibraryGUIEntryType.Single)); else { FileEntry fileEntry = (FileEntry)entry; ResourceMeta[] metas = fileEntry.ResourceMetas; if (metas.Length > 0) { if (metas.Length == 1) resourcesToDisplay.Add(new ResourceToDisplay(entry.Path, LibraryGUIEntryType.Single)); else { resourcesToDisplay.Add(new ResourceToDisplay(entry.Path, LibraryGUIEntryType.MultiFirst)); for (int i = 1; i < metas.Length - 1; i++) { string path = Path.Combine(entry.Path, metas[i].SubresourceName); resourcesToDisplay.Add(new ResourceToDisplay(path, LibraryGUIEntryType.MultiElement)); } string lastPath = Path.Combine(entry.Path, metas[metas.Length - 1].SubresourceName); resourcesToDisplay.Add(new ResourceToDisplay(lastPath, LibraryGUIEntryType.MultiLast)); } } } } int minHorzElemSpacing = 0; if (viewType == ProjectViewType.List16) { tileSize = 16; gridLayout = false; elementsPerRow = 1; minHorzElemSpacing = 0; int elemWidth = bounds.width; int elemHeight = tileSize; main.AddSpace(TOP_MARGIN); for (int i = 0; i < resourcesToDisplay.Count; i++) { ResourceToDisplay entry = resourcesToDisplay[i]; LibraryGUIEntry guiEntry = new LibraryGUIEntry(this, main, entry.path, i, elemWidth, elemHeight, 0, entry.type); entries.Add(guiEntry); entryLookup[guiEntry.path] = guiEntry; if (i != resourcesToDisplay.Count - 1) main.AddSpace(LIST_ENTRY_SPACING); } main.AddFlexibleSpace(); } else { int elemWidth = 0; int elemHeight = 0; int vertElemSpacing = 0; switch (viewType) { case ProjectViewType.Grid64: tileSize = 64; elemWidth = tileSize; elemHeight = tileSize + 36; minHorzElemSpacing = 10; vertElemSpacing = 12; break; case ProjectViewType.Grid48: tileSize = 48; elemWidth = tileSize; elemHeight = tileSize + 36; minHorzElemSpacing = 8; vertElemSpacing = 10; break; case ProjectViewType.Grid32: tileSize = 32; elemWidth = tileSize + 16; elemHeight = tileSize + 48; minHorzElemSpacing = 6; vertElemSpacing = 10; break; } gridLayout = true; int availableWidth = bounds.width; elementsPerRow = MathEx.FloorToInt((availableWidth - minHorzElemSpacing) / (float)(elemWidth + minHorzElemSpacing)); int numRows = MathEx.CeilToInt(resourcesToDisplay.Count / (float)Math.Max(elementsPerRow, 1)); int neededHeight = numRows * elemHeight + TOP_MARGIN; if (numRows > 0) neededHeight += (numRows - 1)* vertElemSpacing; bool requiresScrollbar = neededHeight > bounds.height; if (requiresScrollbar) { availableWidth -= parent.ScrollBarWidth; elementsPerRow = MathEx.FloorToInt((availableWidth - minHorzElemSpacing) / (float)(elemWidth + minHorzElemSpacing)); } int extraRowSpace = availableWidth - minHorzElemSpacing * 2 - elementsPerRow * elemWidth; paddingLeft = minHorzElemSpacing; paddingRight = minHorzElemSpacing; float horzSpacing = 0.0f; // Distribute the spacing to the padding, as there are not in-between spaces to apply it to if (extraRowSpace > 0 && elementsPerRow < 2) { int extraPadding = extraRowSpace / 2; paddingLeft += extraPadding; paddingRight += (extraRowSpace - extraPadding); extraRowSpace = 0; } else horzSpacing = extraRowSpace / (float)(elementsPerRow - 1); elementsPerRow = Math.Max(elementsPerRow, 1); main.AddSpace(TOP_MARGIN); GUILayoutX rowLayout = main.AddLayoutX(); rowLayout.AddSpace(paddingLeft); float spacingCounter = 0.0f; int elemsInRow = 0; for (int i = 0; i < resourcesToDisplay.Count; i++) { if (elemsInRow == elementsPerRow && elemsInRow > 0) { main.AddSpace(vertElemSpacing); rowLayout = main.AddLayoutX(); rowLayout.AddSpace(paddingLeft); elemsInRow = 0; spacingCounter = 0.0f; } ResourceToDisplay entry = resourcesToDisplay[i]; elemsInRow++; if (elemsInRow != elementsPerRow) spacingCounter += horzSpacing; int spacing = (int)spacingCounter; spacingCounter -= spacing; LibraryGUIEntry guiEntry = new LibraryGUIEntry(this, rowLayout, entry.path, i, elemWidth, elemHeight, spacing, entry.type); entries.Add(guiEntry); entryLookup[guiEntry.path] = guiEntry; if (elemsInRow == elementsPerRow) rowLayout.AddSpace(paddingRight); rowLayout.AddSpace(spacing); } int extraElements = elementsPerRow - elemsInRow; int extraSpacing = 0; if (extraElements > 1) { spacingCounter += (extraElements - 1) * horzSpacing; extraSpacing += (int)spacingCounter; } if (extraElements > 0) rowLayout.AddSpace(elemWidth * extraElements + extraSpacing + paddingRight); main.AddFlexibleSpace(); } // Fix bounds as that makes GUI updates faster underlay.Bounds = main.Bounds; overlay.Bounds = main.Bounds; deepUnderlay.Bounds = main.Bounds; renameOverlay.Bounds = main.Bounds; for (int i = 0; i < entries.Count; i++) { LibraryGUIEntry guiEntry = entries[i]; guiEntry.Initialize(); } } /// /// Called every frame. /// public void Update() { for (int i = 0; i < entries.Count; i++) entries[i].Update(); } /// /// Changes the visual representation of an element at the specified path as being hovered over. /// /// Project library path to the element to mark. /// True if mark as hovered, false to reset to normal. public void MarkAsHovered(string path, bool hovered) { if (!string.IsNullOrEmpty(path)) { LibraryGUIEntry previousUnderCursorElem; if (entryLookup.TryGetValue(path, out previousUnderCursorElem)) previousUnderCursorElem.MarkAsHovered(hovered); } } /// /// Changes the visual representation of an element at the specified path as being pinged. /// /// Project library path to the element to mark. /// True if mark as pinged, false to reset to normal. public void MarkAsPinged(string path, bool pinged) { if (!string.IsNullOrEmpty(path)) { LibraryGUIEntry previousUnderCursorElem; if (entryLookup.TryGetValue(path, out previousUnderCursorElem)) previousUnderCursorElem.MarkAsPinged(pinged); } } /// /// Changes the visual representation of an element at the specified path as being cut. /// /// Project library path to the element to mark. /// True if mark as cut, false to reset to normal. public void MarkAsCut(string path, bool cut) { if (!string.IsNullOrEmpty(path)) { LibraryGUIEntry previousUnderCursorElem; if (entryLookup.TryGetValue(path, out previousUnderCursorElem)) previousUnderCursorElem.MarkAsCut(cut); } } /// /// Changes the visual representation of an element at the specified path as being selected. /// /// Project library path to the element to mark. /// True if mark as selected, false to reset to normal. public void MarkAsSelected(string path, bool selected) { if (!string.IsNullOrEmpty(path)) { LibraryGUIEntry previousUnderCursorElem; if (entryLookup.TryGetValue(path, out previousUnderCursorElem)) previousUnderCursorElem.MarkAsSelected(selected); } } /// /// Attempts to find a resource tile element at the specified coordinates. /// /// Coordinates relative to the scroll area the content area is part of. /// True if found an entry, false otherwise. public LibraryGUIEntry FindElementAt(Vector2I scrollPos) { foreach (var element in entries) { if (element.bounds.Contains(scrollPos)) return element; } return null; } /// /// Attempts to find all resource tile elements overlapping the specified bounds. /// /// Bounds to check for overlap, specified relative to the scroll area the content area /// is part of. /// A list of found entries. public LibraryGUIEntry[] FindElementsOverlapping(Rect2I scrollBounds) { List elements = new List(); foreach (var element in entries) { if (element.Bounds.Overlaps(scrollBounds)) elements.Add(element); } return elements.ToArray(); } /// /// Attempts to find a resource tile element with the specified path. /// /// Project library path to the element. /// Found element, or null if none found. /// True if an element was found, false otherwise. public bool TryGetEntry(string path, out LibraryGUIEntry entry) { return entryLookup.TryGetValue(path, out entry); } /// /// Helper structure containing information about a single entry to display in the library. /// private struct ResourceToDisplay { public ResourceToDisplay(string path, LibraryGUIEntryType type) { this.path = path; this.type = type; } public string path; public LibraryGUIEntryType type; } } /** @} */ }