diff --git a/Assets/Merino/Editor/BackendData/MerinoTreeData.cs b/Assets/Merino/Editor/BackendData/MerinoTreeData.cs new file mode 100644 index 0000000..9e3755f --- /dev/null +++ b/Assets/Merino/Editor/BackendData/MerinoTreeData.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using UnityEngine; +using UnityEditor.TreeViewExamples; + +namespace Merino +{ + + public class MerinoTreeData : ScriptableObject + { + public int editedID = -1; + public double timestamp; + [SerializeField] List m_TreeElements = new List (); + + internal List treeElements + { + get { return m_TreeElements; } + set { m_TreeElements = value; } + } + + } +} diff --git a/Assets/Merino/Editor/BackendData/MyTreeAsset.cs.meta b/Assets/Merino/Editor/BackendData/MerinoTreeData.cs.meta similarity index 100% rename from Assets/Merino/Editor/BackendData/MyTreeAsset.cs.meta rename to Assets/Merino/Editor/BackendData/MerinoTreeData.cs.meta diff --git a/Assets/Merino/Editor/BackendData/MyTreeAsset.cs b/Assets/Merino/Editor/BackendData/MyTreeAsset.cs deleted file mode 100644 index a869827..0000000 --- a/Assets/Merino/Editor/BackendData/MyTreeAsset.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; -using UnityEditor.TreeViewExamples; - -namespace Merino -{ - - //[CreateAssetMenu (fileName = "TreeDataAsset", menuName = "Tree Asset", order = 1)] - public class MyTreeAsset : ScriptableObject - { - [SerializeField] List m_TreeElements = new List (); - - internal List treeElements - { - get { return m_TreeElements; } - set { m_TreeElements = value; } - } - - void Awake () - { - if (m_TreeElements.Count == 0) - m_TreeElements = MyTreeElementGenerator.GenerateRandomTree(160); - } - } - - public class MerinoTreeData : ScriptableObject - { - public int editedID = -1; - public double timestamp; - [SerializeField] List m_TreeElements = new List (); - - internal List treeElements - { - get { return m_TreeElements; } - set { m_TreeElements = value; } - } - - } -} diff --git a/Assets/Merino/Editor/BackendData/MyTreeAssetEditor.cs b/Assets/Merino/Editor/BackendData/MyTreeAssetEditor.cs deleted file mode 100644 index 7d2844f..0000000 --- a/Assets/Merino/Editor/BackendData/MyTreeAssetEditor.cs +++ /dev/null @@ -1,140 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; -using UnityEditor.IMGUI.Controls; -using UnityEditor; - -namespace Merino -{ - - [CustomEditor (typeof(MyTreeAsset))] - public class MyTreeAssetEditor : Editor - { - MyTreeView m_TreeView; - SearchField m_SearchField; - const string kSessionStateKeyPrefix = "TVS"; - - MyTreeAsset asset - { - get { return (MyTreeAsset) target; } - } - - void OnEnable () - { - Undo.undoRedoPerformed += OnUndoRedoPerformed; - - var treeViewState = new TreeViewState (); - var jsonState = SessionState.GetString (kSessionStateKeyPrefix + asset.GetInstanceID (), ""); - if (!string.IsNullOrEmpty (jsonState)) - JsonUtility.FromJsonOverwrite (jsonState, treeViewState); - var treeModel = new TreeModel (asset.treeElements); - m_TreeView = new MyTreeView(treeViewState, treeModel); - m_TreeView.beforeDroppingDraggedItems += OnBeforeDroppingDraggedItems; - m_TreeView.Reload (); - - m_SearchField = new SearchField (); - - m_SearchField.downOrUpArrowKeyPressed += m_TreeView.SetFocusAndEnsureSelectedItem; - } - - - void OnDisable () - { - Undo.undoRedoPerformed -= OnUndoRedoPerformed; - - SessionState.SetString (kSessionStateKeyPrefix + asset.GetInstanceID (), JsonUtility.ToJson (m_TreeView.state)); - } - - void OnUndoRedoPerformed () - { - if (m_TreeView != null) - { - m_TreeView.treeModel.SetData (asset.treeElements); - m_TreeView.Reload (); - } - } - - void OnBeforeDroppingDraggedItems (IList draggedRows) - { - Undo.RecordObject (asset, string.Format ("Moving {0} Item{1}", draggedRows.Count, draggedRows.Count > 1 ? "s" : "")); - } - - public override void OnInspectorGUI () - { - GUILayout.Space (5f); - ToolBar (); - GUILayout.Space (3f); - - const float topToolbarHeight = 20f; - const float spacing = 2f; - float totalHeight = m_TreeView.totalHeight + topToolbarHeight + 2 * spacing; - Rect rect = GUILayoutUtility.GetRect (0, 10000, 0, totalHeight); - Rect toolbarRect = new Rect (rect.x, rect.y, rect.width, topToolbarHeight); - Rect multiColumnTreeViewRect = new Rect (rect.x, rect.y + topToolbarHeight + spacing, rect.width, rect.height - topToolbarHeight - 2 * spacing); - SearchBar (toolbarRect); - DoTreeView (multiColumnTreeViewRect); - } - - void SearchBar (Rect rect) - { - m_TreeView.searchString = m_SearchField.OnGUI(rect, m_TreeView.searchString); - } - - void DoTreeView (Rect rect) - { - m_TreeView.OnGUI (rect); - } - - void ToolBar () - { - using (new EditorGUILayout.HorizontalScope ()) - { - var style = "miniButton"; - if (GUILayout.Button ("Expand All", style)) - { - m_TreeView.ExpandAll (); - } - - if (GUILayout.Button ("Collapse All", style)) - { - m_TreeView.CollapseAll (); - } - - GUILayout.FlexibleSpace (); - - if (GUILayout.Button ("Add Item", style)) - { - Undo.RecordObject (asset, "Add Item To Asset"); - - // Add item as child of selection - var selection = m_TreeView.GetSelection (); - TreeElement parent = (selection.Count == 1 ? m_TreeView.treeModel.Find (selection[0]) : null) ?? m_TreeView.treeModel.root; - int depth = parent != null ? parent.depth + 1 : 0; - int id = m_TreeView.treeModel.GenerateUniqueID (); - var element = new MerinoTreeElement ("Item " + id, depth, id); - m_TreeView.treeModel.AddElement(element, parent, 0); - - // Select newly created element - m_TreeView.SetSelection (new[] {id}, TreeViewSelectionOptions.RevealAndFrame); - } - - if (GUILayout.Button ("Remove Item", style)) - { - Undo.RecordObject (asset, "Remove Item From Asset"); - var selection = m_TreeView.GetSelection (); - m_TreeView.treeModel.RemoveElements (selection); - } - } - } - - - class MyTreeView : TreeViewWithTreeModel - { - public MyTreeView(TreeViewState state, TreeModel model) - : base(state, model) - { - showBorder = true; - showAlternatingRowBackgrounds = true; - } - } - } -} diff --git a/Assets/Merino/Editor/BackendData/MyTreeAssetEditor.cs.meta b/Assets/Merino/Editor/BackendData/MyTreeAssetEditor.cs.meta deleted file mode 100644 index c4cf765..0000000 --- a/Assets/Merino/Editor/BackendData/MyTreeAssetEditor.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: 7489aa2dcfff17d49bc0775cfcc18578 -timeCreated: 1479133171 -licenseType: Pro -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Merino/Editor/BackendData/MyTreeElementGenerator.cs b/Assets/Merino/Editor/BackendData/MyTreeElementGenerator.cs deleted file mode 100644 index e980d0e..0000000 --- a/Assets/Merino/Editor/BackendData/MyTreeElementGenerator.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Collections.Generic; -using Random = UnityEngine.Random; -using UnityEditor.TreeViewExamples; - -namespace Merino -{ - - static class MyTreeElementGenerator - { - static int IDCounter; - static int minNumChildren = 5; - static int maxNumChildren = 10; - static float probabilityOfBeingLeaf = 0.5f; - - public static List GenerateRandomTree(int numTotalElements) - { - int numRootChildren = numTotalElements / 4; - IDCounter = 0; - var treeElements = new List(numTotalElements); - - var root = new MerinoTreeElement("Root", -1, IDCounter); - treeElements.Add(root); - for (int i = 0; i < numRootChildren; ++i) - { - int allowedDepth = 6; - AddChildrenRecursive(root, Random.Range(minNumChildren, maxNumChildren), true, numTotalElements, ref allowedDepth, treeElements); - } - - return treeElements; - } - static void AddChildrenRecursive(TreeElement element, int numChildren, bool force, int numTotalElements, ref int allowedDepth, List treeElements) - { - if (element.depth >= allowedDepth) - { - allowedDepth = 0; - return; - } - - for (int i = 0; i < numChildren; ++i) - { - if (IDCounter > numTotalElements) - return; - - var child = new MerinoTreeElement("Element " + IDCounter, element.depth + 1, ++IDCounter); - treeElements.Add(child); - - if (!force && Random.value < probabilityOfBeingLeaf) - continue; - - AddChildrenRecursive(child, Random.Range(minNumChildren, maxNumChildren), false, numTotalElements, ref allowedDepth, treeElements); - } - } - } -} diff --git a/Assets/Merino/Editor/BackendData/MyTreeElementGenerator.cs.meta b/Assets/Merino/Editor/BackendData/MyTreeElementGenerator.cs.meta deleted file mode 100644 index 23db13c..0000000 --- a/Assets/Merino/Editor/BackendData/MyTreeElementGenerator.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: be52582de2246c741b4ce116b33eca27 -timeCreated: 1479294126 -licenseType: Pro -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Merino/Editor/MerinoEditorWindow.cs b/Assets/Merino/Editor/MerinoEditorWindow.cs index a6fcab0..dd16af5 100644 --- a/Assets/Merino/Editor/MerinoEditorWindow.cs +++ b/Assets/Merino/Editor/MerinoEditorWindow.cs @@ -6,7 +6,6 @@ using System.IO; using System.Linq; using UnityEditor; -using UnityEditor.Callbacks; using UnityEditor.IMGUI.Controls; using UnityEngine; using Yarn; @@ -32,41 +31,61 @@ public MerinoTreeView treeView { } // double lastTabTime = 0.0; // this is a really bad hack - // UI settings - [SerializeField] bool stopOnDialogueEnd = true; - [SerializeField] bool autoAdvance = false; + // preferences + static bool prefsLoaded = false; - [SerializeField] bool useAutosave = false; - bool doubleClickOpensFile = true; + static string newFileTemplatePath = "NewFileTemplate.yarn"; + static string tempDataPath = "Assets/Merino/Editor/MerinoTempData.asset"; + + static Color highlightComments = new Color(0.3f, 0.6f, 0.25f); + static Color highlightCommands = new Color(0.8f, 0.5f, 0.1f); + static Color highlightNodeOptions = new Color(0.8f, 0.4f, 0.6f); + static Color highlightShortcutOptions = new Color(0.2f, 0.6f, 0.7f); + // public static Color highlightVariables; + + // UI settings, will still get saved by Hidden EditorPrefs + static bool stopOnDialogueEnd = true; + static bool useAutoAdvance = false; + static bool useAutosave = true; + static float sidebarWidth = 180f; + + const int margin = 10; [SerializeField] Vector2 scrollPos; - [NonSerialized] float sidebarWidth = 180f; + bool resizingSidebar = false; + float playPreviewHeight { get { return isDialogueRunning ? 180f : 0f; } } - Rect multiColumnTreeViewRect + + Rect sidebarSearchRect + { + get { return new Rect (margin, margin, sidebarWidth, margin*2); } + } + + Rect sidebarRect { - get { return new Rect(10, 30, sidebarWidth, position.height-40); } + get { return new Rect(margin, 30, sidebarWidth, position.height-40); } } - Rect toolbarRect + Rect sidebarResizeRect { - get { return new Rect (10f, 10f, sidebarWidth, 20f); } + get { return new Rect(margin + sidebarWidth, 0, 5, position.height); } } Rect nodeEditRect { - get { return new Rect( sidebarWidth+15f, 10, position.width-sidebarWidth-20, position.height-20-playPreviewHeight);} // was height-30 + get { return new Rect( sidebarWidth+margin*2, margin, position.width-sidebarWidth-margin*3, position.height-margin*2-playPreviewHeight);} // was height-30 } Rect playPreviewRect { - get { return new Rect( sidebarWidth+15f, position.height-10-playPreviewHeight, position.width-sidebarWidth-15, playPreviewHeight-10);} + get { return new Rect( sidebarWidth+margin*2, position.height-margin-playPreviewHeight, position.width-sidebarWidth-15, playPreviewHeight-margin);} } - Rect bottomToolbarRect - { - get { return new Rect(20f, position.height - 18f, position.width - 40f, 16f); } - } +// Rect bottomToolbarRect +// { +// get { return new Rect(20f, position.height - 18f, position.width - 40f, 16f); } +// } // misc resources Texture helpIcon; @@ -106,7 +125,7 @@ Dialogue dialogue { } #region EditorWindowStuff - + [MenuItem("Window/Merino (Yarn Editor)")] public static MerinoEditorWindow GetWindow () { @@ -116,6 +135,89 @@ public static MerinoEditorWindow GetWindow () window.Repaint(); return window; } + + static void ResetEditorPrefsAll() + { + newFileTemplatePath = "NewFileTemplate.yarn"; + + highlightComments = new Color(0.3f, 0.6f, 0.25f); + highlightCommands = new Color(0.8f, 0.5f, 0.1f); + highlightNodeOptions = new Color(0.8f, 0.4f, 0.6f); + highlightShortcutOptions = new Color(0.2f, 0.6f, 0.7f); + + SaveEditorPrefs(); + + stopOnDialogueEnd = true; + useAutoAdvance = false; + useAutosave = true; + sidebarWidth = 180f; + + SaveHiddenEditorPrefs(); + } + + public static void LoadEditorPrefs() + { + if (EditorPrefs.HasKey("MerinoFirstRun") == false) + { + SaveEditorPrefs(); + EditorPrefs.SetBool("MerinoFirstRun", true); + } + newFileTemplatePath = EditorPrefs.GetString("MerinoTemplatePath", newFileTemplatePath); + + ColorUtility.TryParseHtmlString(EditorPrefs.GetString("MerinoHighlightCommands"), out highlightCommands); + ColorUtility.TryParseHtmlString(EditorPrefs.GetString("MerinoHighlightComments"), out highlightComments); + ColorUtility.TryParseHtmlString(EditorPrefs.GetString("MerinoHighlightNodeOptions"), out highlightNodeOptions); + ColorUtility.TryParseHtmlString(EditorPrefs.GetString("MerinoHighlightShortcutOptions"), out highlightShortcutOptions); + } + + public static void SaveEditorPrefs() + { + EditorPrefs.SetString("MerinoTemplatePath", newFileTemplatePath); + + EditorPrefs.SetString("MerinoHighlightCommands", "#"+ColorUtility.ToHtmlStringRGB(highlightCommands) ); + EditorPrefs.SetString("MerinoHighlightComments", "#"+ColorUtility.ToHtmlStringRGB(highlightComments) ); + EditorPrefs.SetString("MerinoHighlightNodeOptions", "#"+ColorUtility.ToHtmlStringRGB(highlightNodeOptions) ); + EditorPrefs.SetString("MerinoHighlightShortcutOptions", "#"+ColorUtility.ToHtmlStringRGB(highlightShortcutOptions) ); + + } + + [PreferenceItem("Merino (Yarn)")] + public static void MerinoPreferencesGUI() + { + // Load the preferences + if (!prefsLoaded) + { + LoadEditorPrefs(); + prefsLoaded = true; + } + + + // Reset button + if (GUILayout.Button("Reset Merino to default settings")) + { + ResetEditorPrefsAll(); + SaveEditorPrefs(); + } + + // Preferences GUI + GUILayout.Label("File Handling", EditorStyles.boldLabel); + EditorGUILayout.Space(); + GUILayout.Label("New File Template filepath (relative to /Resources/, omit .txt)"); + newFileTemplatePath = EditorGUILayout.TextField("/Resources/", newFileTemplatePath); + + GUILayout.Label("Syntax Highlighting Colors", EditorStyles.boldLabel); + highlightCommands = EditorGUILayout.ColorField("<>", highlightCommands); + highlightComments = EditorGUILayout.ColorField("// Comments", highlightComments); + highlightNodeOptions = EditorGUILayout.ColorField("[[NodeOptions]]", highlightNodeOptions); + highlightShortcutOptions = EditorGUILayout.ColorField("-> ShortcutOptions", highlightShortcutOptions); + + // Save the preferences + if (GUI.changed) + { + SaveEditorPrefs(); + } + + } void ResetMerino() { @@ -123,16 +225,42 @@ void ResetMerino() viewState = null; m_Initialized = false; ForceStopDialogue(); + AssetDatabase.DeleteAsset(tempDataPath); // delete tempdata, or else it will just get reloaded again Selection.objects = new UnityEngine.Object[0]; // deselect all Undo.undoRedoPerformed -= OnUndo; InitIfNeeded(true); } + + static void LoadHiddenEditorPrefs() + { + if (EditorPrefs.HasKey("MerinoStopOn") == false) + { + SaveHiddenEditorPrefs(); // save defaults if not found + } + stopOnDialogueEnd = EditorPrefs.GetBool("MerinoStopOn"); + useAutoAdvance = EditorPrefs.GetBool("MerinoAutoAdvance"); + useAutosave = EditorPrefs.GetBool("MerinoAutosave"); + sidebarWidth = EditorPrefs.GetFloat("MerinoSidebarWidth"); + } + + static void SaveHiddenEditorPrefs() + { + EditorPrefs.SetBool("MerinoStopOn", stopOnDialogueEnd); + EditorPrefs.SetBool("MerinoAutoAdvance", useAutoAdvance); + EditorPrefs.SetBool("MerinoAutosave", useAutosave); + EditorPrefs.SetFloat("MerinoSidebarWidth", sidebarWidth); + } void InitIfNeeded (bool ignoreSelection=false) { if (m_Initialized) return; Undo.undoRedoPerformed += OnUndo; + undoData.Clear(); + + // default highlight colors + LoadEditorPrefs(); + LoadHiddenEditorPrefs(); // load font if (monoFont == null) @@ -151,21 +279,31 @@ void InitIfNeeded (bool ignoreSelection=false) viewState = new TreeViewState(); bool firstInit = m_MultiColumnHeaderState == null; - var headerState = MerinoTreeView.CreateDefaultMultiColumnHeaderState(multiColumnTreeViewRect.width); + var headerState = MerinoTreeView.CreateDefaultMultiColumnHeaderState(sidebarRect.width); if (MultiColumnHeaderState.CanOverwriteSerializedFields(m_MultiColumnHeaderState, headerState)) MultiColumnHeaderState.OverwriteSerializedFields(m_MultiColumnHeaderState, headerState); m_MultiColumnHeaderState = headerState; - var multiColumnHeader = new MyMultiColumnHeader(headerState); + var multiColumnHeader = new MerinoMultiColumnHeader(headerState); if (firstInit) multiColumnHeader.ResizeToFit (); - treeData = ScriptableObject.CreateInstance(); - undoData.Clear(); - var treeModel = new TreeModel(GetData()); - - m_TreeView = new MerinoTreeView(viewState, multiColumnHeader, treeModel); + // detect temp data (e.g. when going into play mode and back) + var possibleTempData = AssetDatabase.LoadAssetAtPath(tempDataPath); + if (possibleTempData == null) + { + treeData = ScriptableObject.CreateInstance(); + AssetDatabase.CreateAsset(treeData, tempDataPath); + AssetDatabase.SaveAssets(); + } + else + { + treeData = possibleTempData; + } + // generate sidebar data structures + var treeModel = new TreeModel(GetData(null, true)); + m_TreeView = new MerinoTreeView(viewState, multiColumnHeader, treeModel); m_SearchField = new SearchField(); m_SearchField.downOrUpArrowKeyPressed += m_TreeView.SetFocusAndEnsureSelectedItem; @@ -177,6 +315,7 @@ void InitIfNeeded (bool ignoreSelection=false) } } + // called when something get selected in Project tab void OnSelectionChange () { if (!m_Initialized) @@ -200,18 +339,6 @@ void OnSelectionChange () } } - [OnOpenAsset] - public static bool OnOpenAsset (int instanceID, int line) - { - var myTextAsset = EditorUtility.InstanceIDToObject(instanceID) as TextAsset; - if (myTextAsset != null) - { - var window = GetWindow (); - return window.SetTreeAsset(myTextAsset); - } - return false; // we did not handle the open - } - // This gets called many times a second public void Update() { @@ -222,7 +349,7 @@ public void Update() lastSaveTime = EditorApplication.timeSinceStartup + 9999; // don't save again until SaveDataToFile resets the variable } - if (isDialogueRunning) + if (isDialogueRunning || resizingSidebar) { // MASSIVELY improves framerate, wow, amazing Repaint(); @@ -240,7 +367,6 @@ void OnDestroy() Undo.undoRedoPerformed -= OnUndo; Undo.ClearUndo(treeData); - DestroyImmediate(treeData,true); undoData.Clear(); ForceStopDialogue(); @@ -259,7 +385,7 @@ void OnUndo() for( int i=undoData.Count-1; i>0 && treeData.timestamp < undoData[i].time; i-- ) { var recent = undoData[i]; - var treeDataNode = treeData.treeElements.Where(x => x.id == recent.id).First(); + var treeDataNode = treeData.treeElements.First(x => x.id == recent.id); moveCursorUndoID = recent.id; @@ -282,20 +408,6 @@ void OnUndo() #region LoadingAndSaving - - bool SetTreeAsset (TextAsset myTextAsset) - { - if (doubleClickOpensFile && IsProbablyYarnFile(myTextAsset)) - { - currentFile = myTextAsset; - m_Initialized = false; - return true; - } - else - { - return false; - } - } bool IsProbablyYarnFile(TextAsset textAsset) { @@ -309,21 +421,27 @@ bool IsProbablyYarnFile(TextAsset textAsset) } } - IList GetData (TextAsset source=null) + IList GetData (TextAsset source=null, bool isFromInit=false) { var treeElements = new List(); var root = new MerinoTreeElement("Root", -1, 0); treeElements.Add(root); - // extract data from the text file - if (source == null && currentFile != null) + // extract data from the text file, but only if it's not about entering playmode + if (source == null && currentFile != null ) { source = currentFile; } + + // hack hack hack: if we're calling GetData from init, then ignore playmode currentFile + if (isFromInit && EditorApplication.isPlayingOrWillChangePlaymode) + { + source = null; + } if (source != null) { AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(source)); - //var format = YarnSpinnerLoader.GetFormatFromFileName(AssetDatabase.GetAssetPath(currentFile)); + //var format = YarnSpinnerLoader.GetFormatFromFileName(AssetDatabase.GetAssetPath(currentFile)); // TODO: add JSON and ByteCode support? var nodes = YarnSpinnerLoader.GetNodesFromText(source.text, NodeFormat.Text); foreach (var node in nodes) { @@ -332,7 +450,7 @@ IList GetData (TextAsset source=null) string cleanBody = CleanYarnField(node.body); string cleanTags = CleanYarnField(node.tags, true); // write data to the objects - var newItem = new MerinoTreeElement( cleanName ,0,treeElements.Count); + var newItem = new MerinoTreeElement( cleanName, 0, treeElements.Count); newItem.nodeBody = cleanBody; newItem.nodePosition = new Vector2Int(node.position.x, node.position.y); newItem.nodeTags = cleanTags; @@ -341,15 +459,21 @@ IList GetData (TextAsset source=null) } else { - // load default data - var defaultData = Resources.Load("NewFileTemplate.yarn"); + // see if we can load temp data + if (treeData != null && treeData.treeElements != null && treeData.treeElements.Count > 0) + { + return treeData.treeElements; + } + + // otherwise, load default data from template + var defaultData = Resources.Load(newFileTemplatePath); if (defaultData != null) { return GetData(defaultData); } else - { - Debug.LogError("Merino couldn't load default NewFileTemplate.yarn.txt... by default, it is in Assets/Merino/Editor/Resources/"); + { // oops, couldn't find the new file template! + Debug.LogErrorFormat("Merino couldn't load default data for a new Yarn file! Looked for /Resources/{0}.txt ... by default, it is in Assets/Merino/Editor/Resources/NewFileTemplate.yarn.txt and the preference is set to [NewFileTemplate.yarn]", newFileTemplatePath); return null; } @@ -384,7 +508,8 @@ string SaveNodesAsString() // gather data ValidateNodeTitles(); var nodeInfo = new List(); - var treeNodes = treeData.treeElements; // m_TreeView.treeModel.root.children; + // grab nodes based on visible order in the hierarchy tree view (sorting) + var treeNodes = treeView.GetRows().Select(x => treeView.treeModel.Find(x.id)).ToArray(); // treeData.treeElements; // m_TreeView.treeModel.root.children; // save data to string foreach (var item in treeNodes) { @@ -536,7 +661,7 @@ IEnumerator RunDialogue (string startNode = "Start") // Wait for line to finish displaying var lineResult = step as Yarn.Dialogue.LineResult; - yield return this.StartCoroutine(this.dialogueUI.RunLine(lineResult.line, autoAdvance)); + yield return this.StartCoroutine(this.dialogueUI.RunLine(lineResult.line, useAutoAdvance)); // while (dialogueUI.inputContinue == false) // { // yield return new WaitForSeconds(0.01f); @@ -561,7 +686,7 @@ IEnumerator RunDialogue (string startNode = "Start") // if (DispatchCommand(commandResult.command.text) == true) { // // command was dispatched // } else { - yield return this.StartCoroutine( dialogueUI.RunCommand(commandResult.command, autoAdvance)); + yield return this.StartCoroutine( dialogueUI.RunCommand(commandResult.command, useAutoAdvance)); // } @@ -595,8 +720,9 @@ void OnGUI () { InitIfNeeded(); - SearchBar (toolbarRect); - DoTreeView (multiColumnTreeViewRect); + DrawSidebarSearch (sidebarSearchRect); + DrawSidebar (sidebarRect); + ResizeSidebar(); if (viewState != null) { @@ -605,7 +731,7 @@ void OnGUI () if (dialogueUI != null && isDialogueRunning) { - PreviewToolbar(playPreviewRect); + DrawPlaytestToolbar(playPreviewRect); // if not pressed, then show the normal play preview dialogueUI.OnGUI(playPreviewRect); } @@ -613,7 +739,33 @@ void OnGUI () // BottomToolBar (bottomToolbarRect); } - void PreviewToolbar(Rect rect) + const int sidebarWidthClamp = 50; + + // from https://answers.unity.com/questions/546686/editorguilayout-split-view-resizable-scroll-view.html + private void ResizeSidebar(){ + EditorGUIUtility.AddCursorRect( sidebarResizeRect,MouseCursor.SplitResizeLeftRight); + + // start resizing + if( Event.current.type == EventType.MouseDown && sidebarResizeRect.Contains(Event.current.mousePosition)) + { + resizingSidebar = true; + } + + // do resize + if(resizingSidebar){ + sidebarWidth = Mathf.Clamp(Event.current.mousePosition.x - 10, sidebarWidthClamp, position.width-sidebarWidth); + // cursorChangeRect.Set(cursorChangeRect.x,currentScrollViewHeight,cursorChangeRect.width,cursorChangeRect.height); + } + + // stop resizing + if (Event.current.rawType == EventType.MouseUp) + { + resizingSidebar = false; + SaveHiddenEditorPrefs(); + } + } + + void DrawPlaytestToolbar(Rect rect) { // toolbar GUILayout.BeginArea(rect); @@ -662,7 +814,9 @@ void PreviewToolbar(Rect rect) GUI.enabled = true; GUILayout.FlexibleSpace(); - // stop on dialogue end + + // begin some settings + EditorGUI.BeginChangeCheck(); var smallToggleStyle = new GUIStyle(); smallToggleStyle.fontSize = 10; smallToggleStyle.alignment = TextAnchor.MiddleLeft; @@ -671,10 +825,16 @@ void PreviewToolbar(Rect rect) smallToggleStyle.normal.textColor = Color.gray; } - autoAdvance = EditorGUILayout.ToggleLeft(new GUIContent("AutoAdvance", "automatically advance dialogue, with no user input, until there's a choice"), autoAdvance, smallToggleStyle, GUILayout.Width(100)); + // auto advance button + useAutoAdvance = EditorGUILayout.ToggleLeft(new GUIContent("AutoAdvance", "automatically advance dialogue, with no user input, until there's a choice"), useAutoAdvance, smallToggleStyle, GUILayout.Width(100)); GUILayout.FlexibleSpace(); - // stop on dialogue end + // stop on dialogue end button stopOnDialogueEnd = EditorGUILayout.ToggleLeft(new GUIContent("CloseOnEnd", "when dialogue terminates, stop and close playtest session automatically"), stopOnDialogueEnd, smallToggleStyle, GUILayout.Width(100)); + if (EditorGUI.EndChangeCheck()) + { + SaveHiddenEditorPrefs(); // remember new settings + } + // stop button var backupColor = GUI.backgroundColor; GUI.backgroundColor = Color.red; @@ -682,18 +842,19 @@ void PreviewToolbar(Rect rect) { ForceStopDialogue(); } - GUI.backgroundColor = backupColor; + + // close out toolbar EditorGUILayout.EndHorizontal(); GUILayout.EndArea(); } - void SearchBar (Rect rect) + void DrawSidebarSearch (Rect rect) { treeView.searchString = m_SearchField.OnGUI (rect, treeView.searchString); } - void DoTreeView (Rect rect) + void DrawSidebar (Rect rect) { float BUTTON_HEIGHT = 20; var buttonRect = rect; @@ -720,16 +881,25 @@ void DrawMainPane(Rect rect) GUILayout.BeginArea(rect); GUILayout.BeginHorizontal(); + + // autosave / save button if (currentFile != null) { + EditorGUI.BeginChangeCheck(); useAutosave = EditorGUILayout.Toggle(new GUIContent("", "if enabled, will automatically save every change"), useAutosave, GUILayout.Width(16)); GUILayout.Label(new GUIContent("AutoSave? ", "if enabled, will automatically save every change"), GUILayout.Width(0), GUILayout.ExpandWidth(true), GUILayout.MaxWidth(80) ); + if (EditorGUI.EndChangeCheck()) + { + SaveHiddenEditorPrefs(); + } if (!useAutosave && GUILayout.Button( new GUIContent("Save", "save all changes to the current .yarn.txt file"), GUILayout.MaxWidth(60))) { SaveDataToFile(); AssetDatabase.ImportAsset( AssetDatabase.GetAssetPath(currentFile)); } } + + // save as button if (GUILayout.Button( new GUIContent("Save As...", "save all changes as a new .yarn.txt file"), GUILayout.MaxWidth(80))) { string defaultPath = Application.dataPath + "/"; @@ -780,8 +950,9 @@ void DrawMainPane(Rect rect) } } GUILayout.FlexibleSpace(); - // help and documentation - if (GUILayout.Button(new GUIContent(helpIcon, "click to open Merino help / documentation in web browser"), EditorStyles.label, GUILayout.Width(24)) ) + // help and documentation, with short and long buttons + if ( (position.width <= 800 && GUILayout.Button(new GUIContent(helpIcon, "click to open Merino help page / documentation in web browser"), EditorStyles.label, GUILayout.Width(24)) ) + || (position.width > 800 && GUILayout.Button(new GUIContent(" Help & Documentation", helpIcon, "click to open Merino help page / documentation in web browser"), GUILayout.Width(160))) ) { Help.BrowseURL("https://github.com/radiatoryang/merino/wiki"); } @@ -825,7 +996,6 @@ void DrawMainPane(Rect rect) // NODE BODY var backupContentColor = GUI.contentColor; - GUI.contentColor = GUI.enabled==false ? backupContentColor : new Color ( 0f, 0f, 0f, 0.16f ); string passage = m_TreeView.treeModel.Find(id).nodeBody; float height = EditorStyles.textArea.CalcHeight(new GUIContent(passage), rect.width); @@ -834,33 +1004,64 @@ void DrawMainPane(Rect rect) var te = typeof(EditorGUI) .GetField("activeEditor", BindingFlags.Static | BindingFlags.NonPublic) .GetValue(null) as TextEditor; - GUI.SetNextControlName ( "TextArea" + newName ); - // draw the body + // start preparing to draw the body + int lineDigits = -1; + var lineNumbers = AddLineNumbers(passage, out lineDigits); var bodyStyle = new GUIStyle( EditorStyles.textArea ); bodyStyle.font = monoFont; - string newBody = EditorGUILayout.TextArea(passage, bodyStyle, GUILayout.Height(0f), GUILayout.ExpandHeight(true), GUILayout.MaxHeight(height)); - GUI.contentColor = backupContentColor; + bodyStyle.margin = new RectOffset(lineDigits * 12 + 10, 4, 4, 4); // make room for the line numbers!!! + + // at around 250-300+ lines, Merino will start giving error messages and line numbers will break: "String too long for TextMeshGenerator. Cutting off characters." + // because Unity EditorGUI TextArea has a limit of 16382 characters, extra-long nodes must be chunked into multiple TextAreas (let's say, every 200 lines?) + const int chunkSize = 200; + // chop passage and line numbers into 200 lines + var linebreak = new string[] {"\n"}; + string[] passageLines = passage.Split(linebreak, StringSplitOptions.None); + string[] numberLines = lineNumbers.Split(linebreak, StringSplitOptions.None); - // only run the fancier stuff if we're not in playtesting mode - if (GUI.enabled) + // recombine into lines chunks of 200 lines + int chunkCount = Mathf.CeilToInt(1f * passageLines.Length / chunkSize); + string[] passageChunks = new string[chunkCount]; + string[] numberChunks = new string[chunkCount]; + for (int i = 0; i < passageLines.Length; i+=chunkSize) { - // manual tab support for GUILayout.TextArea... no longer needed for EditorGUILayout.TextArea -// string oldBody = newBody; -// newBody = KeyboardTabSupport(newBody, te); -// if (oldBody.Length != newBody.Length) -// { -// forceSave = true; -// } - + int chunkIndex = Mathf.CeilToInt(1f * i / chunkSize); + passageChunks[chunkIndex] = string.Join( linebreak[0], passageLines.Skip(i).Take(chunkSize).ToArray() ); + numberChunks[chunkIndex] = string.Join( linebreak[0], numberLines.Skip(i).Take(chunkSize).ToArray() ); + } + + // draw chunks as TextAreas, each with their own highlighting and line number overlays + var newBodies = new string[chunkCount]; + for( int x=0; x= 0 && te != null && GUIUtility.keyboardControl == te.controlID ) { te.cursorIndex = moveCursorUndoIndex; te.selectIndex = moveCursorUndoIndex; - // moveCursorUndo* will get blanked out after the foreach loop + // moveCursorUndo* will get blanked out after the foreach node loop } - // detect whether to increment the undo group + // detect whether to increment the undo group based on typing whitespace / word breaks if (te != null && GUIUtility.keyboardControl == te.controlID) { var e = Event.current; @@ -869,7 +1070,7 @@ void DrawMainPane(Rect rect) Undo.IncrementCurrentGroup(); spaceWillIncrementUndo = false; } - + // if user presses something other than SPACE, then let whitespace increment undo again if (!spaceWillIncrementUndo && e.isKey && e.keyCode != KeyCode.Space) { spaceWillIncrementUndo = true; @@ -877,22 +1078,20 @@ void DrawMainPane(Rect rect) } // syntax highlight via label overlay - Rect lastRect = GUILayoutUtility.GetLastRect(); + Rect lastRect = new Rect(bodyRect); lastRect.x += 2; lastRect.y += 1f; lastRect.width -= 6; lastRect.height -= 1; - string syntaxHighlight = DoSyntaxMarch(newBody); - - GUIStyle highlightOverlay = new GUIStyle(); - highlightOverlay.font = monoFont; - highlightOverlay.normal.textColor = (EditorGUIUtility.isProSkin ? Color.white : Color.black) * 0.8f; - highlightOverlay.richText = true; - highlightOverlay.wordWrap = true; - + highlightOverlay.normal.textColor *= 2; + string syntaxHighlight = DoSyntaxMarch(newBodies[x]); GUI.Label(lastRect, syntaxHighlight, highlightOverlay); - } + } // end of textAreas + + // combine all bodies into a single string for saving + var newBody = string.Join("\n", newBodies); + // playtest preview button at bottom of node if (GUILayout.Button(new GUIContent("▶ Playtest", "click to playtest this node"), GUILayout.Width(80) ) ) { idToPreview = id; @@ -906,9 +1105,10 @@ void DrawMainPane(Rect rect) // did user edit something? if (EditorGUI.EndChangeCheck() ) { - // undo stuff + // undo begin Undo.RecordObject(treeData, "Merino > " + newName ); + // actually commit the data now m_TreeView.treeModel.Find(id).name = newName; m_TreeView.treeModel.Find(id).nodeBody = newBody; treeData.editedID = id; @@ -917,6 +1117,7 @@ void DrawMainPane(Rect rect) // log the undo data undoData.Add( new MerinoUndoLog(id, EditorApplication.timeSinceStartup, newBody) ); + // save after commit if we're autosaving if (currentFile != null && useAutosave) { SaveDataToFile(); @@ -954,7 +1155,7 @@ void DrawMainPane(Rect rect) GUILayout.EndArea(); } - #region Utility + #region MainPaneUtility // I can't believe I finally got this to work, wow // only needed for GUILayout.TextArea @@ -998,16 +1199,16 @@ Color CheckSyntax (string syntax ) string newSyntax = syntax.Replace("\t", "").TrimEnd(' ').TrimStart(' '); // cleanup string if ( newSyntax.StartsWith ( "//" ) ) { - return new Color(0.3f, 0.6f, 0.25f); + return highlightComments; } else if (newSyntax.StartsWith("->") ) { - return new Color(0.8f, 0.5f, 0.1f); + return highlightShortcutOptions; } else if (newSyntax.StartsWith("[[")) { - return new Color(0.8f, 0.4f, 0.6f); + return highlightNodeOptions; }else if (newSyntax.StartsWith("<<")) { - return new Color(0f, 0.6f, 0.7f); + return highlightShortcutOptions; } else { @@ -1015,6 +1216,32 @@ Color CheckSyntax (string syntax ) } } + // given a long string with line breaks, it will tranpose line numbers to them all (and hide the actual text with rich text Color) + string AddLineNumbers(string input, out int digits) + { + var lines = input.Split(new string[] {"\n"}, StringSplitOptions.None ); + digits = Mathf.CeilToInt(1f * lines.Length / 10).ToString().Length + 1; + string invisibleBegin = ""; + string invisibleEnd = ""; + for (int i = 0; i < lines.Length; i++) + { + // generate line numbers + string lineDisplay = i.ToString(); + lineDisplay = lineDisplay.PadLeft(digits); + + // make sure the line will be long enough + if (lines[i].Length < digits) + { + lines[i] = lines[i].PadRight(digits); + } + + // add line number to line + lines[i] = lineDisplay + invisibleBegin + lines[i].Remove(0, digits) + invisibleEnd; + } + + return string.Join("\n", lines); + } + /* void BottomToolBar (Rect rect) { GUILayout.BeginArea (rect); @@ -1391,10 +1618,11 @@ public void Clear () } - internal class MyMultiColumnHeader : MultiColumnHeader + internal class MerinoMultiColumnHeader : MultiColumnHeader { Mode m_Mode; - + //Texture helpIcon = EditorGUIUtility.IconContent("_Help").image; + public enum Mode { LargeHeader, @@ -1402,7 +1630,7 @@ public enum Mode MinimumHeaderWithoutSorting } - public MyMultiColumnHeader(MultiColumnHeaderState state) + public MerinoMultiColumnHeader(MultiColumnHeaderState state) : base(state) { mode = Mode.DefaultHeader; @@ -1425,7 +1653,7 @@ public Mode mode break; case Mode.DefaultHeader: canSort = true; - height = DefaultGUI.defaultHeight; + height = DefaultGUI.minimumHeight; break; case Mode.MinimumHeaderWithoutSorting: canSort = false; @@ -1434,6 +1662,23 @@ public Mode mode } } } + + public override void OnGUI(Rect rect, float xScroll) + { + // add extra "clear sorting" button if sorting is active + var clearSortRect = new Rect(rect); + clearSortRect.width = 18; + clearSortRect.height = clearSortRect.width; + rect.x += clearSortRect.width; + rect.width -= clearSortRect.width; + if (GUI.Button(clearSortRect, new GUIContent("x", "no sort / clear column sorting"), EditorStyles.miniButton)) + { + state.sortedColumnIndex = -1; + } + + // draw rest of the header + base.OnGUI(rect, xScroll); + } protected override void ColumnHeaderGUI (MultiColumnHeaderState.Column column, Rect headerRect, int columnIndex) { diff --git a/ProjectSettings/QualitySettings.asset b/ProjectSettings/QualitySettings.asset index 33b56c6..bed71ee 100644 --- a/ProjectSettings/QualitySettings.asset +++ b/ProjectSettings/QualitySettings.asset @@ -29,6 +29,12 @@ QualitySettings: vSyncCount: 0 lodBias: 0.3 maximumLODLevel: 0 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 particleRaycastBudget: 4 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 4 @@ -57,6 +63,12 @@ QualitySettings: vSyncCount: 0 lodBias: 0.4 maximumLODLevel: 0 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 particleRaycastBudget: 16 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 4 @@ -85,6 +97,12 @@ QualitySettings: vSyncCount: 1 lodBias: 0.7 maximumLODLevel: 0 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 particleRaycastBudget: 64 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 4 @@ -113,6 +131,12 @@ QualitySettings: vSyncCount: 1 lodBias: 1 maximumLODLevel: 0 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 particleRaycastBudget: 256 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 4 @@ -141,6 +165,12 @@ QualitySettings: vSyncCount: 1 lodBias: 1.5 maximumLODLevel: 0 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 particleRaycastBudget: 1024 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 4 @@ -169,23 +199,15 @@ QualitySettings: vSyncCount: 1 lodBias: 2 maximumLODLevel: 0 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 particleRaycastBudget: 4096 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 4 resolutionScalingFixedDPIFactor: 1 excludedTargetPlatforms: [] - m_PerPlatformDefaultQuality: - Android: 2 - Nintendo 3DS: 5 - Nintendo Switch: 5 - PS4: 5 - PSM: 5 - PSP2: 2 - Standalone: 5 - Tizen: 2 - WebGL: 3 - WiiU: 5 - Windows Store Apps: 5 - XboxOne: 5 - iPhone: 2 - tvOS: 2 + m_PerPlatformDefaultQuality: {}