Browse Source

Merge pull request #709 from stevehalliwell/CommandSearchablePopUp

Command searchable pop up
master
Chris Gregan 6 years ago committed by GitHub
parent
commit
c0d0ec71c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 262
      Assets/Fungus/Scripts/Editor/BasePopupWindowContent.cs
  2. 11
      Assets/Fungus/Scripts/Editor/BasePopupWindowContent.cs.meta
  3. 413
      Assets/Fungus/Scripts/Editor/BlockEditor.cs
  4. 10
      Assets/Fungus/Scripts/Editor/FungusEditorPreferences.cs
  5. 8
      Assets/Fungus/Scripts/Editor/PopupContent.meta
  6. 176
      Assets/Fungus/Scripts/Editor/PopupContent/CommandSelectorPopupWindowContent.cs
  7. 11
      Assets/Fungus/Scripts/Editor/PopupContent/CommandSelectorPopupWindowContent.cs.meta
  8. 156
      Assets/Fungus/Scripts/Editor/PopupContent/EventSelectorPopupWindowContent.cs
  9. 11
      Assets/Fungus/Scripts/Editor/PopupContent/EventSelectorPopupWindowContent.cs.meta
  10. 130
      Assets/Fungus/Scripts/Editor/PopupContent/VariableSelectPopupWindowContent.cs
  11. 11
      Assets/Fungus/Scripts/Editor/PopupContent/VariableSelectPopupWindowContent.cs.meta
  12. 49
      Assets/Fungus/Scripts/Editor/VariableListAdaptor.cs

262
Assets/Fungus/Scripts/Editor/BasePopupWindowContent.cs

@ -0,0 +1,262 @@
using UnityEditor;
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Fungus.EditorUtils
{
/// <summary>
/// Common base for PopupWindowContent that is a search filterable list a la AddComponent
///
/// Inspired by https://github.com/roboryantron/UnityEditorJunkie/blob/master/Assets/SearchableEnum/Code/Editor/SearchablePopup.cs
/// </summary>
public abstract class BasePopupWindowContent : PopupWindowContent
{
/// <summary>
/// Called when the user has confirmed an item from the menu.
/// </summary>
/// <param name="index">Index of into the original list of items to show given to the popupcontent</param>
abstract protected void SelectByOrigIndex(int index);
/// <summary>
/// Called during Base Ctor, must fill allItems list so the ctor can continue to fill
/// the visible items and current selected index.
/// </summary>
abstract protected void PrepareAllItems();
/// <summary>
/// Internal representation of 1 row of our popup list
/// </summary>
public class FilteredListItem
{
public FilteredListItem(int index, string str, string tip = "")
{
origIndex = index;
name = str;
lowerName = str.ToLowerInvariant();
content = new GUIContent(str, tip);
}
public int origIndex;
public string name, lowerName;
public GUIContent content;
}
protected int hoverIndex;
protected readonly string SEARCH_CONTROL_NAME = "PopupSearchControlName";
protected readonly float ROW_HEIGHT = EditorGUIUtility.singleLineHeight;
protected List<FilteredListItem> allItems = new List<FilteredListItem>(),
visibleItems = new List<FilteredListItem>();
protected string currentFilter = string.Empty;
protected Vector2 scroll;
protected int scrollToIndex;
protected float scrollOffset;
protected int currentIndex;
protected Vector2 size;
protected bool hasNoneOption = false;
static readonly char[] SEARCH_SPLITS = new char[]{ CATEGORY_CHAR, ' ' };
protected static readonly char CATEGORY_CHAR = '/';
public BasePopupWindowContent(string currentHandlerName, int width, int height, bool showNoneOption = false)
{
this.size = new Vector2(width, height);
hasNoneOption = showNoneOption;
PrepareAllItems();
allItems.Sort((lhs, rhs) =>
{
//order root level objects first
var islhsRoot = lhs.lowerName.IndexOf(CATEGORY_CHAR) != -1;
var isrhsRoot = rhs.lowerName.IndexOf(CATEGORY_CHAR) != -1;
if(islhsRoot == isrhsRoot)
return lhs.lowerName.CompareTo(rhs.lowerName);
return islhsRoot ? 1 : -1;
});
UpdateFilter();
currentIndex = Mathf.Max(0, visibleItems.FindIndex(x=>x.name.Contains(currentHandlerName)));
hoverIndex = currentIndex;
}
public override void OnGUI(Rect rect)
{
Rect searchRect = new Rect(0, 0, rect.width, EditorStyles.toolbar.fixedHeight);
Rect scrollRect = Rect.MinMaxRect(0, searchRect.yMax, rect.xMax, rect.yMax);
HandleKeyboard();
DrawSearch(searchRect);
DrawSelectionArea(scrollRect);
}
public override Vector2 GetWindowSize()
{
return size;
}
private void DrawSearch(Rect rect)
{
if (Event.current.type == EventType.Repaint)
EditorStyles.toolbar.Draw(rect, false, false, false, false);
Rect searchRect = new Rect(rect);
searchRect.xMin += 6;
searchRect.xMax -= 6;
searchRect.y += 2;
GUI.FocusControl(SEARCH_CONTROL_NAME);
GUI.SetNextControlName(SEARCH_CONTROL_NAME);
var prevFilter = currentFilter;
currentFilter = GUI.TextField(searchRect, currentFilter);
if (prevFilter != currentFilter)
{
UpdateFilter();
}
}
private void UpdateFilter()
{
var curlower = currentFilter.ToLowerInvariant();
var lowers = curlower.Split(SEARCH_SPLITS);
lowers = lowers.Where(x => x.Length > 0).ToArray();
if (lowers == null || lowers.Length == 0)
{
visibleItems.AddRange(allItems);
}
else
{
visibleItems = allItems.Where(x =>
{
//we want all tokens
foreach (var item in lowers)
{
if (!x.lowerName.Contains(item))
return false;
}
return true;
}).ToList();
}
hoverIndex = 0;
scroll = Vector2.zero;
if(hasNoneOption)
visibleItems.Insert(0, new FilteredListItem(-1, "None"));
}
private void DrawSelectionArea(Rect scrollRect)
{
Rect contentRect = new Rect(0, 0,
scrollRect.width - GUI.skin.verticalScrollbar.fixedWidth,
visibleItems.Count * ROW_HEIGHT);
scroll = GUI.BeginScrollView(scrollRect, scroll, contentRect);
Rect rowRect = new Rect(0, 0, scrollRect.width, ROW_HEIGHT);
for (int i = 0; i < visibleItems.Count; i++)
{
if (scrollToIndex == i &&
(Event.current.type == EventType.Repaint
|| Event.current.type == EventType.Layout))
{
Rect r = new Rect(rowRect);
r.y += scrollOffset;
GUI.ScrollTo(r);
scrollToIndex = -1;
scroll.x = 0;
}
if (rowRect.Contains(Event.current.mousePosition))
{
if (Event.current.type == EventType.MouseMove ||
Event.current.type == EventType.ScrollWheel)
{
//if new item force update so it's snappier
if (hoverIndex != 1)
{
this.editorWindow.Repaint();
}
hoverIndex = i;
}
if (Event.current.type == EventType.MouseDown)
{
//onSelectionMade(list.Entries[i].Index);
SelectByOrigIndex(visibleItems[i].origIndex);
EditorWindow.focusedWindow.Close();
}
}
DrawRow(rowRect, i);
rowRect.y = rowRect.yMax;
}
GUI.EndScrollView();
}
private static void DrawBox(Rect rect, Color tint)
{
Color c = GUI.color;
GUI.color = tint;
GUI.Box(rect, "");
GUI.color = c;
}
private void DrawRow(Rect rowRect, int i)
{
if (i == currentIndex)
DrawBox(rowRect, Color.cyan);
else if (i == hoverIndex)
DrawBox(rowRect, Color.white);
Rect labelRect = new Rect(rowRect);
//labelRect.xMin += ROW_INDENT;
GUI.Label(labelRect, visibleItems[i].content);
}
/// <summary>
/// Process keyboard input to navigate the choices or make a selection.
/// </summary>
private void HandleKeyboard()
{
if (Event.current.type == EventType.KeyDown)
{
if (Event.current.keyCode == KeyCode.DownArrow)
{
hoverIndex = Mathf.Min(visibleItems.Count - 1, hoverIndex + 1);
Event.current.Use();
scrollToIndex = hoverIndex;
scrollOffset = ROW_HEIGHT;
}
if (Event.current.keyCode == KeyCode.UpArrow)
{
hoverIndex = Mathf.Max(0, hoverIndex - 1);
Event.current.Use();
scrollToIndex = hoverIndex;
scrollOffset = -ROW_HEIGHT;
}
if (Event.current.keyCode == KeyCode.Return)
{
if (hoverIndex >= 0 && hoverIndex < visibleItems.Count)
{
SelectByOrigIndex(visibleItems[hoverIndex].origIndex);
EditorWindow.focusedWindow.Close();
}
}
if (Event.current.keyCode == KeyCode.Escape)
{
EditorWindow.focusedWindow.Close();
}
}
}
}
}

11
Assets/Fungus/Scripts/Editor/BasePopupWindowContent.cs.meta

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: be9eead8f19216444b4a87e94fe02ed3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

413
Assets/Fungus/Scripts/Editor/BlockEditor.cs

@ -17,22 +17,6 @@ namespace Fungus.EditorUtils
[CustomEditor(typeof(Block))]
public class BlockEditor : Editor
{
protected class SetEventHandlerOperation
{
public Block block;
public Type eventHandlerType;
}
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;
@ -41,27 +25,12 @@ namespace Fungus.EditorUtils
protected Texture2D duplicateIcon;
protected Texture2D deleteIcon;
protected string commandTextFieldContents = string.Empty;
protected int filteredCommandPreviewSelectedItem = 0;
protected Type commandSelectedByTextInput;
static List<System.Type> commandTypes;
static List<System.Type> eventHandlerTypes;
private CommandListAdaptor commandListAdaptor;
private SerializedProperty commandListProperty;
static void CacheEventHandlerTypes()
{
eventHandlerTypes = EditorExtensions.FindDerivedTypes(typeof(EventHandler)).ToList();
commandTypes = EditorExtensions.FindDerivedTypes(typeof(Command)).ToList();
}
private Rect lastEventPopupPos, lastCMDpopupPos;
[UnityEditor.Callbacks.DidReloadScripts]
private static void OnScriptsReloaded()
{
CacheEventHandlerTypes();
}
protected virtual void OnEnable()
{
@ -86,7 +55,6 @@ namespace Fungus.EditorUtils
commandListAdaptor = new CommandListAdaptor(target as Block, commandListProperty);
CacheEventHandlerTypes();
}
public virtual void DrawBlockName(Flowchart flowchart)
@ -291,32 +259,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))
{
@ -345,13 +287,21 @@ 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));
//using false to prevent forcing a longer row than will fit on smallest inspector
var pos = EditorGUILayout.GetControlRect(false, 0, EditorStyles.objectField);
if (pos.x != 0)
{
lastCMDpopupPos = pos;
lastCMDpopupPos.x += EditorGUIUtility.labelWidth;
lastCMDpopupPos.y += EditorGUIUtility.singleLineHeight * 2;
}
// Add Button
if (GUILayout.Button(addIcon))
{
ShowCommandMenu();
CommandSelectorPopupWindowContent.ShowCommandMenu(lastCMDpopupPos, "", target as Block,
(int)(EditorGUIUtility.currentViewWidth),
(int)(EditorWindow.focusedWindow.position.height - lastCMDpopupPos.y));
}
// Duplicate Button
@ -369,85 +319,9 @@ 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)
{
@ -470,49 +344,18 @@ namespace Fungus.EditorUtils
}
}
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel(new GUIContent("Execute On Event"));
if (GUILayout.Button(new GUIContent(currentHandlerName), EditorStyles.popup))
{
SetEventHandlerOperation noneOperation = new SetEventHandlerOperation();
noneOperation.block = block;
noneOperation.eventHandlerType = null;
GenericMenu eventHandlerMenu = new GenericMenu();
eventHandlerMenu.AddItem(new GUIContent("None"), false, OnSelectEventHandler, noneOperation);
// Add event handlers with no category first
foreach (System.Type type in eventHandlerTypes)
{
EventHandlerInfoAttribute info = EventHandlerEditor.GetEventHandlerInfo(type);
if (info != null &&
info.Category.Length == 0)
var pos = EditorGUILayout.GetControlRect(true, 0, EditorStyles.objectField);
if (pos.x != 0)
{
SetEventHandlerOperation operation = new SetEventHandlerOperation();
operation.block = block;
operation.eventHandlerType = type;
eventHandlerMenu.AddItem(new GUIContent(info.EventHandlerName), false, OnSelectEventHandler, operation);
}
lastEventPopupPos = pos;
lastEventPopupPos.x += EditorGUIUtility.labelWidth;
lastEventPopupPos.y += EditorGUIUtility.singleLineHeight;
}
// Add event handlers with a category afterwards
foreach (System.Type type in eventHandlerTypes)
{
EventHandlerInfoAttribute info = EventHandlerEditor.GetEventHandlerInfo(type);
if (info != null &&
info.Category.Length > 0)
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel(new GUIContent("Execute On Event"));
if (EditorGUILayout.DropdownButton(new GUIContent(currentHandlerName), FocusType.Passive))
{
SetEventHandlerOperation operation = new SetEventHandlerOperation();
operation.block = block;
operation.eventHandlerType = type;
string typeName = info.Category + "/" + info.EventHandlerName;
eventHandlerMenu.AddItem(new GUIContent(typeName), false, OnSelectEventHandler, operation);
}
}
eventHandlerMenu.ShowAsContext();
EventSelectorPopupWindowContent.DoEventHandlerPopUp(lastEventPopupPos, currentHandlerName, block, (int)(EditorGUIUtility.currentViewWidth - lastEventPopupPos.x), 200);
}
EditorGUILayout.EndHorizontal();
@ -527,33 +370,6 @@ namespace Fungus.EditorUtils
}
}
protected void OnSelectEventHandler(object obj)
{
SetEventHandlerOperation operation = obj as SetEventHandlerOperation;
Block block = operation.block;
System.Type selectedType = operation.eventHandlerType;
if (block == null)
{
return;
}
Undo.RecordObject(block, "Set Event Handler");
if (block._EventHandler != null)
{
Undo.DestroyObjectImmediate(block._EventHandler);
}
if (selectedType != null)
{
EventHandler newHandler = Undo.AddComponent(block.gameObject, selectedType) as EventHandler;
newHandler.ParentBlock = block;
block._EventHandler = newHandler;
}
// Because this is an async call, we need to force prefab instances to record changes
PrefabUtility.RecordPrefabInstancePropertyModifications(block);
}
public static void BlockField(SerializedProperty property, GUIContent label, GUIContent nullLabel, Flowchart flowchart)
{
@ -631,17 +447,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()
{
@ -653,6 +458,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
@ -778,140 +626,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()
{

10
Assets/Fungus/Scripts/Editor/FungusEditorPreferences.cs

@ -16,8 +16,11 @@ namespace Fungus
{
// Have we loaded the prefs yet
private static bool prefsLoaded = false;
const string HIDE_MUSH_KEY = "hideMushroomInHierarchy";
const string USE_EXP_MENUS = "useExperimentalMenus";
public static bool hideMushroomInHierarchy;
public static bool useExperimentalMenus;
static FungusEditorPreferences()
{
@ -36,17 +39,20 @@ namespace Fungus
// Preferences GUI
hideMushroomInHierarchy = EditorGUILayout.Toggle("Hide Mushroom Flowchart Icon", hideMushroomInHierarchy);
useExperimentalMenus = EditorGUILayout.Toggle(new GUIContent("Experimental Searchable Menus", "Experimental menus replace the Event, Add Variable and Add Command menus with a searchable menu more like the Unity AddComponent menu."), useExperimentalMenus);
// Save the preferences
if (GUI.changed)
{
EditorPrefs.SetBool("hideMushroomInHierarchy", hideMushroomInHierarchy);
EditorPrefs.SetBool(HIDE_MUSH_KEY, hideMushroomInHierarchy);
EditorPrefs.SetBool(USE_EXP_MENUS, useExperimentalMenus);
}
}
public static void LoadOnScriptLoad()
{
hideMushroomInHierarchy = EditorPrefs.GetBool("hideMushroomInHierarchy", false);
hideMushroomInHierarchy = EditorPrefs.GetBool(HIDE_MUSH_KEY, false);
useExperimentalMenus = EditorPrefs.GetBool(USE_EXP_MENUS, false);
prefsLoaded = true;
}
}

8
Assets/Fungus/Scripts/Editor/PopupContent.meta

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b99cf5f1ea08b914c94ee372bc4d76a7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

176
Assets/Fungus/Scripts/Editor/PopupContent/CommandSelectorPopupWindowContent.cs

@ -0,0 +1,176 @@
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
/// </summary>
public class CommandSelectorPopupWindowContent : BasePopupWindowContent
{
static List<System.Type> commandTypes;
static void CacheCommandTypes()
{
commandTypes = EditorExtensions.FindDerivedTypes(typeof(Command)).Where(x => !x.IsAbstract).ToList();
}
[UnityEditor.Callbacks.DidReloadScripts]
private static void OnScriptsReloaded()
{
CacheCommandTypes();
}
static Block curBlock;
static protected List<KeyValuePair<System.Type, CommandInfoAttribute>> filteredAttributes;
public CommandSelectorPopupWindowContent(string currentHandlerName, int width, int height)
: base(currentHandlerName, width, height)
{
}
protected override void SelectByOrigIndex(int index)
{
var commandType = (index >= 0 && index < commandTypes.Count) ? commandTypes[index] : null;
AddCommandCallback(commandType);
}
protected override void PrepareAllItems()
{
if (commandTypes == null || commandTypes.Count == 0)
{
CacheCommandTypes();
}
filteredAttributes = GetFilteredSupportedCommands(curBlock.GetFlowchart());
foreach (var item in filteredAttributes)
{
//force lookup to orig index here to account for commmand lists being filtered by users
var newFilteredItem = new FilteredListItem(commandTypes.IndexOf(item.Key), (item.Value.Category.Length > 0 ? item.Value.Category + CATEGORY_CHAR : "") + item.Value.CommandName, item.Value.HelpText);
allItems.Add(newFilteredItem);
}
}
static public void ShowCommandMenu(Rect position, string currentHandlerName, Block block, int width, int height)
{
curBlock = block;
if (FungusEditorPreferences.useExperimentalMenus)
{
var win = new CommandSelectorPopupWindowContent(currentHandlerName,
width, (int)(height - EditorGUIUtility.singleLineHeight * 3));
PopupWindow.Show(position, win);
}
else
{
//need to ensure we have filtered data
filteredAttributes = GetFilteredSupportedCommands(curBlock.GetFlowchart());
}
//old method
DoOlderMenu();
}
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;
}
static protected void DoOlderMenu()
{
GenericMenu commandMenu = new GenericMenu();
// Build menu list
foreach (var keyPair in filteredAttributes)
{
GUIContent menuItem;
if (keyPair.Value.Category == "")
{
menuItem = new GUIContent(keyPair.Value.CommandName);
}
else
{
menuItem = new GUIContent(keyPair.Value.Category + CATEGORY_CHAR + keyPair.Value.CommandName);
}
commandMenu.AddItem(menuItem, false, AddCommandCallback, keyPair.Key);
}
commandMenu.ShowAsContext();
}
//Used by GenericMenu Delegate
static protected void AddCommandCallback(object obj)
{
Type command = obj as Type;
if (command != null)
{
AddCommandCallback(command);
}
}
static protected void AddCommandCallback(Type commandType)
{
var block = curBlock;
if (block == null || commandType == 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();
}
}
}

11
Assets/Fungus/Scripts/Editor/PopupContent/CommandSelectorPopupWindowContent.cs.meta

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0e2650550a8549143b9eaab2c2dad511
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

156
Assets/Fungus/Scripts/Editor/PopupContent/EventSelectorPopupWindowContent.cs

@ -0,0 +1,156 @@
using UnityEditor;
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Fungus.EditorUtils
{
/// <summary>
/// Searchable Popup Window for selecting Event type, used by block editor
/// </summary>
public class EventSelectorPopupWindowContent : BasePopupWindowContent
{
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;
public Type eventHandlerType;
}
protected Block block;
public EventSelectorPopupWindowContent(string currentHandlerName, Block block, int width, int height)
:base(currentHandlerName, width, height, true)
{
this.block = block;
}
protected override void PrepareAllItems()
{
if (eventHandlerTypes == null || eventHandlerTypes.Count == 0)
{
CacheEventHandlerTypes();
}
int i = 0;
foreach (System.Type type in eventHandlerTypes)
{
EventHandlerInfoAttribute info = EventHandlerEditor.GetEventHandlerInfo(type);
if (info != null)
{
allItems.Add(new FilteredListItem(i, (info.Category.Length > 0 ? info.Category + CATEGORY_CHAR : "") + info.EventHandlerName, info.HelpText));
}
else
{
allItems.Add(new FilteredListItem(i, type.Name, info.HelpText));
}
i++;
}
}
override protected void SelectByOrigIndex(int index)
{
SetEventHandlerOperation operation = new SetEventHandlerOperation();
operation.block = block;
operation.eventHandlerType = (index >= 0 && index < eventHandlerTypes.Count) ? eventHandlerTypes[index] : null;
OnSelectEventHandler(operation);
}
static public void DoEventHandlerPopUp(Rect position, string currentHandlerName, Block block, int width, int height)
{
if (FungusEditorPreferences.useExperimentalMenus)
{
//new method
EventSelectorPopupWindowContent win = new EventSelectorPopupWindowContent(currentHandlerName, block, width, height);
PopupWindow.Show(position, win);
}
//old method
DoOlderMenu(block);
}
static protected void DoOlderMenu(Block block)
{
SetEventHandlerOperation noneOperation = new SetEventHandlerOperation();
noneOperation.block = block;
noneOperation.eventHandlerType = null;
GenericMenu eventHandlerMenu = new GenericMenu();
eventHandlerMenu.AddItem(new GUIContent("None"), false, OnSelectEventHandler, noneOperation);
// Add event handlers with no category first
foreach (System.Type type in eventHandlerTypes)
{
EventHandlerInfoAttribute info = EventHandlerEditor.GetEventHandlerInfo(type);
if (info != null &&
info.Category.Length == 0)
{
SetEventHandlerOperation operation = new SetEventHandlerOperation();
operation.block = block;
operation.eventHandlerType = type;
eventHandlerMenu.AddItem(new GUIContent(info.EventHandlerName), false, OnSelectEventHandler, operation);
}
}
// Add event handlers with a category afterwards
foreach (System.Type type in eventHandlerTypes)
{
EventHandlerInfoAttribute info = EventHandlerEditor.GetEventHandlerInfo(type);
if (info != null &&
info.Category.Length > 0)
{
SetEventHandlerOperation operation = new SetEventHandlerOperation();
operation.block = block;
operation.eventHandlerType = type;
string typeName = info.Category + "/" + info.EventHandlerName;
eventHandlerMenu.AddItem(new GUIContent(typeName), false, OnSelectEventHandler, operation);
}
}
eventHandlerMenu.ShowAsContext();
}
static protected void OnSelectEventHandler(object obj)
{
SetEventHandlerOperation operation = obj as SetEventHandlerOperation;
Block block = operation.block;
System.Type selectedType = operation.eventHandlerType;
if (block == null)
{
return;
}
Undo.RecordObject(block, "Set Event Handler");
if (block._EventHandler != null)
{
Undo.DestroyObjectImmediate(block._EventHandler);
}
if (selectedType != null)
{
EventHandler newHandler = Undo.AddComponent(block.gameObject, selectedType) as EventHandler;
newHandler.ParentBlock = block;
block._EventHandler = newHandler;
}
// Because this is an async call, we need to force prefab instances to record changes
PrefabUtility.RecordPrefabInstancePropertyModifications(block);
}
}
}

11
Assets/Fungus/Scripts/Editor/PopupContent/EventSelectorPopupWindowContent.cs.meta

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f9ca5de337e96bf4c8a414c5c374d629
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

130
Assets/Fungus/Scripts/Editor/PopupContent/VariableSelectPopupWindowContent.cs

@ -0,0 +1,130 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Linq;
namespace Fungus.EditorUtils
{
/// <summary>
/// Show the variable selection window as a searchable popup
/// </summary>
public class VariableSelectPopupWindowContent : BasePopupWindowContent
{
static readonly int POPUP_WIDTH = 200, POPUP_HEIGHT = 200;
static List<System.Type> types;
static void CacheVariableTypes()
{
var derivedType = typeof(Variable);
types = EditorExtensions.FindDerivedTypes(derivedType)
.Where(x => !x.IsAbstract && derivedType.IsAssignableFrom(x))
.ToList();
}
[UnityEditor.Callbacks.DidReloadScripts]
private static void OnScriptsReloaded()
{
CacheVariableTypes();
}
protected override void PrepareAllItems()
{
if(types == null || types.Count == 0)
{
CacheVariableTypes();
}
int i = 0;
foreach (var item in types)
{
VariableInfoAttribute variableInfo = VariableEditor.GetVariableInfo(item);
if (variableInfo != null)
{
allItems.Add(new FilteredListItem(i, (variableInfo.Category.Length > 0 ? variableInfo.Category + CATEGORY_CHAR : "") + variableInfo.VariableType));
}
i++;
}
}
protected override void SelectByOrigIndex(int index)
{
AddVariable(types[index]);
}
static public void DoAddVariable(Rect position, string currentHandlerName, Flowchart flowchart)
{
curFlowchart = flowchart;
if (FungusEditorPreferences.useExperimentalMenus)
{
//new method
VariableSelectPopupWindowContent win = new VariableSelectPopupWindowContent(currentHandlerName, POPUP_WIDTH, POPUP_HEIGHT);
PopupWindow.Show(position, win);
}
//old method
DoOlderMenu(flowchart);
}
static protected void DoOlderMenu(Flowchart flowchart)
{
GenericMenu menu = new GenericMenu();
// Add variable types without a category
foreach (var type in types)
{
VariableInfoAttribute variableInfo = VariableEditor.GetVariableInfo(type);
if (variableInfo == null ||
variableInfo.Category != "")
{
continue;
}
GUIContent typeName = new GUIContent(variableInfo.VariableType);
menu.AddItem(typeName, false, AddVariable, type);
}
// Add types with a category
foreach (var type in types)
{
VariableInfoAttribute variableInfo = VariableEditor.GetVariableInfo(type);
if (variableInfo == null ||
variableInfo.Category == "")
{
continue;
}
GUIContent typeName = new GUIContent(variableInfo.Category + CATEGORY_CHAR + variableInfo.VariableType);
menu.AddItem(typeName, false, AddVariable, type);
}
menu.ShowAsContext();
}
private static Flowchart curFlowchart;
public VariableSelectPopupWindowContent(string currentHandlerName, int width, int height)
: base(currentHandlerName, width, height)
{
}
protected static void AddVariable(object obj)
{
System.Type t = obj as System.Type;
if (t == null)
{
return;
}
Undo.RecordObject(curFlowchart, "Add Variable");
Variable newVariable = curFlowchart.gameObject.AddComponent(t) as Variable;
newVariable.Key = curFlowchart.GetUniqueVariableKey("");
curFlowchart.Variables.Add(newVariable);
// Because this is an async call, we need to force prefab instances to record changes
PrefabUtility.RecordPrefabInstancePropertyModifications(curFlowchart);
}
}
}

11
Assets/Fungus/Scripts/Editor/PopupContent/VariableSelectPopupWindowContent.cs.meta

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 933b6fc31e5eac04ea3b2a1fa9a3b418
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

49
Assets/Fungus/Scripts/Editor/VariableListAdaptor.cs

@ -58,7 +58,8 @@ namespace Fungus.EditorUtils
list = new ReorderableList(arrayProperty.serializedObject, arrayProperty, true, false, true, true);
list.drawElementCallback = DrawItem;
list.onRemoveCallback = RemoveItem;
list.onAddCallback = AddButton;
//list.onAddCallback = AddButton;
list.onAddDropdownCallback = AddDropDown;
list.onRemoveCallback = RemoveItem;
list.elementHeightCallback = GetElementHeight;
}
@ -76,50 +77,10 @@ namespace Fungus.EditorUtils
Undo.DestroyObjectImmediate(variable);
}
private void AddButton(ReorderableList list)
private void AddDropDown(Rect buttonRect, ReorderableList list)
{
GenericMenu menu = new GenericMenu();
List<System.Type> types = FlowchartEditor.FindAllDerivedTypes<Variable>();
// Add variable types without a category
foreach (var type in types)
{
VariableInfoAttribute variableInfo = VariableEditor.GetVariableInfo(type);
if (variableInfo == null ||
variableInfo.Category != "")
{
continue;
}
AddVariableInfo addVariableInfo = new AddVariableInfo();
addVariableInfo.flowchart = TargetFlowchart;
addVariableInfo.variableType = type;
GUIContent typeName = new GUIContent(variableInfo.VariableType);
menu.AddItem(typeName, false, AddVariable, addVariableInfo);
}
// Add types with a category
foreach (var type in types)
{
VariableInfoAttribute variableInfo = VariableEditor.GetVariableInfo(type);
if (variableInfo == null ||
variableInfo.Category == "")
{
continue;
}
AddVariableInfo info = new AddVariableInfo();
info.flowchart = TargetFlowchart;
info.variableType = type;
GUIContent typeName = new GUIContent(variableInfo.Category + "/" + variableInfo.VariableType);
menu.AddItem(typeName, false, AddVariable, info);
}
menu.ShowAsContext();
Event.current.Use();
VariableSelectPopupWindowContent.DoAddVariable(buttonRect, "", TargetFlowchart);
}
protected virtual void AddVariable(object obj)

Loading…
Cancel
Save