Browse Source

EventSelectorPopup chooses correct starting index based on given current event name

WIP Move CommandSelector to its own PopupWindowContext
master
desktop-maesty/steve 6 years ago
parent
commit
785d6ab744
  1. 330
      Assets/Fungus/Scripts/Editor/BlockEditor.cs
  2. 225
      Assets/Fungus/Scripts/Editor/CommandSelectorPopupWindowContent.cs
  3. 11
      Assets/Fungus/Scripts/Editor/CommandSelectorPopupWindowContent.cs.meta
  4. 30
      Assets/Fungus/Scripts/Editor/EventSelectorPopupWindowContent.cs

330
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<Action> actionList = new List<Action>();
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<System.Type> commandTypes;
static List<System.Type> 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<System.Type, CommandInfoAttribute> x, KeyValuePair<System.Type, CommandInfoAttribute> 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<KeyValuePair<System.Type, CommandInfoAttribute>> GetFilteredCommandInfoAttribute(List<System.Type> menuTypes)
{
Dictionary<string, KeyValuePair<System.Type, CommandInfoAttribute>> filteredAttributes = new Dictionary<string, KeyValuePair<System.Type, CommandInfoAttribute>>();
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<System.Type, CommandInfoAttribute> keyValuePair = new KeyValuePair<System.Type, CommandInfoAttribute>(type, infoAttr);
filteredAttributes[dictionaryName] = keyValuePair;
}
}
}
}
return filteredAttributes.Values.ToList<KeyValuePair<System.Type, CommandInfoAttribute>>();
}
// Compare delegate for sorting the list of command attributes
public static int CompareCommandAttributes(KeyValuePair<System.Type, CommandInfoAttribute> x, KeyValuePair<System.Type, CommandInfoAttribute> 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<KeyValuePair<System.Type, CommandInfoAttribute>> GetFilteredSupportedCommands(Flowchart flowchart)
{
List<KeyValuePair<System.Type, CommandInfoAttribute>> filteredAttributes = GetFilteredCommandInfoAttribute(commandTypes);
filteredAttributes.Sort(CompareCommandAttributes);
filteredAttributes = filteredAttributes.Where(x => flowchart.IsCommandSupported(x.Value)).ToList();
return filteredAttributes;
}
protected static List<KeyValuePair<System.Type, CommandInfoAttribute>> GetFilteredCommandInfoAttribute(List<System.Type> menuTypes)
{
Dictionary<string, KeyValuePair<System.Type, CommandInfoAttribute>> filteredAttributes = new Dictionary<string, KeyValuePair<System.Type, CommandInfoAttribute>>();
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<System.Type, CommandInfoAttribute> keyValuePair = new KeyValuePair<System.Type, CommandInfoAttribute>(type, infoAttr);
filteredAttributes[dictionaryName] = keyValuePair;
}
}
}
}
return filteredAttributes.Values.ToList<KeyValuePair<System.Type, CommandInfoAttribute>>();
}
//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()
{

225
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
{
/// <summary>
/// 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
/// </summary>
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<System.Type> 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<KeyValuePair<System.Type, CommandInfoAttribute>> GetFilteredSupportedCommands(Flowchart flowchart)
{
List<KeyValuePair<System.Type, CommandInfoAttribute>> 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);
//}
}
}

11
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:

30
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
/// </summary>
public class EventSelectorPopupWindowContent : PopupWindowContent
{
static List<System.Type> 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<Type> 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<Type> 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<Type> 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

Loading…
Cancel
Save