From 785d6ab744111e16aada8b9d107d4d32131d9e71 Mon Sep 17 00:00:00 2001 From: desktop-maesty/steve Date: Wed, 1 Aug 2018 20:20:57 +1000 Subject: [PATCH] EventSelectorPopup chooses correct starting index based on given current event name WIP Move CommandSelector to its own PopupWindowContext --- Assets/Fungus/Scripts/Editor/BlockEditor.cs | 330 +++--------------- .../CommandSelectorPopupWindowContent.cs | 225 ++++++++++++ .../CommandSelectorPopupWindowContent.cs.meta | 11 + .../Editor/EventSelectorPopupWindowContent.cs | 30 +- 4 files changed, 301 insertions(+), 295 deletions(-) create mode 100644 Assets/Fungus/Scripts/Editor/CommandSelectorPopupWindowContent.cs create mode 100644 Assets/Fungus/Scripts/Editor/CommandSelectorPopupWindowContent.cs.meta diff --git a/Assets/Fungus/Scripts/Editor/BlockEditor.cs b/Assets/Fungus/Scripts/Editor/BlockEditor.cs index d739301d..c4e0544a 100644 --- a/Assets/Fungus/Scripts/Editor/BlockEditor.cs +++ b/Assets/Fungus/Scripts/Editor/BlockEditor.cs @@ -18,16 +18,6 @@ namespace Fungus.EditorUtils [CustomEditor(typeof(Block))] public class BlockEditor : Editor { - protected class AddCommandOperation - { - public Type commandType; - } - - - private static readonly char[] SPLIT_INPUT_ON = new char[] { ' ', '/', '\\' }; - private static readonly int MAX_PREVIEW_GRID = 7; - private static readonly string ELIPSIS = "..."; - public static List actionList = new List(); protected Texture2D upIcon; @@ -35,27 +25,9 @@ namespace Fungus.EditorUtils protected Texture2D addIcon; protected Texture2D duplicateIcon; protected Texture2D deleteIcon; - - protected string commandTextFieldContents = string.Empty; - protected int filteredCommandPreviewSelectedItem = 0; - protected Type commandSelectedByTextInput; - + private Rect lastEventPopupPos; - static List commandTypes; - static List eventHandlerTypes; - - static void CacheEventHandlerTypes() - { - eventHandlerTypes = EditorExtensions.FindDerivedTypes(typeof(EventHandler)).Where(x=>!x.IsAbstract).ToList(); - commandTypes = EditorExtensions.FindDerivedTypes(typeof(Command)).Where(x => !x.IsAbstract).ToList(); - } - - [UnityEditor.Callbacks.DidReloadScripts] - private static void OnScriptsReloaded() - { - CacheEventHandlerTypes(); - } protected virtual void OnEnable() { @@ -64,8 +36,6 @@ namespace Fungus.EditorUtils addIcon = FungusEditorResources.Add; duplicateIcon = FungusEditorResources.Duplicate; deleteIcon = FungusEditorResources.Delete; - - CacheEventHandlerTypes(); } public virtual void DrawBlockName(Flowchart flowchart) @@ -284,32 +254,6 @@ namespace Fungus.EditorUtils GUILayout.BeginHorizontal(); - - //handle movement along our selection grid before something else eats our inputs - if (Event.current.type == EventType.KeyDown) - { - //up down to change selection / esc to clear field - if (Event.current.keyCode == KeyCode.UpArrow) - { - filteredCommandPreviewSelectedItem--; - } - else if (Event.current.keyCode == KeyCode.DownArrow) - { - filteredCommandPreviewSelectedItem++; - } - - if (commandSelectedByTextInput != null && - Event.current.keyCode == KeyCode.Return || Event.current.keyCode == KeyCode.KeypadEnter) - { - AddCommandCallback(commandSelectedByTextInput); - commandSelectedByTextInput = null; - commandTextFieldContents = String.Empty; - //GUI.FocusControl("dummycontrol"); - Event.current.Use(); - filteredCommandPreviewSelectedItem = 0; - } - } - // Previous Command if ((Event.current.type == EventType.KeyDown) && (Event.current.keyCode == KeyCode.PageUp)) { @@ -338,13 +282,11 @@ namespace Fungus.EditorUtils GUILayout.FlexibleSpace(); - //should track if text actually changes and pass that to the ShowPartialMatches so it can cache - commandTextFieldContents = GUILayout.TextField(commandTextFieldContents, GUILayout.MinWidth(20), GUILayout.MaxWidth(200)); - + // Add Button if (GUILayout.Button(addIcon)) { - ShowCommandMenu(); + CommandSelectorPopupWindowContent.ShowCommandMenu(target as Block); } // Duplicate Button @@ -362,85 +304,10 @@ namespace Fungus.EditorUtils GUILayout.EndHorizontal(); - if (!string.IsNullOrEmpty(commandTextFieldContents)) - ShowPartialMatches(); } - //Handles showing partial matches against the text input next to the AddCommand button - // Splits and matches and can use up down arrows and return/enter/numenter to confirm - // TODO add sorting of results so we get best match at the not just just a match - // e.g. "if" should show Flow/If at the top not Flow/Else If - private void ShowPartialMatches() - { - var block = target as Block; - - var flowchart = (Flowchart)block.GetFlowchart(); - - //TODO this could be cached if input hasn't changed to avoid thrashing - var filteredAttributes = GetFilteredSupportedCommands(flowchart); - - var upperCommandText = commandTextFieldContents.ToUpper().Trim(); - - if (upperCommandText.Length == 0) - return; - - var tokens = upperCommandText.Split(SPLIT_INPUT_ON); - - //we want commands that have all the elements you have typed - filteredAttributes = filteredAttributes.Where((x) => - { - bool catAny = tokens.Any(x.Value.Category.ToUpper().Contains); - bool comAny = tokens.Any(x.Value.CommandName.ToUpper().Contains); - bool catAll = tokens.All(x.Value.Category.ToUpper().Contains); - bool comAll = tokens.All(x.Value.CommandName.ToUpper().Contains); - - //so if both category and command found something, then there are multiple tokens and they line up with category and command - if (catAny && comAny) - return true; - //or its a single token or a complex token that matches entirely in cat or com - else if (catAll || comAll) - return true; - //this setup avoids multiple bad suggestions due to a complex category name that gives many false matches on complex partials - - return false; - - }).ToList(); - - if (filteredAttributes == null || filteredAttributes.Count == 0) - return; - - //show results - GUILayout.Space(5); - - GUILayout.BeginHorizontal(); - - filteredCommandPreviewSelectedItem = Mathf.Clamp(filteredCommandPreviewSelectedItem, 0, filteredAttributes.Count - 1); - - var toShow = filteredAttributes.Select(x => x.Value.Category + "/" + x.Value.CommandName).ToArray(); - - //show the first x max that match our filters - if(toShow.Length > MAX_PREVIEW_GRID) - { - toShow = toShow.Take(MAX_PREVIEW_GRID).ToArray(); - toShow[MAX_PREVIEW_GRID - 1] = ELIPSIS; - } - - filteredCommandPreviewSelectedItem = GUILayout.SelectionGrid(filteredCommandPreviewSelectedItem, toShow, 1); - - if (toShow[filteredCommandPreviewSelectedItem] != ELIPSIS) - { - commandSelectedByTextInput = filteredAttributes[filteredCommandPreviewSelectedItem].Key; - } - else - { - commandSelectedByTextInput = null; - } - - GUILayout.EndHorizontal(); - - GUILayout.Space(5); - } + protected virtual void DrawEventHandlerGUI(Flowchart flowchart) { @@ -474,7 +341,7 @@ namespace Fungus.EditorUtils EditorGUILayout.PrefixLabel(new GUIContent("Execute On Event")); if (EditorGUILayout.DropdownButton(new GUIContent(currentHandlerName),FocusType.Passive)) { - EventSelectorPopupWindowContent.DoEventHandlerPopUp(lastEventPopupPos, currentHandlerName, block, eventHandlerTypes, (int)(EditorGUIUtility.currentViewWidth - lastEventPopupPos.x), 200); + EventSelectorPopupWindowContent.DoEventHandlerPopUp(lastEventPopupPos, currentHandlerName, block, (int)(EditorGUIUtility.currentViewWidth - lastEventPopupPos.x), 200); } EditorGUILayout.EndHorizontal(); @@ -566,17 +433,6 @@ namespace Fungus.EditorUtils return result; } - // Compare delegate for sorting the list of command attributes - protected static int CompareCommandAttributes(KeyValuePair x, KeyValuePair y) - { - int compare = (x.Value.Category.CompareTo(y.Value.Category)); - if (compare == 0) - { - compare = (x.Value.CommandName.CompareTo(y.Value.CommandName)); - } - return compare; - } - [MenuItem("Tools/Fungus/Utilities/Export Reference Docs")] protected static void ExportReferenceDocs() { @@ -588,6 +444,49 @@ namespace Fungus.EditorUtils FlowchartWindow.ShowNotification("Exported Reference Documentation"); } + public static List> GetFilteredCommandInfoAttribute(List menuTypes) + { + Dictionary> filteredAttributes = new Dictionary>(); + + foreach (System.Type type in menuTypes) + { + object[] attributes = type.GetCustomAttributes(false); + foreach (object obj in attributes) + { + CommandInfoAttribute infoAttr = obj as CommandInfoAttribute; + if (infoAttr != null) + { + string dictionaryName = string.Format("{0}/{1}", infoAttr.Category, infoAttr.CommandName); + + int existingItemPriority = -1; + if (filteredAttributes.ContainsKey(dictionaryName)) + { + existingItemPriority = filteredAttributes[dictionaryName].Value.Priority; + } + + if (infoAttr.Priority > existingItemPriority) + { + KeyValuePair keyValuePair = new KeyValuePair(type, infoAttr); + filteredAttributes[dictionaryName] = keyValuePair; + } + } + } + } + return filteredAttributes.Values.ToList>(); + } + + + // Compare delegate for sorting the list of command attributes + public static int CompareCommandAttributes(KeyValuePair x, KeyValuePair y) + { + int compare = (x.Value.Category.CompareTo(y.Value.Category)); + if (compare == 0) + { + compare = (x.Value.CommandName.CompareTo(y.Value.CommandName)); + } + return compare; + } + protected static void ExportCommandInfo(string path) { // Dump command info @@ -713,140 +612,7 @@ namespace Fungus.EditorUtils return markdown; } - protected virtual void ShowCommandMenu() - { - var block = target as Block; - - var flowchart = (Flowchart)block.GetFlowchart(); - - GenericMenu commandMenu = new GenericMenu(); - - // Build menu list - var filteredAttributes = GetFilteredSupportedCommands(flowchart); - - foreach (var keyPair in filteredAttributes) - { - AddCommandOperation commandOperation = new AddCommandOperation(); - - commandOperation.commandType = keyPair.Key; - - GUIContent menuItem; - if (keyPair.Value.Category == "") - { - menuItem = new GUIContent(keyPair.Value.CommandName); - } - else - { - menuItem = new GUIContent(keyPair.Value.Category + "/" + keyPair.Value.CommandName); - } - - commandMenu.AddItem(menuItem, false, AddCommandCallback, commandOperation); - } - - commandMenu.ShowAsContext(); - } - - protected static List> GetFilteredSupportedCommands(Flowchart flowchart) - { - List> filteredAttributes = GetFilteredCommandInfoAttribute(commandTypes); - - filteredAttributes.Sort(CompareCommandAttributes); - - filteredAttributes = filteredAttributes.Where(x => flowchart.IsCommandSupported(x.Value)).ToList(); - - return filteredAttributes; - } - - protected static List> GetFilteredCommandInfoAttribute(List menuTypes) - { - Dictionary> filteredAttributes = new Dictionary>(); - - foreach (System.Type type in menuTypes) - { - object[] attributes = type.GetCustomAttributes(false); - foreach (object obj in attributes) - { - CommandInfoAttribute infoAttr = obj as CommandInfoAttribute; - if (infoAttr != null) - { - string dictionaryName = string.Format("{0}/{1}", infoAttr.Category, infoAttr.CommandName); - - int existingItemPriority = -1; - if (filteredAttributes.ContainsKey(dictionaryName)) - { - existingItemPriority = filteredAttributes[dictionaryName].Value.Priority; - } - - if (infoAttr.Priority > existingItemPriority) - { - KeyValuePair keyValuePair = new KeyValuePair(type, infoAttr); - filteredAttributes[dictionaryName] = keyValuePair; - } - } - } - } - return filteredAttributes.Values.ToList>(); - } - - //Used by GenericMenu Delegate - protected void AddCommandCallback(object obj) - { - AddCommandOperation commandOperation = obj as AddCommandOperation; - if (commandOperation != null) - { - AddCommandCallback(commandOperation.commandType); - } - } - protected void AddCommandCallback(Type commandType) - { - var block = target as Block; - if (block == null) - { - return; - } - - var flowchart = (Flowchart)block.GetFlowchart(); - - // Use index of last selected command in list, or end of list if nothing selected. - int index = -1; - foreach (var command in flowchart.SelectedCommands) - { - if (command.CommandIndex + 1 > index) - { - index = command.CommandIndex + 1; - } - } - if (index == -1) - { - index = block.CommandList.Count; - } - - var newCommand = Undo.AddComponent(block.gameObject, commandType) as Command; - block.GetFlowchart().AddSelectedCommand(newCommand); - newCommand.ParentBlock = block; - newCommand.ItemId = flowchart.NextItemId(); - - // Let command know it has just been added to the block - newCommand.OnCommandAdded(block); - - Undo.RecordObject(block, "Set command type"); - if (index < block.CommandList.Count - 1) - { - block.CommandList.Insert(index, newCommand); - } - else - { - block.CommandList.Add(newCommand); - } - - // Because this is an async call, we need to force prefab instances to record changes - PrefabUtility.RecordPrefabInstancePropertyModifications(block); - - flowchart.ClearSelectedCommands(); - - commandTextFieldContents = string.Empty; - } public virtual void ShowContextMenu() { diff --git a/Assets/Fungus/Scripts/Editor/CommandSelectorPopupWindowContent.cs b/Assets/Fungus/Scripts/Editor/CommandSelectorPopupWindowContent.cs new file mode 100644 index 00000000..12cb839c --- /dev/null +++ b/Assets/Fungus/Scripts/Editor/CommandSelectorPopupWindowContent.cs @@ -0,0 +1,225 @@ +using UnityEditor; +using UnityEngine; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Fungus.EditorUtils +{ + /// + /// Searchable Popup Window for adding a command to a block + /// + /// Inspired by https://github.com/roboryantron/UnityEditorJunkie/blob/master/Assets/SearchableEnum/Code/Editor/SearchablePopup.cs + /// + public class CommandSelectorPopupWindowContent //: PopupWindowContent + { + private static readonly char[] SPLIT_INPUT_ON = new char[] { ' ', '/', '\\' }; + private static readonly int MAX_PREVIEW_GRID = 7; + private static readonly string ELIPSIS = "..."; + + + static List commandTypes; + + static void CacheEventHandlerTypes() + { + commandTypes = EditorExtensions.FindDerivedTypes(typeof(Command)).Where(x => !x.IsAbstract).ToList(); + } + + [UnityEditor.Callbacks.DidReloadScripts] + private static void OnScriptsReloaded() + { + CacheEventHandlerTypes(); + } + + static Block curBlock; + + static public void ShowCommandMenu(Block block) + { + curBlock = block; + + var flowchart = (Flowchart)block.GetFlowchart(); + + GenericMenu commandMenu = new GenericMenu(); + + // Build menu list + var filteredAttributes = GetFilteredSupportedCommands(flowchart); + + foreach (var keyPair in filteredAttributes) + { + AddCommandOperation commandOperation = new AddCommandOperation(); + + commandOperation.commandType = keyPair.Key; + + GUIContent menuItem; + if (keyPair.Value.Category == "") + { + menuItem = new GUIContent(keyPair.Value.CommandName); + } + else + { + menuItem = new GUIContent(keyPair.Value.Category + "/" + keyPair.Value.CommandName); + } + + commandMenu.AddItem(menuItem, false, AddCommandCallback, commandOperation); + } + + commandMenu.ShowAsContext(); + } + + protected static List> GetFilteredSupportedCommands(Flowchart flowchart) + { + List> filteredAttributes = BlockEditor.GetFilteredCommandInfoAttribute(commandTypes); + + filteredAttributes.Sort(BlockEditor.CompareCommandAttributes); + + filteredAttributes = filteredAttributes.Where(x => flowchart.IsCommandSupported(x.Value)).ToList(); + + return filteredAttributes; + } + + + + //Used by GenericMenu Delegate + static protected void AddCommandCallback(object obj) + { + AddCommandOperation commandOperation = obj as AddCommandOperation; + if (commandOperation != null) + { + AddCommandCallback(commandOperation.commandType); + } + } + + + static protected void AddCommandCallback(Type commandType) + { + var block = curBlock; + if (block == null) + { + return; + } + + var flowchart = (Flowchart)block.GetFlowchart(); + + // Use index of last selected command in list, or end of list if nothing selected. + int index = -1; + foreach (var command in flowchart.SelectedCommands) + { + if (command.CommandIndex + 1 > index) + { + index = command.CommandIndex + 1; + } + } + if (index == -1) + { + index = block.CommandList.Count; + } + + var newCommand = Undo.AddComponent(block.gameObject, commandType) as Command; + block.GetFlowchart().AddSelectedCommand(newCommand); + newCommand.ParentBlock = block; + newCommand.ItemId = flowchart.NextItemId(); + + // Let command know it has just been added to the block + newCommand.OnCommandAdded(block); + + Undo.RecordObject(block, "Set command type"); + if (index < block.CommandList.Count - 1) + { + block.CommandList.Insert(index, newCommand); + } + else + { + block.CommandList.Add(newCommand); + } + + // Because this is an async call, we need to force prefab instances to record changes + PrefabUtility.RecordPrefabInstancePropertyModifications(block); + + flowchart.ClearSelectedCommands(); + + //commandTextFieldContents = string.Empty; + } + + + protected class AddCommandOperation + { + public Type commandType; + } + + //Handles showing partial matches against the text input next to the AddCommand button + // Splits and matches and can use up down arrows and return/enter/numenter to confirm + // TODO add sorting of results so we get best match at the not just just a match + // e.g. "if" should show Flow/If at the top not Flow/Else If + //private void ShowPartialMatches() + //{ + // var block = curBlock; + + // var flowchart = (Flowchart)block.GetFlowchart(); + + // //TODO this could be cached if input hasn't changed to avoid thrashing + // var filteredAttributes = GetFilteredSupportedCommands(flowchart); + + // var upperCommandText = commandTextFieldContents.ToUpper().Trim(); + + // if (upperCommandText.Length == 0) + // return; + + // var tokens = upperCommandText.Split(SPLIT_INPUT_ON); + + // //we want commands that have all the elements you have typed + // filteredAttributes = filteredAttributes.Where((x) => + // { + // bool catAny = tokens.Any(x.Value.Category.ToUpper().Contains); + // bool comAny = tokens.Any(x.Value.CommandName.ToUpper().Contains); + // bool catAll = tokens.All(x.Value.Category.ToUpper().Contains); + // bool comAll = tokens.All(x.Value.CommandName.ToUpper().Contains); + + // //so if both category and command found something, then there are multiple tokens and they line up with category and command + // if (catAny && comAny) + // return true; + // //or its a single token or a complex token that matches entirely in cat or com + // else if (catAll || comAll) + // return true; + // //this setup avoids multiple bad suggestions due to a complex category name that gives many false matches on complex partials + + // return false; + + // }).ToList(); + + // if (filteredAttributes == null || filteredAttributes.Count == 0) + // return; + + // //show results + // GUILayout.Space(5); + + // GUILayout.BeginHorizontal(); + + // filteredCommandPreviewSelectedItem = Mathf.Clamp(filteredCommandPreviewSelectedItem, 0, filteredAttributes.Count - 1); + + // var toShow = filteredAttributes.Select(x => x.Value.Category + "/" + x.Value.CommandName).ToArray(); + + // //show the first x max that match our filters + // if (toShow.Length > MAX_PREVIEW_GRID) + // { + // toShow = toShow.Take(MAX_PREVIEW_GRID).ToArray(); + // toShow[MAX_PREVIEW_GRID - 1] = ELIPSIS; + // } + + // filteredCommandPreviewSelectedItem = GUILayout.SelectionGrid(filteredCommandPreviewSelectedItem, toShow, 1); + + // if (toShow[filteredCommandPreviewSelectedItem] != ELIPSIS) + // { + // commandSelectedByTextInput = filteredAttributes[filteredCommandPreviewSelectedItem].Key; + // } + // else + // { + // commandSelectedByTextInput = null; + // } + + // GUILayout.EndHorizontal(); + + // GUILayout.Space(5); + //} + } + +} \ No newline at end of file diff --git a/Assets/Fungus/Scripts/Editor/CommandSelectorPopupWindowContent.cs.meta b/Assets/Fungus/Scripts/Editor/CommandSelectorPopupWindowContent.cs.meta new file mode 100644 index 00000000..e09c36f2 --- /dev/null +++ b/Assets/Fungus/Scripts/Editor/CommandSelectorPopupWindowContent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0e2650550a8549143b9eaab2c2dad511 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fungus/Scripts/Editor/EventSelectorPopupWindowContent.cs b/Assets/Fungus/Scripts/Editor/EventSelectorPopupWindowContent.cs index db7b4345..68d722ce 100644 --- a/Assets/Fungus/Scripts/Editor/EventSelectorPopupWindowContent.cs +++ b/Assets/Fungus/Scripts/Editor/EventSelectorPopupWindowContent.cs @@ -1,14 +1,8 @@ using UnityEditor; -using UnityEditorInternal; using UnityEngine; using System; -using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Text.RegularExpressions; -using Rotorz.ReorderableList; -using System.IO; -using System.Reflection; namespace Fungus.EditorUtils { @@ -19,6 +13,19 @@ namespace Fungus.EditorUtils /// public class EventSelectorPopupWindowContent : PopupWindowContent { + static List eventHandlerTypes; + + static void CacheEventHandlerTypes() + { + eventHandlerTypes = EditorExtensions.FindDerivedTypes(typeof(EventHandler)).Where(x => !x.IsAbstract).ToList(); + } + + [UnityEditor.Callbacks.DidReloadScripts] + private static void OnScriptsReloaded() + { + CacheEventHandlerTypes(); + } + protected class SetEventHandlerOperation { public Block block; @@ -37,9 +44,7 @@ namespace Fungus.EditorUtils public string name, lowerName; } - private string currentHandlerName; private Block block; - private List eventHandlerTypes; private int hoverIndex; private readonly string SEARCH_CONTROL_NAME = "PopupSearchControlName"; private readonly float ROW_HEIGHT = EditorGUIUtility.singleLineHeight; @@ -53,11 +58,9 @@ namespace Fungus.EditorUtils static readonly char[] SEARCH_SPLITS = new char[]{ '/', ' ' }; - public EventSelectorPopupWindowContent(string currentHandlerName, Block block, List eventHandlerTypes, int width, int height) + public EventSelectorPopupWindowContent(string currentHandlerName, Block block, int width, int height) { - this.currentHandlerName = currentHandlerName; this.block = block; - this.eventHandlerTypes = eventHandlerTypes; this.size = new Vector2(width, height); int i = 0; @@ -77,6 +80,7 @@ namespace Fungus.EditorUtils } allItems.Sort((lhs, rhs) => lhs.name.CompareTo(rhs.name)); UpdateFilter(); + currentIndex = Mathf.Max(0, visibleItems.FindIndex(x=>x.name.Contains(currentHandlerName))); } public override void OnGUI(Rect rect) @@ -249,10 +253,10 @@ namespace Fungus.EditorUtils } } - static public void DoEventHandlerPopUp(Rect position, string currentHandlerName, Block block, List eventHandlerTypes, int width, int height) + static public void DoEventHandlerPopUp(Rect position, string currentHandlerName, Block block, int width, int height) { //new method - EventSelectorPopupWindowContent win = new EventSelectorPopupWindowContent(currentHandlerName, block, eventHandlerTypes, width, height); + EventSelectorPopupWindowContent win = new EventSelectorPopupWindowContent(currentHandlerName, block, width, height); PopupWindow.Show(position, win); //old method