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