//********************************** 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.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using BansheeEngine; namespace BansheeEditor { /// /// Represents GUI for a single resource tile used in . /// internal class LibraryGUIEntry { private const int MAX_LABEL_HEIGHT = 50; 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 SELECTION_EXTRA_WIDTH = 3; /// /// 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; private GUITexture underlay; private LibraryGUIContent owner; private UnderlayState underlayState; private GUITextBox renameTextBox; 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 labels that display the elements. public LibraryGUIEntry(LibraryGUIContent owner, GUILayout parent, string path, int index, int labelWidth) { GUILayout entryLayout; if (owner.GridLayout) entryLayout = parent.AddLayoutY(); else entryLayout = parent.AddLayoutX(); SpriteTexture iconTexture = GetIcon(path, owner.TileSize); icon = new GUITexture(iconTexture, GUIImageScaleMode.ScaleToFit, true, GUIOption.FixedHeight(owner.TileSize), GUIOption.FixedWidth(owner.TileSize)); label = null; string name = PathEx.GetTail(path); if (owner.GridLayout) { label = new GUILabel(name, EditorStyles.MultiLineLabelCentered, GUIOption.FixedWidth(labelWidth), GUIOption.FlexibleHeight(0, MAX_LABEL_HEIGHT)); } else { label = new GUILabel(name); } entryLayout.AddElement(icon); entryLayout.AddElement(label); this.owner = owner; this.index = index; this.path = path; this.bounds = new Rect2I(); this.underlay = null; } /// /// 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() { bounds = icon.Bounds; Rect2I labelBounds = label.Bounds; bounds.x = MathEx.Min(bounds.x, labelBounds.x - SELECTION_EXTRA_WIDTH); bounds.y = MathEx.Min(bounds.y, labelBounds.y) - 5; // 5 - Just padding for better look bounds.width = MathEx.Max(bounds.x + bounds.width, labelBounds.x + labelBounds.width) - bounds.x + SELECTION_EXTRA_WIDTH; bounds.height = MathEx.Max(bounds.y + bounds.height, labelBounds.y + labelBounds.height) - bounds.y; 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); owner.Overlay.AddElement(overlayBtn); } /// /// 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 (e.g. 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 (e.g. 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; } } } } /// /// 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.GetLibraryItemIcon(LibraryItemIcon.Folder, size); } else { ResourceMeta meta = ProjectLibrary.GetMeta(path); switch (meta.ResType) { case ResourceType.Font: return EditorBuiltin.GetLibraryItemIcon(LibraryItemIcon.Font, size); case ResourceType.Mesh: return EditorBuiltin.GetLibraryItemIcon(LibraryItemIcon.Mesh, size); case ResourceType.Texture: return EditorBuiltin.GetLibraryItemIcon(LibraryItemIcon.Texture, size); case ResourceType.PlainText: return EditorBuiltin.GetLibraryItemIcon(LibraryItemIcon.PlainText, size); case ResourceType.ScriptCode: return EditorBuiltin.GetLibraryItemIcon(LibraryItemIcon.ScriptCode, size); case ResourceType.SpriteTexture: return EditorBuiltin.GetLibraryItemIcon(LibraryItemIcon.SpriteTexture, size); case ResourceType.Shader: return EditorBuiltin.GetLibraryItemIcon(LibraryItemIcon.Shader, size); case ResourceType.ShaderInclude: return EditorBuiltin.GetLibraryItemIcon(LibraryItemIcon.Shader, size); case ResourceType.Material: return EditorBuiltin.GetLibraryItemIcon(LibraryItemIcon.Material, size); case ResourceType.Prefab: return EditorBuiltin.GetLibraryItemIcon(LibraryItemIcon.Prefab, size); case ResourceType.GUISkin: return EditorBuiltin.GetLibraryItemIcon(LibraryItemIcon.GUISkin, size); case ResourceType.PhysicsMaterial: return EditorBuiltin.GetLibraryItemIcon(LibraryItemIcon.PhysicsMaterial, size); case ResourceType.PhysicsMesh: return EditorBuiltin.GetLibraryItemIcon(LibraryItemIcon.PhysicsMesh, size); } } return null; } } }