//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
//**************** Copyright (c) 2016 Marko Pintera (marko.pintera@gmail.com). All rights reserved. **********************//
using System.Diagnostics;
using System.IO;
using bs;
namespace bs.Editor
{
/** @addtogroup Library
* @{
*/
///
/// Represents GUI for a single resource tile used in .
///
internal class LibraryGUIEntry
{
private static readonly Color PING_COLOR = Color.BansheeOrange;
private static readonly Color SELECTION_COLOR = Color.DarkCyan;
private static readonly Color HOVER_COLOR = new Color(Color.DarkCyan.r, Color.DarkCyan.g, Color.DarkCyan.b, 0.5f);
private static readonly Color CUT_COLOR = new Color(1.0f, 1.0f, 1.0f, 0.5f);
private const int VERT_PADDING = 3;
private const int BG_HORZ_PADDING = 2;
private const int BG_VERT_PADDING = 2;
private const string LibraryEntryFirstBg = "LibraryEntryFirstBg";
private const string LibraryEntryBg = "LibraryEntryBg";
private const string LibraryEntryLastBg = "LibraryEntryLastBg";
private const string LibraryEntryVertFirstBg = "LibraryEntryVertFirstBg";
private const string LibraryEntryVertBg = "LibraryEntryVertBg";
private const string LibraryEntryVertLastBg = "LibraryEntryVertLastBg";
///
/// Possible visual states for the resource tile.
///
enum UnderlayState // Note: Order of these is relevant
{
None, Hovered, Selected, Pinged
}
public int index;
public string path;
public GUITexture icon;
public GUILabel label;
public Rect2I bounds;
public int spacing;
private GUITexture underlay;
private GUITexture groupUnderlay;
private LibraryGUIContent owner;
private UnderlayState underlayState;
private GUITextBox renameTextBox;
private int width;
private LibraryGUIEntryType type;
private bool delayedSelect;
private float delayedSelectTime;
private ulong delayedOpenCodeEditorFrame = ulong.MaxValue;
///
/// Bounds of the entry relative to part content area.
///
public Rect2I Bounds
{
get { return bounds; }
}
///
/// Constructs a new resource tile entry.
///
/// Content area this entry is part of.
/// Parent layout to add this entry's GUI elements to.
/// Path to the project library entry to display data for.
/// Sequential index of the entry in the conent area.
/// Width of the GUI entry.
/// Maximum allowed height for the label.
/// Spacing between this element and the next element on the same row. 0 if last.
/// Type of the entry, which controls its style and/or behaviour.
public LibraryGUIEntry(LibraryGUIContent owner, GUILayout parent, string path, int index, int width, int height,
int spacing, LibraryGUIEntryType type)
{
GUILayout entryLayout;
if (owner.GridLayout)
entryLayout = parent.AddLayoutY();
else
entryLayout = parent.AddLayoutX();
SpriteTexture iconTexture = GetIcon(path, owner.TileSize);
icon = new GUITexture(iconTexture, GUITextureScaleMode.ScaleToFit,
true, GUIOption.FixedHeight(owner.TileSize), GUIOption.FixedWidth(owner.TileSize));
label = null;
string name = PathEx.GetTail(path);
if (owner.GridLayout)
{
int labelHeight = height - owner.TileSize;
label = new GUILabel(name, EditorStyles.MultiLineLabelCentered,
GUIOption.FixedWidth(width), GUIOption.FixedHeight(labelHeight));
switch (type)
{
case LibraryGUIEntryType.Single:
break;
case LibraryGUIEntryType.MultiFirst:
groupUnderlay = new GUITexture(null, LibraryEntryFirstBg);
break;
case LibraryGUIEntryType.MultiElement:
groupUnderlay = new GUITexture(null, LibraryEntryBg);
break;
case LibraryGUIEntryType.MultiLast:
groupUnderlay = new GUITexture(null, LibraryEntryLastBg);
break;
}
}
else
{
label = new GUILabel(name, GUIOption.FixedWidth(width - owner.TileSize), GUIOption.FixedHeight(height));
switch (type)
{
case LibraryGUIEntryType.Single:
break;
case LibraryGUIEntryType.MultiFirst:
groupUnderlay = new GUITexture(null, LibraryEntryVertFirstBg);
break;
case LibraryGUIEntryType.MultiElement:
groupUnderlay = new GUITexture(null, LibraryEntryVertBg);
break;
case LibraryGUIEntryType.MultiLast:
groupUnderlay = new GUITexture(null, LibraryEntryVertLastBg);
break;
}
}
entryLayout.AddElement(icon);
entryLayout.AddElement(label);
if (groupUnderlay != null)
owner.DeepUnderlay.AddElement(groupUnderlay);
this.owner = owner;
this.index = index;
this.path = path;
this.bounds = new Rect2I();
this.underlay = null;
this.type = type;
this.width = width;
this.spacing = spacing;
}
///
/// Positions the GUI elements. Must be called after construction, but only after all content area entries have
/// been constructed so that entry's final bounds are known.
///
public void Initialize()
{
Rect2I iconBounds = icon.Bounds;
bounds = iconBounds;
Rect2I labelBounds = label.Bounds;
if (owner.GridLayout)
{
bounds.x = labelBounds.x;
bounds.y -= VERT_PADDING;
bounds.width = labelBounds.width;
bounds.height = (labelBounds.y + labelBounds.height + VERT_PADDING) - bounds.y;
}
else
{
bounds.y -= VERT_PADDING;
bounds.width = width;
bounds.height += VERT_PADDING;
}
string hoistedPath = path;
GUIButton overlayBtn = new GUIButton("", EditorStyles.Blank);
overlayBtn.Bounds = bounds;
overlayBtn.OnClick += () => OnEntryClicked(hoistedPath);
overlayBtn.OnDoubleClick += () => OnEntryDoubleClicked(hoistedPath);
overlayBtn.SetContextMenu(owner.Window.ContextMenu);
overlayBtn.AcceptsKeyFocus = false;
owner.Overlay.AddElement(overlayBtn);
if (groupUnderlay != null)
{
if (owner.GridLayout)
{
bool firstInRow = index % owner.ElementsPerRow == 0;
bool lastInRow = index % owner.ElementsPerRow == (owner.ElementsPerRow - 1);
int offsetToPrevious = 0;
if (type == LibraryGUIEntryType.MultiFirst)
{
if (firstInRow)
offsetToPrevious = owner.PaddingLeft / 3;
else
offsetToPrevious = spacing / 3;
}
else if (firstInRow)
offsetToPrevious = owner.PaddingLeft;
int offsetToNext = spacing;
if (type == LibraryGUIEntryType.MultiLast)
{
if (lastInRow)
offsetToNext = owner.PaddingRight / 3;
else
offsetToNext = spacing / 3;
}
else if (lastInRow)
offsetToNext = owner.PaddingRight + spacing;
Rect2I bgBounds = new Rect2I(bounds.x - offsetToPrevious, bounds.y,
bounds.width + offsetToNext + offsetToPrevious, bounds.height);
groupUnderlay.Bounds = bgBounds;
}
else
{
int offsetToNext = BG_VERT_PADDING + LibraryGUIContent.LIST_ENTRY_SPACING;
if (type == LibraryGUIEntryType.MultiLast)
offsetToNext = BG_VERT_PADDING * 2;
Rect2I bgBounds = new Rect2I(bounds.x, bounds.y - BG_VERT_PADDING,
bounds.width, bounds.height + offsetToNext);
groupUnderlay.Bounds = bgBounds;
}
}
}
///
/// Called every frame.
///
public void Update()
{
if (delayedSelect && Time.RealElapsed > delayedSelectTime)
{
owner.Window.Select(path);
delayedSelect = false;
}
if (delayedOpenCodeEditorFrame == Time.FrameIdx)
{
LibraryEntry entry = ProjectLibrary.GetEntry(path);
if (entry != null && entry.Type == LibraryEntryType.File)
{
FileEntry resEntry = (FileEntry) entry;
CodeEditor.OpenFile(resEntry.Path, 0);
}
ProgressBar.Hide();
}
}
///
/// Changes the visual representation of the element as being cut.
///
/// True if mark as cut, false to reset to normal.
public void MarkAsCut(bool enable)
{
if (enable)
icon.SetTint(CUT_COLOR);
else
icon.SetTint(Color.White);
}
///
/// Changes the visual representation of the element as being selected.
///
/// True if mark as selected, false to reset to normal.
public void MarkAsSelected(bool enable)
{
if ((int)underlayState > (int)UnderlayState.Selected)
return;
if (enable)
{
CreateUnderlay();
underlay.SetTint(SELECTION_COLOR);
underlayState = UnderlayState.Selected;
}
else
{
ClearUnderlay();
underlayState = UnderlayState.None;
}
}
///
/// Changes the visual representation of the element as being pinged.
///
/// True if mark as pinged, false to reset to normal.
public void MarkAsPinged(bool enable)
{
if ((int)underlayState > (int)UnderlayState.Pinged)
return;
if (enable)
{
CreateUnderlay();
underlay.SetTint(PING_COLOR);
underlayState = UnderlayState.Pinged;
}
else
{
ClearUnderlay();
underlayState = UnderlayState.None;
}
}
///
/// Changes the visual representation of the element as being hovered over.
///
/// True if mark as hovered, false to reset to normal.
public void MarkAsHovered(bool enable)
{
if ((int)underlayState > (int)UnderlayState.Hovered)
return;
if (enable)
{
CreateUnderlay();
underlay.SetTint(HOVER_COLOR);
underlayState = UnderlayState.Hovered;
}
else
{
ClearUnderlay();
underlayState = UnderlayState.None;
}
}
///
/// Starts a rename operation over the entry, displaying the rename input box.
///
public void StartRename()
{
if (renameTextBox != null)
return;
renameTextBox = new GUITextBox(true);
Rect2I renameBounds = label.Bounds;
// Rename box allows for less space for text than label, so adjust it slightly so it's more likely to be able
// to display all visible text.
renameBounds.x -= 4;
renameBounds.width += 8;
renameBounds.height += 8;
renameTextBox.Bounds = renameBounds;
owner.RenameOverlay.AddElement(renameTextBox);
string name = Path.GetFileNameWithoutExtension(PathEx.GetTail(path));
renameTextBox.Text = name;
renameTextBox.Focus = true;
}
///
/// Stops a rename operation over the entry, hiding the rename input box.
///
public void StopRename()
{
if (renameTextBox != null)
{
renameTextBox.Destroy();
renameTextBox = null;
}
}
///
/// Gets the new name of the entry. Only valid while a rename operation is in progress.
///
/// New name of the entry currently entered in the rename input box.
public string GetRenamedName()
{
if (renameTextBox != null)
return renameTextBox.Text;
return "";
}
///
/// Clears the underlay GUI element (for example ping, hover, select).
///
private void ClearUnderlay()
{
if (underlay != null)
{
underlay.Destroy();
underlay = null;
}
underlayState = UnderlayState.None;
}
///
/// Creates a GUI elements that may be used for underlay effects (for example ping, hover, select).
///
private void CreateUnderlay()
{
if (underlay == null)
{
underlay = new GUITexture(Builtin.WhiteTexture);
underlay.Bounds = Bounds;
owner.Underlay.AddElement(underlay);
}
}
///
/// Triggered when the user clicks on the entry.
///
/// Project library path of the clicked entry.
private void OnEntryClicked(string path)
{
LibraryEntry entry = ProjectLibrary.GetEntry(path);
if (entry != null && entry.Type == LibraryEntryType.Directory)
{
// If entry is a directory delay selection as it might be a double-click, in which case we want to keep
// whatever selection is active currently so that user can perform drag and drop with its inspector
// from the folder he is browsing to.
delayedSelect = true;
delayedSelectTime = Time.RealElapsed + 0.5f;
}
else
owner.Window.Select(path);
}
///
/// Triggered when the user double-clicked on the entry.
///
/// Project library path of the double-clicked entry.
private void OnEntryDoubleClicked(string path)
{
delayedSelect = false;
LibraryEntry entry = ProjectLibrary.GetEntry(path);
if (entry != null)
{
if (entry.Type == LibraryEntryType.Directory)
owner.Window.EnterDirectory(path);
else
{
ResourceMeta meta = ProjectLibrary.GetMeta(path);
FileEntry fileEntry = (FileEntry)entry;
if (meta.ResType == ResourceType.Prefab)
{
EditorApplication.LoadScene(fileEntry.Path);
}
else if (meta.ResType == ResourceType.ScriptCode)
{
ProgressBar.Show("Opening external code editor...", 1.0f);
delayedOpenCodeEditorFrame = Time.FrameIdx + 1;
}
else if(meta.ResType == ResourceType.PlainText || meta.ResType == ResourceType.Shader || meta.ResType == ResourceType.ShaderInclude)
{
string absPath = Path.Combine(ProjectLibrary.ResourceFolder, fileEntry.Path);
Process.Start(absPath);
}
}
}
}
///
/// Returns an icon that can be used for displaying a resource of the specified type.
///
/// Path to the project library entry to display data for.
/// Size of the icon to retrieve, in pixels.
/// Icon to display for the specified entry.
private static SpriteTexture GetIcon(string path, int size)
{
LibraryEntry entry = ProjectLibrary.GetEntry(path);
if (entry.Type == LibraryEntryType.Directory)
{
return EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.Folder, size);
}
else
{
ResourceMeta meta = ProjectLibrary.GetMeta(path);
ProjectResourceIcons icons = meta.Icons;
RRef icon;
if (size <= 16)
icon = icons.icon16;
else if (size <= 32)
icon = icons.icon32;
else if (size <= 48)
icon = icons.icon48;
else
icon = icons.icon64;
if (icon.Value != null)
return new SpriteTexture(icon);
switch (meta.ResType)
{
case ResourceType.Font:
return EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.Font, size);
case ResourceType.Mesh:
return EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.Mesh, size);
case ResourceType.Texture:
return EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.Texture, size);
case ResourceType.PlainText:
return EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.PlainText, size);
case ResourceType.ScriptCode:
return EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.ScriptCode, size);
case ResourceType.SpriteTexture:
return EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.SpriteTexture, size);
case ResourceType.Shader:
return EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.Shader, size);
case ResourceType.ShaderInclude:
return EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.Shader, size);
case ResourceType.Material:
return EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.Material, size);
case ResourceType.Prefab:
return EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.Prefab, size);
case ResourceType.GUISkin:
return EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.GUISkin, size);
case ResourceType.PhysicsMaterial:
return EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.PhysicsMaterial, size);
case ResourceType.PhysicsMesh:
return EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.PhysicsMesh, size);
case ResourceType.AudioClip:
return EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.AudioClip, size);
case ResourceType.AnimationClip:
return EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.AnimationClip, size);
case ResourceType.VectorField:
return EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.VectorField, size);
}
}
return null;
}
}
///
/// Type of that controls its look.
///
internal enum LibraryGUIEntryType
{
///
/// Represents a single resource.
///
Single,
///
/// Represents the first entry in a multi-resource group.
///
MultiFirst,
///
/// Represents one of the mid entries in a multi-resource group.
///
MultiElement,
///
/// Represents the last entry in a multi-resource group.
///
MultiLast
}
/** @} */
}