//********************************** 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;
}
}
/** @} */
}