1647 lines
60 KiB
C#
1647 lines
60 KiB
C#
|
using System;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.Linq;
|
|||
|
using System.Text;
|
|||
|
using UnityEditor;
|
|||
|
using UnityEditor.IMGUI.Controls;
|
|||
|
using UnityEngine;
|
|||
|
using System.IO;
|
|||
|
|
|||
|
namespace FMODUnity
|
|||
|
{
|
|||
|
class EventBrowser : EditorWindow, ISerializationCallbackReceiver
|
|||
|
{
|
|||
|
[MenuItem("FMOD/Event Browser", priority = 2)]
|
|||
|
public static void ShowWindow()
|
|||
|
{
|
|||
|
EventBrowser eventBrowser = GetWindow<EventBrowser>("FMOD Events");
|
|||
|
eventBrowser.minSize = new Vector2(380, 600);
|
|||
|
|
|||
|
eventBrowser.BeginStandaloneWindow();
|
|||
|
eventBrowser.Show();
|
|||
|
}
|
|||
|
|
|||
|
public static bool IsOpen
|
|||
|
{
|
|||
|
get; private set;
|
|||
|
}
|
|||
|
|
|||
|
public void OnBeforeSerialize()
|
|||
|
{
|
|||
|
treeViewState = treeView.state;
|
|||
|
}
|
|||
|
|
|||
|
public void OnAfterDeserialize()
|
|||
|
{
|
|||
|
}
|
|||
|
|
|||
|
[NonSerialized]
|
|||
|
float nextRepaintTime;
|
|||
|
|
|||
|
[NonSerialized]
|
|||
|
float[] cachedMetering;
|
|||
|
|
|||
|
const float RepaintInterval = 1/30.0f;
|
|||
|
|
|||
|
void Update()
|
|||
|
{
|
|||
|
bool forceRepaint = false;
|
|||
|
|
|||
|
float[] currentMetering = EditorUtils.GetMetering();
|
|||
|
if (cachedMetering == null || !cachedMetering.SequenceEqual(currentMetering))
|
|||
|
{
|
|||
|
cachedMetering = currentMetering;
|
|||
|
forceRepaint = true;
|
|||
|
}
|
|||
|
|
|||
|
if (LastKnownCacheTime != EventManager.CacheTime)
|
|||
|
{
|
|||
|
ReadEventCache();
|
|||
|
forceRepaint = true;
|
|||
|
}
|
|||
|
|
|||
|
if (forceRepaint || (previewArea != null && previewArea.forceRepaint && nextRepaintTime < Time.realtimeSinceStartup))
|
|||
|
{
|
|||
|
Repaint();
|
|||
|
nextRepaintTime = Time.time + RepaintInterval;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
void ReadEventCache()
|
|||
|
{
|
|||
|
LastKnownCacheTime = EventManager.CacheTime;
|
|||
|
treeView.Reload();
|
|||
|
}
|
|||
|
|
|||
|
class TreeView : UnityEditor.IMGUI.Controls.TreeView
|
|||
|
{
|
|||
|
public TreeView(State state) : base(state.baseState)
|
|||
|
{
|
|||
|
noSearchExpandState = state.noSearchExpandState;
|
|||
|
SelectedObject = state.selectedObject;
|
|||
|
TypeFilter = state.typeFilter;
|
|||
|
DragEnabled = state.dragEnabled;
|
|||
|
|
|||
|
for (int i = 0; i < state.itemPaths.Count; ++i)
|
|||
|
{
|
|||
|
itemIDs.Add(state.itemPaths[i], state.itemIDs[i]);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public void JumpToEvent(string path)
|
|||
|
{
|
|||
|
JumpToItem(path);
|
|||
|
}
|
|||
|
|
|||
|
public void JumpToBank(string name)
|
|||
|
{
|
|||
|
JumpToItem(BankPrefix + name);
|
|||
|
}
|
|||
|
|
|||
|
private void JumpToItem(string path)
|
|||
|
{
|
|||
|
nextFramedItemPath = path;
|
|||
|
Reload();
|
|||
|
|
|||
|
int itemID;
|
|||
|
if (itemIDs.TryGetValue(path, out itemID))
|
|||
|
{
|
|||
|
SetSelection(new List<int> { itemID },
|
|||
|
TreeViewSelectionOptions.RevealAndFrame | TreeViewSelectionOptions.FireSelectionChanged);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
SetSelection(new List<int>());
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private static readonly Texture2D folderOpenIcon = EditorGUIUtility.Load("FMOD/FolderIconOpen.png") as Texture2D;
|
|||
|
private static readonly Texture2D folderClosedIcon = EditorGUIUtility.Load("FMOD/FolderIconClosed.png") as Texture2D;
|
|||
|
private static readonly Texture2D eventIcon = EditorGUIUtility.Load("FMOD/EventIcon.png") as Texture2D;
|
|||
|
private static readonly Texture2D snapshotIcon = EditorGUIUtility.Load("FMOD/SnapshotIcon.png") as Texture2D;
|
|||
|
private static readonly Texture2D bankIcon = EditorGUIUtility.Load("FMOD/BankIcon.png") as Texture2D;
|
|||
|
private static readonly Texture2D parameterIcon = EditorGUIUtility.Load("FMOD/EventIcon.png") as Texture2D;
|
|||
|
|
|||
|
private class LeafItem : TreeViewItem
|
|||
|
{
|
|||
|
public LeafItem(int id, int depth, ScriptableObject data)
|
|||
|
: base(id, depth)
|
|||
|
{
|
|||
|
Data = data;
|
|||
|
}
|
|||
|
|
|||
|
public ScriptableObject Data;
|
|||
|
}
|
|||
|
|
|||
|
class FolderItem : TreeViewItem
|
|||
|
{
|
|||
|
public FolderItem(int id, int depth, string displayName)
|
|||
|
: base(id, depth, displayName)
|
|||
|
{
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private FolderItem CreateFolderItem(string name, string path, bool hasChildren, bool forceExpanded,
|
|||
|
TreeViewItem parent)
|
|||
|
{
|
|||
|
FolderItem item = new FolderItem(AffirmItemID("folder:" + path), 0, name);
|
|||
|
|
|||
|
bool expanded;
|
|||
|
|
|||
|
if (!hasChildren)
|
|||
|
{
|
|||
|
expanded = false;
|
|||
|
}
|
|||
|
else if (forceExpanded || expandNextFolderSet
|
|||
|
|| (nextFramedItemPath != null && nextFramedItemPath.StartsWith(path)))
|
|||
|
{
|
|||
|
SetExpanded(item.id, true);
|
|||
|
expanded = true;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
expanded = IsExpanded(item.id);
|
|||
|
}
|
|||
|
|
|||
|
if (expanded)
|
|||
|
{
|
|||
|
item.icon = folderOpenIcon;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
item.icon = folderClosedIcon;
|
|||
|
|
|||
|
if (hasChildren)
|
|||
|
{
|
|||
|
item.children = CreateChildListForCollapsedParent();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
parent.AddChild(item);
|
|||
|
|
|||
|
return item;
|
|||
|
}
|
|||
|
|
|||
|
protected override TreeViewItem BuildRoot()
|
|||
|
{
|
|||
|
return new TreeViewItem(-1, -1);
|
|||
|
}
|
|||
|
|
|||
|
private Dictionary<string, int> itemIDs = new Dictionary<string, int>();
|
|||
|
|
|||
|
private int AffirmItemID(string path)
|
|||
|
{
|
|||
|
int id;
|
|||
|
|
|||
|
if (!itemIDs.TryGetValue(path, out id))
|
|||
|
{
|
|||
|
id = itemIDs.Count;
|
|||
|
itemIDs.Add(path, id);
|
|||
|
}
|
|||
|
|
|||
|
return id;
|
|||
|
}
|
|||
|
|
|||
|
private const string EventPrefix = "event:/";
|
|||
|
private const string SnapshotPrefix = "snapshot:/";
|
|||
|
private const string BankPrefix = "bank:/";
|
|||
|
private const string ParameterPrefix = "parameter:/";
|
|||
|
|
|||
|
bool expandNextFolderSet = false;
|
|||
|
string nextFramedItemPath;
|
|||
|
private string[] searchStringSplit;
|
|||
|
|
|||
|
public TypeFilter TypeFilter { get; set; }
|
|||
|
public bool DragEnabled { get; set; }
|
|||
|
|
|||
|
protected override IList<TreeViewItem> BuildRows(TreeViewItem root)
|
|||
|
{
|
|||
|
if (hasSearch)
|
|||
|
{
|
|||
|
searchStringSplit = searchString.Split(' ');
|
|||
|
}
|
|||
|
|
|||
|
if (rootItem.children != null)
|
|||
|
{
|
|||
|
rootItem.children.Clear();
|
|||
|
}
|
|||
|
|
|||
|
if ((TypeFilter & TypeFilter.Event) != 0)
|
|||
|
{
|
|||
|
CreateSubTree("Events", EventPrefix,
|
|||
|
EventManager.Events.Where(e => e.Path.StartsWith(EventPrefix)), e => e.Path, eventIcon);
|
|||
|
|
|||
|
CreateSubTree("Snapshots", SnapshotPrefix,
|
|||
|
EventManager.Events.Where(e => e.Path.StartsWith(SnapshotPrefix)), s => s.Path, snapshotIcon);
|
|||
|
}
|
|||
|
|
|||
|
if ((TypeFilter & TypeFilter.Bank) != 0)
|
|||
|
{
|
|||
|
CreateSubTree("Banks", BankPrefix, EventManager.Banks, b => b.StudioPath, bankIcon);
|
|||
|
}
|
|||
|
|
|||
|
if ((TypeFilter & TypeFilter.Parameter) != 0)
|
|||
|
{
|
|||
|
CreateSubTree("Global Parameters", ParameterPrefix,
|
|||
|
EventManager.Parameters, p => ParameterPrefix + p.Name, parameterIcon,
|
|||
|
(path, p) => string.Format("{0}:{1:x}:{2:x}", path, p.ID.data1, p.ID.data2));
|
|||
|
}
|
|||
|
|
|||
|
List<TreeViewItem> rows = new List<TreeViewItem>();
|
|||
|
|
|||
|
AddChildrenInOrder(rows, rootItem);
|
|||
|
|
|||
|
SetupDepthsFromParentsAndChildren(rootItem);
|
|||
|
|
|||
|
expandNextFolderSet = false;
|
|||
|
nextFramedItemPath = null;
|
|||
|
|
|||
|
return rows;
|
|||
|
}
|
|||
|
|
|||
|
private class NaturalComparer : IComparer<string>
|
|||
|
{
|
|||
|
public int Compare(string a, string b)
|
|||
|
{
|
|||
|
return EditorUtility.NaturalCompare(a, b);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private static NaturalComparer naturalComparer = new NaturalComparer();
|
|||
|
|
|||
|
private void CreateSubTree<T>(string rootName, string rootPath,
|
|||
|
IEnumerable<T> sourceRecords, Func<T, string> GetPath,
|
|||
|
Texture2D icon, Func<string, T, string> MakeUniquePath = null)
|
|||
|
where T : ScriptableObject
|
|||
|
{
|
|||
|
var records = sourceRecords.Select(r => new { source = r, path = GetPath(r) });
|
|||
|
|
|||
|
if (hasSearch)
|
|||
|
{
|
|||
|
records = records.Where(r => {
|
|||
|
foreach (var word in searchStringSplit)
|
|||
|
{
|
|||
|
if (word.Length > 0 && r.path.IndexOf(word, StringComparison.OrdinalIgnoreCase) < 0)
|
|||
|
{
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
return true;
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
records = records.OrderBy(r => r.path, naturalComparer);
|
|||
|
|
|||
|
TreeViewItem root =
|
|||
|
CreateFolderItem(rootName, rootPath, records.Any(), TypeFilter != TypeFilter.All, rootItem);
|
|||
|
|
|||
|
List<TreeViewItem> currentFolderItems = new List<TreeViewItem>();
|
|||
|
|
|||
|
foreach (var record in records)
|
|||
|
{
|
|||
|
string leafName;
|
|||
|
TreeViewItem parent = CreateFolderItems(record.path, currentFolderItems, root, out leafName);
|
|||
|
|
|||
|
if (parent != null)
|
|||
|
{
|
|||
|
string uniquePath;
|
|||
|
|
|||
|
if (MakeUniquePath != null)
|
|||
|
{
|
|||
|
uniquePath = MakeUniquePath(record.path, record.source);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
uniquePath = record.path;
|
|||
|
}
|
|||
|
|
|||
|
TreeViewItem leafItem = new LeafItem(AffirmItemID(uniquePath), 0, record.source);
|
|||
|
leafItem.displayName = leafName;
|
|||
|
leafItem.icon = icon;
|
|||
|
|
|||
|
parent.AddChild(leafItem);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private TreeViewItem CreateFolderItems(string path, List<TreeViewItem> currentFolderItems,
|
|||
|
TreeViewItem root, out string leafName)
|
|||
|
{
|
|||
|
TreeViewItem parent = root;
|
|||
|
|
|||
|
char separator = '/';
|
|||
|
|
|||
|
// Skip the type prefix at the start of the path
|
|||
|
int elementStart = path.IndexOf(separator) + 1;
|
|||
|
|
|||
|
for (int i = 0; ; ++i)
|
|||
|
{
|
|||
|
if (!IsExpanded(parent.id))
|
|||
|
{
|
|||
|
leafName = null;
|
|||
|
return null;
|
|||
|
}
|
|||
|
|
|||
|
int elementEnd = path.IndexOf(separator, elementStart);
|
|||
|
|
|||
|
if (elementEnd < 0)
|
|||
|
{
|
|||
|
// No more folders; elementStart points to the event name
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
string folderName = path.Substring(elementStart, elementEnd - elementStart);
|
|||
|
|
|||
|
if (i < currentFolderItems.Count && folderName != currentFolderItems[i].displayName)
|
|||
|
{
|
|||
|
currentFolderItems.RemoveRange(i, currentFolderItems.Count - i);
|
|||
|
}
|
|||
|
|
|||
|
if (i == currentFolderItems.Count)
|
|||
|
{
|
|||
|
FolderItem folderItem =
|
|||
|
CreateFolderItem(folderName, path.Substring(0, elementEnd), true, false, parent);
|
|||
|
|
|||
|
currentFolderItems.Add(folderItem);
|
|||
|
}
|
|||
|
|
|||
|
elementStart = elementEnd + 1;
|
|||
|
parent = currentFolderItems[i];
|
|||
|
}
|
|||
|
|
|||
|
leafName = path.Substring(elementStart);
|
|||
|
return parent;
|
|||
|
}
|
|||
|
|
|||
|
private static void AddChildrenInOrder(List<TreeViewItem> list, TreeViewItem item)
|
|||
|
{
|
|||
|
if (item.children != null)
|
|||
|
{
|
|||
|
foreach (TreeViewItem child in item.children.Where(child => child is FolderItem))
|
|||
|
{
|
|||
|
list.Add(child);
|
|||
|
|
|||
|
AddChildrenInOrder(list, child);
|
|||
|
}
|
|||
|
|
|||
|
foreach (TreeViewItem child in item.children.Where(child => !(child == null || child is FolderItem)))
|
|||
|
{
|
|||
|
list.Add(child);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
protected override bool CanMultiSelect(TreeViewItem item)
|
|||
|
{
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
protected override bool CanChangeExpandedState(TreeViewItem item)
|
|||
|
{
|
|||
|
return item.hasChildren;
|
|||
|
}
|
|||
|
|
|||
|
protected override bool CanStartDrag(CanStartDragArgs args)
|
|||
|
{
|
|||
|
if (DragEnabled && args.draggedItem is LeafItem)
|
|||
|
{
|
|||
|
return IsDraggable((args.draggedItem as LeafItem).Data);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
protected override void SetupDragAndDrop(SetupDragAndDropArgs args)
|
|||
|
{
|
|||
|
IList<TreeViewItem> items = FindRows(args.draggedItemIDs);
|
|||
|
|
|||
|
if (items[0] is LeafItem)
|
|||
|
{
|
|||
|
LeafItem item = items[0] as LeafItem;
|
|||
|
|
|||
|
DragAndDrop.PrepareStartDrag();
|
|||
|
DragAndDrop.objectReferences = new UnityEngine.Object[] { Instantiate(item.Data) };
|
|||
|
|
|||
|
string title = string.Empty;
|
|||
|
|
|||
|
if (item.Data is EditorEventRef)
|
|||
|
{
|
|||
|
title = "New FMOD Studio Emitter";
|
|||
|
}
|
|||
|
else if (item.Data is EditorBankRef)
|
|||
|
{
|
|||
|
title = "New FMOD Studio Bank Loader";
|
|||
|
}
|
|||
|
else if (item.Data is EditorParamRef)
|
|||
|
{
|
|||
|
title = "New FMOD Studio Global Parameter Trigger";
|
|||
|
}
|
|||
|
|
|||
|
DragAndDrop.StartDrag(title);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
protected override DragAndDropVisualMode HandleDragAndDrop(DragAndDropArgs args)
|
|||
|
{
|
|||
|
return DragAndDropVisualMode.None;
|
|||
|
}
|
|||
|
|
|||
|
IList<int> noSearchExpandState;
|
|||
|
|
|||
|
protected override void SearchChanged(string newSearch)
|
|||
|
{
|
|||
|
if (!string.IsNullOrEmpty(newSearch.Trim()))
|
|||
|
{
|
|||
|
expandNextFolderSet = true;
|
|||
|
|
|||
|
if (noSearchExpandState == null)
|
|||
|
{
|
|||
|
// A new search is beginning
|
|||
|
noSearchExpandState = GetExpanded();
|
|||
|
SetExpanded(new List<int>());
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if (noSearchExpandState != null)
|
|||
|
{
|
|||
|
// A search is ending
|
|||
|
SetExpanded(noSearchExpandState);
|
|||
|
noSearchExpandState = null;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public ScriptableObject SelectedObject { get; private set; }
|
|||
|
public ScriptableObject DoubleClickedObject { get; private set; }
|
|||
|
|
|||
|
protected override void SelectionChanged(IList<int> selectedIDs)
|
|||
|
{
|
|||
|
SelectedObject = null;
|
|||
|
|
|||
|
if (selectedIDs.Count > 0)
|
|||
|
{
|
|||
|
TreeViewItem item = FindItem(selectedIDs[0], rootItem);
|
|||
|
|
|||
|
if (item is LeafItem)
|
|||
|
{
|
|||
|
SelectedObject = (item as LeafItem).Data;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
protected override void DoubleClickedItem(int id)
|
|||
|
{
|
|||
|
TreeViewItem item = FindItem(id, rootItem);
|
|||
|
|
|||
|
if (item is LeafItem)
|
|||
|
{
|
|||
|
DoubleClickedObject = (item as LeafItem).Data;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
float oldBaseIndent;
|
|||
|
|
|||
|
protected override void BeforeRowsGUI()
|
|||
|
{
|
|||
|
oldBaseIndent = baseIndent;
|
|||
|
DoubleClickedObject = null;
|
|||
|
}
|
|||
|
|
|||
|
protected override void RowGUI(RowGUIArgs args)
|
|||
|
{
|
|||
|
if (hasSearch)
|
|||
|
{
|
|||
|
// Hack to undo TreeView flattening the hierarchy when searching
|
|||
|
baseIndent = oldBaseIndent + args.item.depth * depthIndentWidth;
|
|||
|
}
|
|||
|
|
|||
|
base.RowGUI(args);
|
|||
|
|
|||
|
TreeViewItem item = args.item;
|
|||
|
|
|||
|
if (Event.current.type == EventType.MouseUp && item is FolderItem && item.hasChildren)
|
|||
|
{
|
|||
|
Rect rect = args.rowRect;
|
|||
|
rect.xMin = GetContentIndent(item);
|
|||
|
|
|||
|
if (rect.Contains(Event.current.mousePosition))
|
|||
|
{
|
|||
|
SetExpanded(item.id, !IsExpanded(item.id));
|
|||
|
Event.current.Use();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
protected override void AfterRowsGUI()
|
|||
|
{
|
|||
|
baseIndent = oldBaseIndent;
|
|||
|
}
|
|||
|
|
|||
|
[Serializable]
|
|||
|
public class State
|
|||
|
{
|
|||
|
public State() : this(new TreeViewState())
|
|||
|
{
|
|||
|
}
|
|||
|
|
|||
|
public State(TreeViewState baseState)
|
|||
|
{
|
|||
|
this.baseState = baseState;
|
|||
|
}
|
|||
|
|
|||
|
public TreeViewState baseState;
|
|||
|
public List<int> noSearchExpandState;
|
|||
|
public ScriptableObject selectedObject;
|
|||
|
public List<string> itemPaths = new List<string>();
|
|||
|
public List<int> itemIDs = new List<int>();
|
|||
|
public TypeFilter typeFilter = TypeFilter.All;
|
|||
|
public bool dragEnabled = true;
|
|||
|
}
|
|||
|
|
|||
|
new public State state
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
State result = new State(base.state);
|
|||
|
|
|||
|
if (noSearchExpandState != null)
|
|||
|
{
|
|||
|
result.noSearchExpandState = new List<int>(noSearchExpandState);
|
|||
|
}
|
|||
|
|
|||
|
result.selectedObject = SelectedObject;
|
|||
|
|
|||
|
foreach (var entry in itemIDs)
|
|||
|
{
|
|||
|
result.itemPaths.Add(entry.Key);
|
|||
|
result.itemIDs.Add(entry.Value);
|
|||
|
}
|
|||
|
|
|||
|
result.typeFilter = TypeFilter;
|
|||
|
result.dragEnabled = true;
|
|||
|
|
|||
|
return result;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private Texture2D borderIcon;
|
|||
|
private GUIStyle borderStyle;
|
|||
|
|
|||
|
private void AffirmResources()
|
|||
|
{
|
|||
|
if (borderIcon == null)
|
|||
|
{
|
|||
|
borderIcon = EditorGUIUtility.Load("FMOD/Border.png") as Texture2D;
|
|||
|
|
|||
|
borderStyle = new GUIStyle(GUI.skin.box);
|
|||
|
borderStyle.normal.background = borderIcon;
|
|||
|
borderStyle.margin = new RectOffset();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
[NonSerialized]
|
|||
|
TreeView treeView;
|
|||
|
|
|||
|
[NonSerialized]
|
|||
|
SearchField searchField;
|
|||
|
|
|||
|
[SerializeField]
|
|||
|
PreviewArea previewArea = new PreviewArea();
|
|||
|
|
|||
|
[SerializeField]
|
|||
|
TreeView.State treeViewState;
|
|||
|
|
|||
|
[NonSerialized]
|
|||
|
DateTime LastKnownCacheTime;
|
|||
|
|
|||
|
private SerializedProperty outputProperty;
|
|||
|
|
|||
|
bool InChooserMode { get { return outputProperty != null; } }
|
|||
|
|
|||
|
void OnGUI()
|
|||
|
{
|
|||
|
AffirmResources();
|
|||
|
|
|||
|
if (InChooserMode)
|
|||
|
{
|
|||
|
GUILayout.BeginVertical(borderStyle, GUILayout.ExpandWidth(true));
|
|||
|
}
|
|||
|
|
|||
|
treeView.searchString = searchField.OnGUI(treeView.searchString);
|
|||
|
|
|||
|
Rect treeRect = GUILayoutUtility.GetRect(0, 0, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true));
|
|||
|
treeRect.y += 2;
|
|||
|
treeRect.height -= 2;
|
|||
|
|
|||
|
treeView.OnGUI(treeRect);
|
|||
|
|
|||
|
if (InChooserMode)
|
|||
|
{
|
|||
|
GUILayout.EndVertical();
|
|||
|
HandleChooserModeEvents();
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
previewArea.treeView = treeView;
|
|||
|
previewArea.OnGUI(cachedMetering != null ? cachedMetering : EditorUtils.GetMetering());
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
void HandleChooserModeEvents()
|
|||
|
{
|
|||
|
if (Event.current.isKey)
|
|||
|
{
|
|||
|
KeyCode keyCode = Event.current.keyCode;
|
|||
|
|
|||
|
if ((keyCode == KeyCode.Return || keyCode == KeyCode.KeypadEnter) && treeView.SelectedObject != null)
|
|||
|
{
|
|||
|
SetOutputProperty(treeView.SelectedObject);
|
|||
|
Event.current.Use();
|
|||
|
Close();
|
|||
|
}
|
|||
|
else if (keyCode == KeyCode.Escape)
|
|||
|
{
|
|||
|
Event.current.Use();
|
|||
|
Close();
|
|||
|
}
|
|||
|
}
|
|||
|
else if (treeView.DoubleClickedObject != null)
|
|||
|
{
|
|||
|
SetOutputProperty(treeView.DoubleClickedObject);
|
|||
|
Close();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void SetOutputProperty(ScriptableObject data)
|
|||
|
{
|
|||
|
if (data is EditorEventRef)
|
|||
|
{
|
|||
|
string path = (data as EditorEventRef).Path;
|
|||
|
outputProperty.stringValue = path;
|
|||
|
EditorUtils.UpdateParamsOnEmitter(outputProperty.serializedObject, path);
|
|||
|
}
|
|||
|
else if (data is EditorBankRef)
|
|||
|
{
|
|||
|
outputProperty.stringValue = (data as EditorBankRef).Name;
|
|||
|
}
|
|||
|
else if (data is EditorParamRef)
|
|||
|
{
|
|||
|
outputProperty.stringValue = (data as EditorParamRef).Name;
|
|||
|
}
|
|||
|
|
|||
|
outputProperty.serializedObject.ApplyModifiedProperties();
|
|||
|
}
|
|||
|
|
|||
|
[Serializable]
|
|||
|
class PreviewArea
|
|||
|
{
|
|||
|
[NonSerialized]
|
|||
|
public TreeView treeView;
|
|||
|
|
|||
|
public bool forceRepaint { get { return transportControls.forceRepaint; } }
|
|||
|
|
|||
|
[NonSerialized]
|
|||
|
private EditorEventRef currentEvent;
|
|||
|
|
|||
|
void SetEvent(EditorEventRef eventRef)
|
|||
|
{
|
|||
|
if (eventRef != currentEvent)
|
|||
|
{
|
|||
|
currentEvent = eventRef;
|
|||
|
|
|||
|
EditorUtils.PreviewStop();
|
|||
|
transportControls.Reset();
|
|||
|
event3DPreview.Reset();
|
|||
|
parameterControls.Reset();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
[SerializeField]
|
|||
|
DetailsView detailsView = new DetailsView();
|
|||
|
|
|||
|
[SerializeField]
|
|||
|
TransportControls transportControls = new TransportControls();
|
|||
|
|
|||
|
[SerializeField]
|
|||
|
Event3DPreview event3DPreview = new Event3DPreview();
|
|||
|
|
|||
|
[SerializeField]
|
|||
|
PreviewMeters meters = new PreviewMeters();
|
|||
|
|
|||
|
[SerializeField]
|
|||
|
EventParameterControls parameterControls = new EventParameterControls();
|
|||
|
|
|||
|
private GUIStyle mainStyle;
|
|||
|
|
|||
|
private void AffirmResources()
|
|||
|
{
|
|||
|
if (mainStyle == null)
|
|||
|
{
|
|||
|
mainStyle = new GUIStyle(GUI.skin.box);
|
|||
|
mainStyle.margin = new RectOffset();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public void OnGUI(float[] metering)
|
|||
|
{
|
|||
|
AffirmResources();
|
|||
|
|
|||
|
ScriptableObject selectedObject = treeView.SelectedObject;
|
|||
|
|
|||
|
if (selectedObject is EditorEventRef)
|
|||
|
{
|
|||
|
SetEvent(selectedObject as EditorEventRef);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
SetEvent(null);
|
|||
|
}
|
|||
|
|
|||
|
if (selectedObject != null)
|
|||
|
{
|
|||
|
GUILayout.BeginVertical(mainStyle, GUILayout.ExpandWidth(true));
|
|||
|
|
|||
|
if (selectedObject is EditorEventRef)
|
|||
|
{
|
|||
|
EditorEventRef eventRef = selectedObject as EditorEventRef;
|
|||
|
|
|||
|
if (eventRef.Path.StartsWith("event:"))
|
|||
|
{
|
|||
|
DrawEventPreview(eventRef, metering);
|
|||
|
}
|
|||
|
else if (eventRef.Path.StartsWith("snapshot:"))
|
|||
|
{
|
|||
|
detailsView.DrawSnapshot(eventRef);
|
|||
|
}
|
|||
|
}
|
|||
|
else if (selectedObject is EditorBankRef)
|
|||
|
{
|
|||
|
detailsView.DrawBank(selectedObject as EditorBankRef);
|
|||
|
}
|
|||
|
else if (selectedObject is EditorParamRef)
|
|||
|
{
|
|||
|
detailsView.DrawParameter(selectedObject as EditorParamRef);
|
|||
|
}
|
|||
|
|
|||
|
GUILayout.EndVertical();
|
|||
|
|
|||
|
if (Event.current.type == EventType.Repaint)
|
|||
|
{
|
|||
|
Rect rect = GUILayoutUtility.GetLastRect();
|
|||
|
isNarrow = rect.width < 600;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void DrawSeparatorLine()
|
|||
|
{
|
|||
|
GUILayout.Box(GUIContent.none, GUILayout.Height(1), GUILayout.ExpandWidth(true));
|
|||
|
}
|
|||
|
|
|||
|
private bool isNarrow;
|
|||
|
|
|||
|
private void DrawEventPreview(EditorEventRef eventRef, float[] metering)
|
|||
|
{
|
|||
|
detailsView.DrawEvent(eventRef, isNarrow);
|
|||
|
|
|||
|
DrawSeparatorLine();
|
|||
|
|
|||
|
// Playback controls, 3D Preview and meters
|
|||
|
EditorGUILayout.BeginHorizontal(GUILayout.Height(event3DPreview.Height));
|
|||
|
GUILayout.FlexibleSpace();
|
|||
|
|
|||
|
EditorGUILayout.BeginVertical();
|
|||
|
|
|||
|
if (!isNarrow)
|
|||
|
{
|
|||
|
GUILayout.FlexibleSpace();
|
|||
|
}
|
|||
|
|
|||
|
transportControls.OnGUI(eventRef, parameterControls.ParameterValues);
|
|||
|
|
|||
|
if (isNarrow)
|
|||
|
{
|
|||
|
EditorGUILayout.Separator();
|
|||
|
meters.OnGUI(true, metering);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
GUILayout.FlexibleSpace();
|
|||
|
}
|
|||
|
|
|||
|
EditorGUILayout.EndVertical();
|
|||
|
|
|||
|
event3DPreview.OnGUI(eventRef);
|
|||
|
|
|||
|
if (!isNarrow)
|
|||
|
{
|
|||
|
meters.OnGUI(false, metering);
|
|||
|
}
|
|||
|
|
|||
|
GUILayout.FlexibleSpace();
|
|||
|
EditorGUILayout.EndHorizontal();
|
|||
|
|
|||
|
DrawSeparatorLine();
|
|||
|
|
|||
|
parameterControls.OnGUI(eventRef);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
[Serializable]
|
|||
|
class DetailsView
|
|||
|
{
|
|||
|
private Texture copyIcon;
|
|||
|
private GUIStyle textFieldNameStyle;
|
|||
|
|
|||
|
private void AffirmResources()
|
|||
|
{
|
|||
|
if (copyIcon == null)
|
|||
|
{
|
|||
|
copyIcon = EditorGUIUtility.Load("FMOD/CopyIcon.png") as Texture;
|
|||
|
|
|||
|
textFieldNameStyle = new GUIStyle(EditorStyles.label);
|
|||
|
textFieldNameStyle.fontStyle = FontStyle.Bold;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public void DrawEvent(EditorEventRef selectedEvent, bool isNarrow)
|
|||
|
{
|
|||
|
AffirmResources();
|
|||
|
|
|||
|
DrawCopyableTextField("Full Path", selectedEvent.Path);
|
|||
|
|
|||
|
DrawTextField("Banks", string.Join(", ", selectedEvent.Banks.Select(x => x.Name).ToArray()));
|
|||
|
|
|||
|
EditorGUILayout.BeginHorizontal();
|
|||
|
DrawTextField("Panning", selectedEvent.Is3D ? "3D" : "2D");
|
|||
|
DrawTextField("Oneshot", selectedEvent.IsOneShot.ToString());
|
|||
|
|
|||
|
TimeSpan t = TimeSpan.FromMilliseconds(selectedEvent.Length);
|
|||
|
DrawTextField("Length", selectedEvent.Length > 0 ? string.Format("{0:D2}:{1:D2}:{2:D3}", t.Minutes, t.Seconds, t.Milliseconds) : "N/A");
|
|||
|
|
|||
|
if (!isNarrow) DrawTextField("Streaming", selectedEvent.IsStream.ToString());
|
|||
|
EditorGUILayout.EndHorizontal();
|
|||
|
if (isNarrow) DrawTextField("Streaming", selectedEvent.IsStream.ToString());
|
|||
|
}
|
|||
|
|
|||
|
public void DrawSnapshot(EditorEventRef eventRef)
|
|||
|
{
|
|||
|
AffirmResources();
|
|||
|
|
|||
|
DrawCopyableTextField("Full Path", eventRef.Path);
|
|||
|
}
|
|||
|
|
|||
|
public void DrawBank(EditorBankRef bank)
|
|||
|
{
|
|||
|
AffirmResources();
|
|||
|
|
|||
|
DrawCopyableTextField("Full Path", "bank:/" + bank.Name);
|
|||
|
|
|||
|
string[] SizeSuffix = { "B", "KB", "MB", "GB" };
|
|||
|
|
|||
|
GUILayout.Label("Platform Bank Sizes", textFieldNameStyle);
|
|||
|
|
|||
|
EditorGUI.indentLevel++;
|
|||
|
|
|||
|
foreach (var sizeInfo in bank.FileSizes)
|
|||
|
{
|
|||
|
int order = 0;
|
|||
|
long size = sizeInfo.Value;
|
|||
|
|
|||
|
while (size >= 1024 && order + 1 < SizeSuffix.Length)
|
|||
|
{
|
|||
|
order++;
|
|||
|
size /= 1024;
|
|||
|
}
|
|||
|
|
|||
|
EditorGUILayout.LabelField(sizeInfo.Name, string.Format("{0} {1}", size, SizeSuffix[order]));
|
|||
|
}
|
|||
|
|
|||
|
EditorGUI.indentLevel--;
|
|||
|
}
|
|||
|
|
|||
|
public void DrawParameter(EditorParamRef parameter)
|
|||
|
{
|
|||
|
AffirmResources();
|
|||
|
|
|||
|
DrawCopyableTextField("Name", parameter.Name);
|
|||
|
DrawCopyableTextField("ID",
|
|||
|
string.Format("{{ data1 = 0x{0:x8}, data2 = 0x{1:x8} }}", parameter.ID.data1, parameter.ID.data2));
|
|||
|
DrawTextField("Minimum", parameter.Min.ToString());
|
|||
|
DrawTextField("Maximum", parameter.Max.ToString());
|
|||
|
}
|
|||
|
|
|||
|
private void DrawCopyableTextField(string name, string value)
|
|||
|
{
|
|||
|
EditorGUILayout.BeginHorizontal();
|
|||
|
DrawTextField(name, value);
|
|||
|
if (GUILayout.Button(copyIcon, GUILayout.ExpandWidth(false)))
|
|||
|
{
|
|||
|
EditorGUIUtility.systemCopyBuffer = value;
|
|||
|
}
|
|||
|
EditorGUILayout.EndHorizontal();
|
|||
|
}
|
|||
|
|
|||
|
private void DrawTextField(string name, string content)
|
|||
|
{
|
|||
|
EditorGUILayout.BeginHorizontal();
|
|||
|
|
|||
|
GUILayout.Label(name, textFieldNameStyle, GUILayout.Width(75));
|
|||
|
GUILayout.Label(content);
|
|||
|
|
|||
|
EditorGUILayout.EndHorizontal();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
[Serializable]
|
|||
|
class TransportControls
|
|||
|
{
|
|||
|
public bool forceRepaint { get; private set; }
|
|||
|
|
|||
|
public void Reset()
|
|||
|
{
|
|||
|
forceRepaint = false;
|
|||
|
}
|
|||
|
|
|||
|
private Texture playOff;
|
|||
|
private Texture playOn;
|
|||
|
private Texture stopOff;
|
|||
|
private Texture stopOn;
|
|||
|
private Texture openIcon;
|
|||
|
private GUIStyle buttonStyle;
|
|||
|
|
|||
|
private void AffirmResources()
|
|||
|
{
|
|||
|
if (playOff == null)
|
|||
|
{
|
|||
|
playOff = EditorGUIUtility.Load("FMOD/TransportPlayButtonOff.png") as Texture;
|
|||
|
playOn = EditorGUIUtility.Load("FMOD/TransportPlayButtonOn.png") as Texture;
|
|||
|
stopOff = EditorGUIUtility.Load("FMOD/TransportStopButtonOff.png") as Texture;
|
|||
|
stopOn = EditorGUIUtility.Load("FMOD/TransportStopButtonOn.png") as Texture;
|
|||
|
openIcon = EditorGUIUtility.Load("FMOD/transportOpen.png") as Texture;
|
|||
|
|
|||
|
buttonStyle = new GUIStyle();
|
|||
|
buttonStyle.padding.left = 4;
|
|||
|
buttonStyle.padding.top = 10;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public void OnGUI(EditorEventRef selectedEvent, Dictionary<string, float> parameterValues)
|
|||
|
{
|
|||
|
AffirmResources();
|
|||
|
|
|||
|
var previewState = EditorUtils.PreviewState;
|
|||
|
bool playing = previewState == PreviewState.Playing;
|
|||
|
bool paused = previewState == PreviewState.Paused;
|
|||
|
bool stopped = previewState == PreviewState.Stopped;
|
|||
|
|
|||
|
EditorGUILayout.BeginHorizontal();
|
|||
|
|
|||
|
if (GUILayout.Button(stopped || paused ? stopOn : stopOff, buttonStyle, GUILayout.ExpandWidth(false)))
|
|||
|
{
|
|||
|
forceRepaint = false;
|
|||
|
|
|||
|
if (paused)
|
|||
|
{
|
|||
|
EditorUtils.PreviewStop();
|
|||
|
}
|
|||
|
if (playing)
|
|||
|
{
|
|||
|
EditorUtils.PreviewPause();
|
|||
|
}
|
|||
|
}
|
|||
|
if (GUILayout.Button(playing ? playOn : playOff, buttonStyle, GUILayout.ExpandWidth(false)))
|
|||
|
{
|
|||
|
if (playing || stopped)
|
|||
|
{
|
|||
|
EditorUtils.PreviewEvent(selectedEvent, parameterValues);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
EditorUtils.PreviewPause();
|
|||
|
}
|
|||
|
|
|||
|
forceRepaint = true;
|
|||
|
}
|
|||
|
if (GUILayout.Button(new GUIContent(openIcon, "Show Event in FMOD Studio"), buttonStyle, GUILayout.ExpandWidth(false)))
|
|||
|
{
|
|||
|
string cmd = string.Format("studio.window.navigateTo(studio.project.lookup(\"{0}\"))", selectedEvent.Guid.ToString("b"));
|
|||
|
EditorUtils.SendScriptCommand(cmd);
|
|||
|
}
|
|||
|
|
|||
|
EditorGUILayout.EndHorizontal();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
[Serializable]
|
|||
|
class Event3DPreview
|
|||
|
{
|
|||
|
private bool isDragging;
|
|||
|
private Rect arenaRect;
|
|||
|
|
|||
|
private Vector2 eventPosition;
|
|||
|
private float eventDistance = 0;
|
|||
|
private float eventOrientation = 0;
|
|||
|
|
|||
|
public void Reset()
|
|||
|
{
|
|||
|
eventPosition = new Vector2(0, 0);
|
|||
|
eventDistance = 0;
|
|||
|
eventOrientation = 0;
|
|||
|
}
|
|||
|
|
|||
|
private Texture arena;
|
|||
|
private Texture emitter;
|
|||
|
|
|||
|
private void AffirmResources()
|
|||
|
{
|
|||
|
if (arena == null)
|
|||
|
{
|
|||
|
arena = EditorGUIUtility.Load("FMOD/preview.png") as Texture;
|
|||
|
emitter = EditorGUIUtility.Load("FMOD/previewemitter.png") as Texture;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public float Height
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
AffirmResources();
|
|||
|
return GUI.skin.label.CalcSize(new GUIContent(arena)).y;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public void OnGUI(EditorEventRef selectedEvent)
|
|||
|
{
|
|||
|
AffirmResources();
|
|||
|
|
|||
|
var originalColour = GUI.color;
|
|||
|
if (!selectedEvent.Is3D)
|
|||
|
{
|
|||
|
GUI.color = new Color(1.0f, 1.0f, 1.0f, 0.1f);
|
|||
|
}
|
|||
|
|
|||
|
GUILayout.Label(arena, GUILayout.ExpandWidth(false));
|
|||
|
|
|||
|
if (Event.current.type == EventType.Repaint)
|
|||
|
{
|
|||
|
arenaRect = GUILayoutUtility.GetLastRect();
|
|||
|
}
|
|||
|
|
|||
|
Vector2 center = arenaRect.center;
|
|||
|
Rect rect2 = new Rect(center.x + eventPosition.x - 6, center.y + eventPosition.y - 6, 12, 12);
|
|||
|
GUI.DrawTexture(rect2, emitter);
|
|||
|
|
|||
|
GUI.color = originalColour;
|
|||
|
|
|||
|
if (selectedEvent.Is3D)
|
|||
|
{
|
|||
|
bool useGUIEvent = false;
|
|||
|
|
|||
|
switch (Event.current.type)
|
|||
|
{
|
|||
|
case EventType.MouseDown:
|
|||
|
if (arenaRect.Contains(Event.current.mousePosition))
|
|||
|
{
|
|||
|
isDragging = true;
|
|||
|
useGUIEvent = true;
|
|||
|
}
|
|||
|
break;
|
|||
|
case EventType.MouseUp:
|
|||
|
if (isDragging)
|
|||
|
{
|
|||
|
isDragging = false;
|
|||
|
useGUIEvent = true;
|
|||
|
}
|
|||
|
break;
|
|||
|
case EventType.MouseDrag:
|
|||
|
if (isDragging)
|
|||
|
{
|
|||
|
useGUIEvent = true;
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
if (useGUIEvent)
|
|||
|
{
|
|||
|
Vector2 newPosition = Event.current.mousePosition;
|
|||
|
Vector2 delta = newPosition - center;
|
|||
|
|
|||
|
float maximumDistance = (arena.width - emitter.width) / 2;
|
|||
|
float distance = Math.Min(delta.magnitude, maximumDistance);
|
|||
|
|
|||
|
delta.Normalize();
|
|||
|
eventPosition = delta * distance;
|
|||
|
eventDistance = distance / maximumDistance * selectedEvent.MaxDistance;
|
|||
|
|
|||
|
float angle = Mathf.Atan2(delta.y, delta.x);
|
|||
|
eventOrientation = angle + Mathf.PI * 0.5f;
|
|||
|
|
|||
|
Event.current.Use();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
EditorUtils.PreviewUpdatePosition(eventDistance, eventOrientation);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
[Serializable]
|
|||
|
class EventParameterControls
|
|||
|
{
|
|||
|
[NonSerialized]
|
|||
|
private Dictionary<string, float> parameterValues = new Dictionary<string, float>();
|
|||
|
|
|||
|
public Dictionary<string, float> ParameterValues { get { return parameterValues; } }
|
|||
|
|
|||
|
[NonSerialized]
|
|||
|
private Vector2 scrollPosition;
|
|||
|
|
|||
|
public void Reset()
|
|||
|
{
|
|||
|
parameterValues.Clear();
|
|||
|
}
|
|||
|
|
|||
|
public void OnGUI(EditorEventRef selectedEvent)
|
|||
|
{
|
|||
|
scrollPosition = GUILayout.BeginScrollView(scrollPosition,
|
|||
|
GUILayout.Height(EditorGUIUtility.singleLineHeight * 3.5f));
|
|||
|
|
|||
|
foreach (EditorParamRef paramRef in selectedEvent.Parameters)
|
|||
|
{
|
|||
|
if (!parameterValues.ContainsKey(paramRef.Name))
|
|||
|
{
|
|||
|
parameterValues[paramRef.Name] = paramRef.Default;
|
|||
|
}
|
|||
|
|
|||
|
parameterValues[paramRef.Name] = EditorGUILayout.Slider(paramRef.Name, parameterValues[paramRef.Name], paramRef.Min, paramRef.Max);
|
|||
|
|
|||
|
EditorUtils.PreviewUpdateParameter(paramRef.ID, parameterValues[paramRef.Name]);
|
|||
|
}
|
|||
|
|
|||
|
GUILayout.EndScrollView();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
[Serializable]
|
|||
|
class PreviewMeters
|
|||
|
{
|
|||
|
private Texture meterOn;
|
|||
|
private Texture meterOff;
|
|||
|
|
|||
|
private void AffirmResources()
|
|||
|
{
|
|||
|
if (meterOn == null)
|
|||
|
{
|
|||
|
meterOn = EditorGUIUtility.Load("FMOD/LevelMeter.png") as Texture;
|
|||
|
meterOff = EditorGUIUtility.Load("FMOD/LevelMeterOff.png") as Texture;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public void OnGUI(bool minimized, float[] metering)
|
|||
|
{
|
|||
|
AffirmResources();
|
|||
|
|
|||
|
int meterHeight = minimized ? 86 : 128;
|
|||
|
int meterWidth = (int)((128 / (float)meterOff.height) * meterOff.width);
|
|||
|
|
|||
|
List<float> meterPositions = meterPositionsForSpeakerMode(speakerModeForChannelCount(metering.Length), meterWidth, 2, 6);
|
|||
|
|
|||
|
const int MeterCountMaximum = 16;
|
|||
|
|
|||
|
int minimumWidth = meterWidth * MeterCountMaximum;
|
|||
|
|
|||
|
Rect fullRect = GUILayoutUtility.GetRect(minimumWidth, meterHeight,
|
|||
|
GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true));
|
|||
|
|
|||
|
float baseX = fullRect.x + (fullRect.width - (meterWidth * metering.Length)) / 2;
|
|||
|
|
|||
|
for(int i = 0; i < metering.Length; i++)
|
|||
|
{
|
|||
|
Rect meterRect = new Rect(baseX + meterPositions[i], fullRect.y, meterWidth, fullRect.height);
|
|||
|
|
|||
|
GUI.DrawTexture(meterRect, meterOff);
|
|||
|
|
|||
|
float db = 20.0f * Mathf.Log10(metering[i] * Mathf.Sqrt(2.0f));
|
|||
|
db = Mathf.Clamp(db, -80.0f, 10.0f);
|
|||
|
float visible = 0;
|
|||
|
int[] segmentPixels = new int[] { 0, 18, 38, 60, 89, 130, 187, 244, 300 };
|
|||
|
float[] segmentDB = new float[] { -80.0f, -60.0f, -50.0f, -40.0f, -30.0f, -20.0f, -10.0f, 0, 10.0f };
|
|||
|
int segment = 1;
|
|||
|
while (segmentDB[segment] < db)
|
|||
|
{
|
|||
|
segment++;
|
|||
|
}
|
|||
|
visible = segmentPixels[segment - 1] + ((db - segmentDB[segment - 1]) / (segmentDB[segment] - segmentDB[segment - 1])) * (segmentPixels[segment] - segmentPixels[segment - 1]);
|
|||
|
|
|||
|
visible *= fullRect.height / (float)meterOff.height;
|
|||
|
|
|||
|
Rect levelPosRect = new Rect(meterRect.x, fullRect.height - visible + meterRect.y, meterWidth, visible);
|
|||
|
Rect levelUVRect = new Rect(0, 0, 1.0f, visible / fullRect.height);
|
|||
|
GUI.DrawTextureWithTexCoords(levelPosRect, meterOn, levelUVRect);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private FMOD.SPEAKERMODE speakerModeForChannelCount(int channelCount)
|
|||
|
{
|
|||
|
switch(channelCount)
|
|||
|
{
|
|||
|
case 1:
|
|||
|
return FMOD.SPEAKERMODE.MONO;
|
|||
|
case 4:
|
|||
|
return FMOD.SPEAKERMODE.QUAD;
|
|||
|
case 5:
|
|||
|
return FMOD.SPEAKERMODE.SURROUND;
|
|||
|
case 6:
|
|||
|
return FMOD.SPEAKERMODE._5POINT1;
|
|||
|
case 8:
|
|||
|
return FMOD.SPEAKERMODE._7POINT1;
|
|||
|
case 12:
|
|||
|
return FMOD.SPEAKERMODE._7POINT1POINT4;
|
|||
|
default:
|
|||
|
return FMOD.SPEAKERMODE.STEREO;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private List<float> meterPositionsForSpeakerMode(FMOD.SPEAKERMODE mode, float meterWidth, float groupGap, float lfeGap)
|
|||
|
{
|
|||
|
List<float> offsets = new List<float>();
|
|||
|
|
|||
|
switch(mode)
|
|||
|
{
|
|||
|
case FMOD.SPEAKERMODE.MONO: // M
|
|||
|
offsets.Add(0);
|
|||
|
break;
|
|||
|
|
|||
|
case FMOD.SPEAKERMODE.STEREO: // L R
|
|||
|
offsets.Add(0);
|
|||
|
offsets.Add(meterWidth);
|
|||
|
break;
|
|||
|
|
|||
|
case FMOD.SPEAKERMODE.QUAD:
|
|||
|
switch(Settings.Instance.MeterChannelOrdering)
|
|||
|
{
|
|||
|
case MeterChannelOrderingType.Standard:
|
|||
|
case MeterChannelOrderingType.SeparateLFE: // L R | LS RS
|
|||
|
offsets.Add(0); // L
|
|||
|
offsets.Add(meterWidth*1); // R
|
|||
|
offsets.Add(meterWidth*2 + groupGap); // LS
|
|||
|
offsets.Add(meterWidth*3 + groupGap); // RS
|
|||
|
break;
|
|||
|
case MeterChannelOrderingType.Positional: // LS | L R | RS
|
|||
|
offsets.Add(meterWidth*1 + groupGap); // L
|
|||
|
offsets.Add(meterWidth*2 + groupGap); // R
|
|||
|
offsets.Add(0); // LS
|
|||
|
offsets.Add(meterWidth*3 + groupGap*2); // RS
|
|||
|
break;
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
case FMOD.SPEAKERMODE.SURROUND:
|
|||
|
switch(Settings.Instance.MeterChannelOrdering)
|
|||
|
{
|
|||
|
case MeterChannelOrderingType.Standard:
|
|||
|
case MeterChannelOrderingType.SeparateLFE: // L R | C | LS RS
|
|||
|
offsets.Add(0); // L
|
|||
|
offsets.Add(meterWidth*1); // R
|
|||
|
offsets.Add(meterWidth*2 + groupGap); // C
|
|||
|
offsets.Add(meterWidth*3 + groupGap*2); // LS
|
|||
|
offsets.Add(meterWidth*4 + groupGap*2); // RS
|
|||
|
break;
|
|||
|
case MeterChannelOrderingType.Positional: // LS | L C R | RS
|
|||
|
offsets.Add(meterWidth*1 + groupGap); // L
|
|||
|
offsets.Add(meterWidth*3 + groupGap); // R
|
|||
|
offsets.Add(meterWidth*2 + groupGap); // C
|
|||
|
offsets.Add(0); // LS
|
|||
|
offsets.Add(meterWidth*4 + groupGap*2); // RS
|
|||
|
break;
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
case FMOD.SPEAKERMODE._5POINT1:
|
|||
|
switch(Settings.Instance.MeterChannelOrdering)
|
|||
|
{
|
|||
|
case MeterChannelOrderingType.Standard: // L R | C | LFE | LS RS
|
|||
|
offsets.Add(0); // L
|
|||
|
offsets.Add(meterWidth*1); // R
|
|||
|
offsets.Add(meterWidth*2 + groupGap); // C
|
|||
|
offsets.Add(meterWidth*3 + groupGap*2); // LFE
|
|||
|
offsets.Add(meterWidth*4 + groupGap*3); // LS
|
|||
|
offsets.Add(meterWidth*5 + groupGap*3); // RS
|
|||
|
break;
|
|||
|
case MeterChannelOrderingType.SeparateLFE: // L R | C | LS RS || LFE
|
|||
|
offsets.Add(0); // L
|
|||
|
offsets.Add(meterWidth*1); // R
|
|||
|
offsets.Add(meterWidth*2 + groupGap); // C
|
|||
|
offsets.Add(meterWidth*5 + groupGap*2 + lfeGap); // LFE
|
|||
|
offsets.Add(meterWidth*3 + groupGap*2); // LS
|
|||
|
offsets.Add(meterWidth*4 + groupGap*2); // RS
|
|||
|
break;
|
|||
|
case MeterChannelOrderingType.Positional: // LS | L C R | RS || LFE
|
|||
|
offsets.Add(meterWidth*1 + groupGap); // L
|
|||
|
offsets.Add(meterWidth*3 + groupGap); // R
|
|||
|
offsets.Add(meterWidth*2 + groupGap); // C
|
|||
|
offsets.Add(meterWidth*5 + groupGap*2 + lfeGap); // LFE
|
|||
|
offsets.Add(0); // LS
|
|||
|
offsets.Add(meterWidth*4 + groupGap*2); // RS
|
|||
|
break;
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
case FMOD.SPEAKERMODE._7POINT1:
|
|||
|
switch(Settings.Instance.MeterChannelOrdering)
|
|||
|
{
|
|||
|
case MeterChannelOrderingType.Standard: // L R | C | LFE | LS RS | LSR RSR
|
|||
|
offsets.Add(0); // L
|
|||
|
offsets.Add(meterWidth*1); // R
|
|||
|
offsets.Add(meterWidth*2 + groupGap); // C
|
|||
|
offsets.Add(meterWidth*3 + groupGap*2); // LFE
|
|||
|
offsets.Add(meterWidth*4 + groupGap*3); // LS
|
|||
|
offsets.Add(meterWidth*5 + groupGap*3); // RS
|
|||
|
offsets.Add(meterWidth*6 + groupGap*4); // LSR
|
|||
|
offsets.Add(meterWidth*7 + groupGap*4); // RSR
|
|||
|
break;
|
|||
|
case MeterChannelOrderingType.SeparateLFE: // L R | C | LS RS | LSR RSR || LFE
|
|||
|
offsets.Add(0); // L
|
|||
|
offsets.Add(meterWidth*1); // R
|
|||
|
offsets.Add(meterWidth*2 + groupGap); // C
|
|||
|
offsets.Add(meterWidth*7 + groupGap*3 + lfeGap); // LFE
|
|||
|
offsets.Add(meterWidth*3 + groupGap*2); // LS
|
|||
|
offsets.Add(meterWidth*4 + groupGap*2); // RS
|
|||
|
offsets.Add(meterWidth*5 + groupGap*3); // LSR
|
|||
|
offsets.Add(meterWidth*6 + groupGap*3); // RSR
|
|||
|
break;
|
|||
|
case MeterChannelOrderingType.Positional: // LSR LS | L C R | RS RSR || LFE
|
|||
|
offsets.Add(meterWidth*2 + groupGap); // L
|
|||
|
offsets.Add(meterWidth*4 + groupGap); // R
|
|||
|
offsets.Add(meterWidth*3 + groupGap); // C
|
|||
|
offsets.Add(meterWidth*7 + groupGap*2 + lfeGap); // LFE
|
|||
|
offsets.Add(meterWidth*1); // LS
|
|||
|
offsets.Add(meterWidth*5 + groupGap*2); // RS
|
|||
|
offsets.Add(0); // LSR
|
|||
|
offsets.Add(meterWidth*6 + groupGap*2); // RSR
|
|||
|
break;
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
case FMOD.SPEAKERMODE._7POINT1POINT4:
|
|||
|
switch(Settings.Instance.MeterChannelOrdering)
|
|||
|
{
|
|||
|
case MeterChannelOrderingType.Standard: // L R | C | LFE | LS RS | LSR RSR | TFL TFR TBL TBR
|
|||
|
offsets.Add(0); // L
|
|||
|
offsets.Add(meterWidth*1); // R
|
|||
|
offsets.Add(meterWidth*2 + groupGap); // C
|
|||
|
offsets.Add(meterWidth*3 + groupGap*2); // LFE
|
|||
|
offsets.Add(meterWidth*4 + groupGap*3); // LS
|
|||
|
offsets.Add(meterWidth*5 + groupGap*3); // RS
|
|||
|
offsets.Add(meterWidth*6 + groupGap*4); // LSR
|
|||
|
offsets.Add(meterWidth*7 + groupGap*4); // RSR
|
|||
|
offsets.Add(meterWidth*8 + groupGap*5); // TFL
|
|||
|
offsets.Add(meterWidth*9 + groupGap*5); // TFR
|
|||
|
offsets.Add(meterWidth*10 + groupGap*5); // TBL
|
|||
|
offsets.Add(meterWidth*11 + groupGap*5); // TBR
|
|||
|
break;
|
|||
|
case MeterChannelOrderingType.SeparateLFE: // L R | C | LS RS | LSR RSR | TFL TFR TBL TBR || LFE
|
|||
|
offsets.Add(0); // L
|
|||
|
offsets.Add(meterWidth*1); // R
|
|||
|
offsets.Add(meterWidth*2 + groupGap); // C
|
|||
|
offsets.Add(meterWidth*11 + groupGap*4 + lfeGap); // LFE
|
|||
|
offsets.Add(meterWidth*3 + groupGap*2); // LS
|
|||
|
offsets.Add(meterWidth*4 + groupGap*2); // RS
|
|||
|
offsets.Add(meterWidth*5 + groupGap*3); // LSR
|
|||
|
offsets.Add(meterWidth*6 + groupGap*3); // RSR
|
|||
|
offsets.Add(meterWidth*7 + groupGap*4); // TFL
|
|||
|
offsets.Add(meterWidth*8 + groupGap*4); // TFR
|
|||
|
offsets.Add(meterWidth*9 + groupGap*4); // TBL
|
|||
|
offsets.Add(meterWidth*10 + groupGap*4); // TBR
|
|||
|
break;
|
|||
|
case MeterChannelOrderingType.Positional: // LSR LS | L C R | RS RSR | TBL TFL TFR TBR || LFE
|
|||
|
offsets.Add(meterWidth*2 + groupGap); // L
|
|||
|
offsets.Add(meterWidth*4 + groupGap); // R
|
|||
|
offsets.Add(meterWidth*3 + groupGap); // C
|
|||
|
offsets.Add(meterWidth*11 + groupGap*3 + lfeGap); // LFE
|
|||
|
offsets.Add(meterWidth*1); // LS
|
|||
|
offsets.Add(meterWidth*5 + groupGap*2); // RS
|
|||
|
offsets.Add(0); // LSR
|
|||
|
offsets.Add(meterWidth*6 + groupGap*2); // RSR
|
|||
|
offsets.Add(meterWidth*8 + groupGap*3); // TFL
|
|||
|
offsets.Add(meterWidth*9 + groupGap*3); // TFR
|
|||
|
offsets.Add(meterWidth*7 + groupGap*3); // TBL
|
|||
|
offsets.Add(meterWidth*10 + groupGap*3); // TBR
|
|||
|
break;
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
return offsets;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
[Flags]
|
|||
|
enum TypeFilter
|
|||
|
{
|
|||
|
Event = 1,
|
|||
|
Bank = 2,
|
|||
|
Parameter = 4,
|
|||
|
All = Event | Bank | Parameter,
|
|||
|
}
|
|||
|
|
|||
|
public void ChooseEvent(SerializedProperty property)
|
|||
|
{
|
|||
|
BeginInspectorPopup(property, TypeFilter.Event);
|
|||
|
|
|||
|
if (!string.IsNullOrEmpty(property.stringValue))
|
|||
|
{
|
|||
|
treeView.JumpToEvent(property.stringValue);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public void ChooseBank(SerializedProperty property)
|
|||
|
{
|
|||
|
BeginInspectorPopup(property, TypeFilter.Bank);
|
|||
|
|
|||
|
if (!string.IsNullOrEmpty(property.stringValue))
|
|||
|
{
|
|||
|
treeView.JumpToBank(property.stringValue);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public void ChooseParameter(SerializedProperty property)
|
|||
|
{
|
|||
|
BeginInspectorPopup(property, TypeFilter.Parameter);
|
|||
|
}
|
|||
|
|
|||
|
public void FrameEvent(string path)
|
|||
|
{
|
|||
|
treeView.JumpToEvent(path);
|
|||
|
}
|
|||
|
|
|||
|
private void BeginInspectorPopup(SerializedProperty property, TypeFilter typeFilter)
|
|||
|
{
|
|||
|
treeView.TypeFilter = typeFilter;
|
|||
|
outputProperty = property;
|
|||
|
searchField.SetFocus();
|
|||
|
treeView.DragEnabled = false;
|
|||
|
}
|
|||
|
|
|||
|
private void BeginStandaloneWindow()
|
|||
|
{
|
|||
|
treeView.TypeFilter = TypeFilter.All;
|
|||
|
outputProperty = null;
|
|||
|
searchField.SetFocus();
|
|||
|
treeView.DragEnabled = true;
|
|||
|
}
|
|||
|
|
|||
|
public void OnEnable()
|
|||
|
{
|
|||
|
if (treeViewState == null)
|
|||
|
{
|
|||
|
treeViewState = new TreeView.State();
|
|||
|
}
|
|||
|
|
|||
|
searchField = new SearchField();
|
|||
|
treeView = new TreeView(treeViewState);
|
|||
|
|
|||
|
ReadEventCache();
|
|||
|
|
|||
|
searchField.downOrUpArrowKeyPressed += treeView.SetFocus;
|
|||
|
|
|||
|
#if UNITY_2019_1_OR_NEWER
|
|||
|
SceneView.duringSceneGui += SceneUpdate;
|
|||
|
#else
|
|||
|
SceneView.onSceneGUIDelegate += SceneUpdate;
|
|||
|
#endif
|
|||
|
|
|||
|
EditorApplication.hierarchyWindowItemOnGUI += HierarchyUpdate;
|
|||
|
|
|||
|
IsOpen = true;
|
|||
|
}
|
|||
|
|
|||
|
public void OnDestroy()
|
|||
|
{
|
|||
|
EditorUtils.PreviewStop();
|
|||
|
|
|||
|
IsOpen = false;
|
|||
|
}
|
|||
|
|
|||
|
private static bool IsDraggable(UnityEngine.Object data)
|
|||
|
{
|
|||
|
return data is EditorEventRef || data is EditorBankRef || data is EditorParamRef;
|
|||
|
}
|
|||
|
|
|||
|
public static bool IsDroppable(UnityEngine.Object[] data)
|
|||
|
{
|
|||
|
return data.Length > 0 && IsDraggable(data[0]);
|
|||
|
}
|
|||
|
|
|||
|
// This is an event handler on the hierachy view to handle dragging our objects from the browser
|
|||
|
void HierarchyUpdate(int instance, Rect rect)
|
|||
|
{
|
|||
|
if (Event.current.type == EventType.DragPerform && rect.Contains(Event.current.mousePosition))
|
|||
|
{
|
|||
|
if (IsDroppable(DragAndDrop.objectReferences))
|
|||
|
{
|
|||
|
UnityEngine.Object data = DragAndDrop.objectReferences[0];
|
|||
|
|
|||
|
GameObject target = EditorUtility.InstanceIDToObject(instance) as GameObject;
|
|||
|
|
|||
|
if (data is EditorEventRef)
|
|||
|
{
|
|||
|
Undo.SetCurrentGroupName("Add Studio Event Emitter");
|
|||
|
|
|||
|
StudioEventEmitter emitter = Undo.AddComponent<StudioEventEmitter>(target);
|
|||
|
emitter.Event = (data as EditorEventRef).Path;
|
|||
|
}
|
|||
|
else if (data is EditorBankRef)
|
|||
|
{
|
|||
|
Undo.SetCurrentGroupName("Add Studio Bank Loader");
|
|||
|
|
|||
|
StudioBankLoader loader = Undo.AddComponent<StudioBankLoader>(target);
|
|||
|
loader.Banks = new List<string>();
|
|||
|
loader.Banks.Add((data as EditorBankRef).Name);
|
|||
|
}
|
|||
|
else // data is EditorParamRef
|
|||
|
{
|
|||
|
Undo.SetCurrentGroupName("Add Studio Global Parameter Trigger");
|
|||
|
|
|||
|
StudioGlobalParameterTrigger trigger = Undo.AddComponent<StudioGlobalParameterTrigger>(target);
|
|||
|
trigger.parameter = (data as EditorParamRef).Name;
|
|||
|
}
|
|||
|
|
|||
|
Selection.activeObject = target;
|
|||
|
|
|||
|
Event.current.Use();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// This is an event handler on the scene view to handle dragging our objects from the browser
|
|||
|
// and creating new gameobjects
|
|||
|
void SceneUpdate(SceneView sceneView)
|
|||
|
{
|
|||
|
if (Event.current.type == EventType.DragPerform && IsDroppable(DragAndDrop.objectReferences))
|
|||
|
{
|
|||
|
UnityEngine.Object data = DragAndDrop.objectReferences[0];
|
|||
|
GameObject newObject;
|
|||
|
|
|||
|
if (data is EditorEventRef)
|
|||
|
{
|
|||
|
string path = (data as EditorEventRef).Path;
|
|||
|
|
|||
|
string name = path.Substring(path.LastIndexOf("/") + 1);
|
|||
|
newObject = new GameObject(name + " Emitter");
|
|||
|
|
|||
|
StudioEventEmitter emitter = newObject.AddComponent<StudioEventEmitter>();
|
|||
|
emitter.Event = path;
|
|||
|
|
|||
|
Undo.RegisterCreatedObjectUndo(newObject, "Create Studio Event Emitter");
|
|||
|
}
|
|||
|
else if (data is EditorBankRef)
|
|||
|
{
|
|||
|
newObject = new GameObject("Studio Bank Loader");
|
|||
|
|
|||
|
StudioBankLoader loader = newObject.AddComponent<StudioBankLoader>();
|
|||
|
loader.Banks = new List<string>();
|
|||
|
loader.Banks.Add((data as EditorBankRef).Name);
|
|||
|
|
|||
|
Undo.RegisterCreatedObjectUndo(newObject, "Create Studio Bank Loader");
|
|||
|
}
|
|||
|
else // data is EditorParamRef
|
|||
|
{
|
|||
|
string name = (data as EditorParamRef).Name;
|
|||
|
|
|||
|
newObject = new GameObject(name + " Trigger");
|
|||
|
|
|||
|
StudioGlobalParameterTrigger trigger = newObject.AddComponent<StudioGlobalParameterTrigger>();
|
|||
|
trigger.parameter = name;
|
|||
|
|
|||
|
Undo.RegisterCreatedObjectUndo(newObject, "Create Studio Global Parameter Trigger");
|
|||
|
}
|
|||
|
|
|||
|
Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
|
|||
|
object hit = HandleUtility.RaySnap(ray);
|
|||
|
|
|||
|
if (hit != null)
|
|||
|
{
|
|||
|
newObject.transform.position = ((RaycastHit)hit).point;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
newObject.transform.position = ray.origin + ray.direction * 10.0f;
|
|||
|
}
|
|||
|
|
|||
|
Selection.activeObject = newObject;
|
|||
|
Event.current.Use();
|
|||
|
}
|
|||
|
else if (Event.current.type == EventType.DragUpdated && IsDroppable(DragAndDrop.objectReferences))
|
|||
|
{
|
|||
|
DragAndDrop.visualMode = DragAndDropVisualMode.Move;
|
|||
|
DragAndDrop.AcceptDrag();
|
|||
|
Event.current.Use();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|