|
|
|
// This code is part of the Fungus library (http://fungusgames.com) maintained by Chris Gregan (http://twitter.com/gofungus).
|
|
|
|
// It is released for free under the MIT open source license (https://github.com/snozbot/fungus/blob/master/LICENSE)
|
|
|
|
|
|
|
|
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
|
|
|
|
{
|
|
|
|
[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;
|
|
|
|
protected Texture2D downIcon;
|
|
|
|
protected Texture2D addIcon;
|
|
|
|
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;
|
|
|
|
|
|
|
|
static void CacheEventHandlerTypes()
|
|
|
|
{
|
|
|
|
eventHandlerTypes = EditorExtensions.FindDerivedTypes(typeof(EventHandler)).ToList();
|
|
|
|
commandTypes = EditorExtensions.FindDerivedTypes(typeof(Command)).ToList();
|
|
|
|
}
|
|
|
|
|
|
|
|
[UnityEditor.Callbacks.DidReloadScripts]
|
|
|
|
private static void OnScriptsReloaded()
|
|
|
|
{
|
|
|
|
CacheEventHandlerTypes();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected virtual void OnEnable()
|
|
|
|
{
|
|
|
|
upIcon = FungusEditorResources.Up;
|
|
|
|
downIcon = FungusEditorResources.Down;
|
|
|
|
addIcon = FungusEditorResources.Add;
|
|
|
|
duplicateIcon = FungusEditorResources.Duplicate;
|
|
|
|
deleteIcon = FungusEditorResources.Delete;
|
|
|
|
|
|
|
|
CacheEventHandlerTypes();
|
|
|
|
}
|
|
|
|
|
|
|
|
public virtual void DrawBlockName(Flowchart flowchart)
|
|
|
|
{
|
|
|
|
serializedObject.Update();
|
|
|
|
|
|
|
|
SerializedProperty blockNameProperty = serializedObject.FindProperty("blockName");
|
|
|
|
Rect blockLabelRect = new Rect(45, 5, 120, 16);
|
|
|
|
EditorGUI.LabelField(blockLabelRect, new GUIContent("Block Name"));
|
|
|
|
Rect blockNameRect = new Rect(45, 21, 180, 16);
|
|
|
|
EditorGUI.PropertyField(blockNameRect, blockNameProperty, new GUIContent(""));
|
|
|
|
|
|
|
|
// Ensure block name is unique for this Flowchart
|
|
|
|
var block = target as Block;
|
|
|
|
string uniqueName = flowchart.GetUniqueBlockKey(blockNameProperty.stringValue, block);
|
|
|
|
if (uniqueName != block.BlockName)
|
|
|
|
{
|
|
|
|
blockNameProperty.stringValue = uniqueName;
|
|
|
|
}
|
|
|
|
|
|
|
|
serializedObject.ApplyModifiedProperties();
|
|
|
|
}
|
|
|
|
|
|
|
|
public virtual void DrawBlockGUI(Flowchart flowchart)
|
|
|
|
{
|
|
|
|
serializedObject.Update();
|
|
|
|
|
|
|
|
// Execute any queued cut, copy, paste, etc. operations from the prevous GUI update
|
|
|
|
// We need to defer applying these operations until the following update because
|
|
|
|
// the ReorderableList control emits GUI errors if you clear the list in the same frame
|
|
|
|
// as drawing the control (e.g. select all and then delete)
|
|
|
|
if (Event.current.type == EventType.Layout)
|
|
|
|
{
|
|
|
|
foreach (Action action in actionList)
|
|
|
|
{
|
|
|
|
if (action != null)
|
|
|
|
{
|
|
|
|
action();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
actionList.Clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
var block = target as Block;
|
|
|
|
|
|
|
|
SerializedProperty commandListProperty = serializedObject.FindProperty("commandList");
|
|
|
|
|
|
|
|
if (block == flowchart.SelectedBlock)
|
|
|
|
{
|
|
|
|
// Custom tinting
|
|
|
|
SerializedProperty useCustomTintProp = serializedObject.FindProperty("useCustomTint");
|
|
|
|
SerializedProperty tintProp = serializedObject.FindProperty("tint");
|
|
|
|
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
|
|
|
|
|
|
useCustomTintProp.boolValue = GUILayout.Toggle(useCustomTintProp.boolValue, " Custom Tint");
|
|
|
|
if (useCustomTintProp.boolValue)
|
|
|
|
{
|
|
|
|
EditorGUILayout.PropertyField(tintProp, GUIContent.none);
|
|
|
|
}
|
|
|
|
|
|
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
|
|
|
|
SerializedProperty descriptionProp = serializedObject.FindProperty("description");
|
|
|
|
EditorGUILayout.PropertyField(descriptionProp);
|
|
|
|
|
|
|
|
DrawEventHandlerGUI(flowchart);
|
|
|
|
|
|
|
|
block.UpdateIndentLevels();
|
|
|
|
|
|
|
|
// Make sure each command has a reference to its parent block
|
|
|
|
foreach (var command in block.CommandList)
|
|
|
|
{
|
|
|
|
if (command == null) // Will be deleted from the list later on
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
command.ParentBlock = block;
|
|
|
|
}
|
|
|
|
|
|
|
|
ReorderableListGUI.Title("Commands");
|
|
|
|
CommandListAdaptor adaptor = new CommandListAdaptor(commandListProperty, 0);
|
|
|
|
adaptor.nodeRect = block._NodeRect;
|
|
|
|
|
|
|
|
ReorderableListFlags flags = ReorderableListFlags.HideAddButton | ReorderableListFlags.HideRemoveButtons | ReorderableListFlags.DisableContextMenu;
|
|
|
|
|
|
|
|
if (block.CommandList.Count == 0)
|
|
|
|
{
|
|
|
|
EditorGUILayout.HelpBox("Press the + button below to add a command to the list.", MessageType.Info);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ReorderableListControl.DrawControlFromState(adaptor, null, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
// EventType.contextClick doesn't register since we moved the Block Editor to be inside
|
|
|
|
// a GUI Area, no idea why. As a workaround we just check for right click instead.
|
|
|
|
if (Event.current.type == EventType.MouseUp &&
|
|
|
|
Event.current.button == 1)
|
|
|
|
{
|
|
|
|
ShowContextMenu();
|
|
|
|
Event.current.Use();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (GUIUtility.keyboardControl == 0) //Only call keyboard shortcuts when not typing in a text field
|
|
|
|
{
|
|
|
|
Event e = Event.current;
|
|
|
|
|
|
|
|
// Copy keyboard shortcut
|
|
|
|
if (e.type == EventType.ValidateCommand && e.commandName == "Copy")
|
|
|
|
{
|
|
|
|
if (flowchart.SelectedCommands.Count > 0)
|
|
|
|
{
|
|
|
|
e.Use();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (e.type == EventType.ExecuteCommand && e.commandName == "Copy")
|
|
|
|
{
|
|
|
|
actionList.Add(Copy);
|
|
|
|
e.Use();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cut keyboard shortcut
|
|
|
|
if (e.type == EventType.ValidateCommand && e.commandName == "Cut")
|
|
|
|
{
|
|
|
|
if (flowchart.SelectedCommands.Count > 0)
|
|
|
|
{
|
|
|
|
e.Use();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (e.type == EventType.ExecuteCommand && e.commandName == "Cut")
|
|
|
|
{
|
|
|
|
actionList.Add(Cut);
|
|
|
|
e.Use();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Paste keyboard shortcut
|
|
|
|
if (e.type == EventType.ValidateCommand && e.commandName == "Paste")
|
|
|
|
{
|
|
|
|
CommandCopyBuffer commandCopyBuffer = CommandCopyBuffer.GetInstance();
|
|
|
|
if (commandCopyBuffer.HasCommands())
|
|
|
|
{
|
|
|
|
e.Use();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (e.type == EventType.ExecuteCommand && e.commandName == "Paste")
|
|
|
|
{
|
|
|
|
actionList.Add(Paste);
|
|
|
|
e.Use();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Duplicate keyboard shortcut
|
|
|
|
if (e.type == EventType.ValidateCommand && e.commandName == "Duplicate")
|
|
|
|
{
|
|
|
|
if (flowchart.SelectedCommands.Count > 0)
|
|
|
|
{
|
|
|
|
e.Use();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (e.type == EventType.ExecuteCommand && e.commandName == "Duplicate")
|
|
|
|
{
|
|
|
|
actionList.Add(Copy);
|
|
|
|
actionList.Add(Paste);
|
|
|
|
e.Use();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete keyboard shortcut
|
|
|
|
if (e.type == EventType.ValidateCommand && e.commandName == "Delete")
|
|
|
|
{
|
|
|
|
if (flowchart.SelectedCommands.Count > 0)
|
|
|
|
{
|
|
|
|
e.Use();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (e.type == EventType.ExecuteCommand && e.commandName == "Delete")
|
|
|
|
{
|
|
|
|
actionList.Add(Delete);
|
|
|
|
e.Use();
|
|
|
|
}
|
|
|
|
|
|
|
|
// SelectAll keyboard shortcut
|
|
|
|
if (e.type == EventType.ValidateCommand && e.commandName == "SelectAll")
|
|
|
|
{
|
|
|
|
e.Use();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (e.type == EventType.ExecuteCommand && e.commandName == "SelectAll")
|
|
|
|
{
|
|
|
|
actionList.Add(SelectAll);
|
|
|
|
e.Use();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove any null entries in the command list.
|
|
|
|
// This can happen when a command class is deleted or renamed.
|
|
|
|
for (int i = commandListProperty.arraySize - 1; i >= 0; --i)
|
|
|
|
{
|
|
|
|
SerializedProperty commandProperty = commandListProperty.GetArrayElementAtIndex(i);
|
|
|
|
if (commandProperty.objectReferenceValue == null)
|
|
|
|
{
|
|
|
|
commandListProperty.DeleteArrayElementAtIndex(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
serializedObject.ApplyModifiedProperties();
|
|
|
|
}
|
|
|
|
|
|
|
|
public virtual void DrawButtonToolbar()
|
|
|
|
{
|
|
|
|
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))
|
|
|
|
{
|
|
|
|
SelectPrevious();
|
|
|
|
GUI.FocusControl("dummycontrol");
|
|
|
|
Event.current.Use();
|
|
|
|
}
|
|
|
|
// Next Command
|
|
|
|
if ((Event.current.type == EventType.KeyDown) && (Event.current.keyCode == KeyCode.PageDown))
|
|
|
|
{
|
|
|
|
SelectNext();
|
|
|
|
GUI.FocusControl("dummycontrol");
|
|
|
|
Event.current.Use();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (GUILayout.Button(upIcon))
|
|
|
|
{
|
|
|
|
SelectPrevious();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Down Button
|
|
|
|
if (GUILayout.Button(downIcon))
|
|
|
|
{
|
|
|
|
SelectNext();
|
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Duplicate Button
|
|
|
|
if (GUILayout.Button(duplicateIcon))
|
|
|
|
{
|
|
|
|
Copy();
|
|
|
|
Paste();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete Button
|
|
|
|
if (GUILayout.Button(deleteIcon))
|
|
|
|
{
|
|
|
|
Delete();
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
// Show available Event Handlers in a drop down list with type of current
|
|
|
|
// event handler selected.
|
|
|
|
Block block = target as Block;
|
|
|
|
System.Type currentType = null;
|
|
|
|
if (block._EventHandler != null)
|
|
|
|
{
|
|
|
|
currentType = block._EventHandler.GetType();
|
|
|
|
}
|
|
|
|
|
|
|
|
string currentHandlerName = "<None>";
|
|
|
|
if (currentType != null)
|
|
|
|
{
|
|
|
|
EventHandlerInfoAttribute info = EventHandlerEditor.GetEventHandlerInfo(currentType);
|
|
|
|
if (info != null)
|
|
|
|
{
|
|
|
|
currentHandlerName = info.EventHandlerName;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
|
|
|
|
if (block._EventHandler != null)
|
|
|
|
{
|
|
|
|
EventHandlerEditor eventHandlerEditor = Editor.CreateEditor(block._EventHandler) as EventHandlerEditor;
|
|
|
|
if (eventHandlerEditor != null)
|
|
|
|
{
|
|
|
|
eventHandlerEditor.DrawInspectorGUI();
|
|
|
|
DestroyImmediate(eventHandlerEditor);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
if (flowchart == null)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var block = property.objectReferenceValue as Block;
|
|
|
|
|
|
|
|
// Build dictionary of child blocks
|
|
|
|
List<GUIContent> blockNames = new List<GUIContent>();
|
|
|
|
|
|
|
|
int selectedIndex = 0;
|
|
|
|
blockNames.Add(nullLabel);
|
|
|
|
var blocks = flowchart.GetComponents<Block>();
|
|
|
|
for (int i = 0; i < blocks.Length; ++i)
|
|
|
|
{
|
|
|
|
blockNames.Add(new GUIContent(blocks[i].BlockName));
|
|
|
|
|
|
|
|
if (block == blocks[i])
|
|
|
|
{
|
|
|
|
selectedIndex = i + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
selectedIndex = EditorGUILayout.Popup(label, selectedIndex, blockNames.ToArray());
|
|
|
|
if (selectedIndex == 0)
|
|
|
|
{
|
|
|
|
block = null; // Option 'None'
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
block = blocks[selectedIndex - 1];
|
|
|
|
}
|
|
|
|
|
|
|
|
property.objectReferenceValue = block;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static Block BlockField(Rect position, GUIContent nullLabel, Flowchart flowchart, Block block)
|
|
|
|
{
|
|
|
|
if (flowchart == null)
|
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
Block result = block;
|
|
|
|
|
|
|
|
// Build dictionary of child blocks
|
|
|
|
List<GUIContent> blockNames = new List<GUIContent>();
|
|
|
|
|
|
|
|
int selectedIndex = 0;
|
|
|
|
blockNames.Add(nullLabel);
|
|
|
|
Block[] blocks = flowchart.GetComponents<Block>();
|
|
|
|
for (int i = 0; i < blocks.Length; ++i)
|
|
|
|
{
|
|
|
|
blockNames.Add(new GUIContent(blocks[i].BlockName));
|
|
|
|
|
|
|
|
if (block == blocks[i])
|
|
|
|
{
|
|
|
|
selectedIndex = i + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
selectedIndex = EditorGUI.Popup(position, selectedIndex, blockNames.ToArray());
|
|
|
|
if (selectedIndex == 0)
|
|
|
|
{
|
|
|
|
result = null; // Option 'None'
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
result = blocks[selectedIndex - 1];
|
|
|
|
}
|
|
|
|
|
|
|
|
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()
|
|
|
|
{
|
|
|
|
const string path = "./Docs";
|
|
|
|
|
|
|
|
ExportCommandInfo(path);
|
|
|
|
ExportEventHandlerInfo(path);
|
|
|
|
|
|
|
|
FlowchartWindow.ShowNotification("Exported Reference Documentation");
|
|
|
|
}
|
|
|
|
|
|
|
|
protected static void ExportCommandInfo(string path)
|
|
|
|
{
|
|
|
|
// Dump command info
|
|
|
|
List<System.Type> menuTypes = EditorExtensions.FindDerivedTypes(typeof(Command)).ToList();
|
|
|
|
List<KeyValuePair<System.Type, CommandInfoAttribute>> filteredAttributes = GetFilteredCommandInfoAttribute(menuTypes);
|
|
|
|
filteredAttributes.Sort(CompareCommandAttributes);
|
|
|
|
|
|
|
|
// Build list of command categories
|
|
|
|
List<string> commandCategories = new List<string>();
|
|
|
|
foreach (var keyPair in filteredAttributes)
|
|
|
|
{
|
|
|
|
CommandInfoAttribute info = keyPair.Value;
|
|
|
|
if (info.Category != "" &&
|
|
|
|
!commandCategories.Contains(info.Category))
|
|
|
|
{
|
|
|
|
commandCategories.Add(info.Category);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
commandCategories.Sort();
|
|
|
|
|
|
|
|
// Output the commands in each category
|
|
|
|
foreach (string category in commandCategories)
|
|
|
|
{
|
|
|
|
string markdown = "# " + category + " commands # {#" + category.ToLower() + "_commands}\n\n";
|
|
|
|
markdown += "[TOC]\n";
|
|
|
|
|
|
|
|
foreach (var keyPair in filteredAttributes)
|
|
|
|
{
|
|
|
|
CommandInfoAttribute info = keyPair.Value;
|
|
|
|
|
|
|
|
if (info.Category == category ||
|
|
|
|
info.Category == "" && category == "Scripting")
|
|
|
|
{
|
|
|
|
markdown += "# " + info.CommandName + " # {#" + info.CommandName.Replace(" ", "") + "}\n";
|
|
|
|
markdown += info.HelpText + "\n\n";
|
|
|
|
markdown += "Defined in " + keyPair.Key.FullName + "\n";
|
|
|
|
markdown += GetPropertyInfo(keyPair.Key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
string filePath = path + "/command_ref/" + category.ToLower() + "_commands.md";
|
|
|
|
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
|
|
|
|
File.WriteAllText(filePath, markdown);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected static void ExportEventHandlerInfo(string path)
|
|
|
|
{
|
|
|
|
List<System.Type> eventHandlerTypes = EditorExtensions.FindDerivedTypes(typeof(EventHandler)).ToList();
|
|
|
|
List<string> eventHandlerCategories = new List<string>();
|
|
|
|
eventHandlerCategories.Add("Core");
|
|
|
|
foreach (System.Type type in eventHandlerTypes)
|
|
|
|
{
|
|
|
|
EventHandlerInfoAttribute info = EventHandlerEditor.GetEventHandlerInfo(type);
|
|
|
|
if (info != null &&
|
|
|
|
info.Category != "" &&
|
|
|
|
!eventHandlerCategories.Contains(info.Category))
|
|
|
|
{
|
|
|
|
eventHandlerCategories.Add(info.Category);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
eventHandlerCategories.Sort();
|
|
|
|
|
|
|
|
// Output the commands in each category
|
|
|
|
foreach (string category in eventHandlerCategories)
|
|
|
|
{
|
|
|
|
string markdown = "# " + category + " event handlers # {#" + category.ToLower() + "_events}\n\n";
|
|
|
|
markdown += "[TOC]\n";
|
|
|
|
|
|
|
|
foreach (System.Type type in eventHandlerTypes)
|
|
|
|
{
|
|
|
|
EventHandlerInfoAttribute info = EventHandlerEditor.GetEventHandlerInfo(type);
|
|
|
|
|
|
|
|
if (info != null &&
|
|
|
|
(info.Category == category ||
|
|
|
|
(info.Category == "" && category == "Core")))
|
|
|
|
{
|
|
|
|
markdown += "# " + info.EventHandlerName + " # {#" + info.EventHandlerName.Replace(" ", "") + "}\n";
|
|
|
|
markdown += info.HelpText + "\n\n";
|
|
|
|
markdown += "Defined in " + type.FullName + "\n";
|
|
|
|
markdown += GetPropertyInfo(type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
string filePath = path + "/command_ref/" + category.ToLower() + "_events.md";
|
|
|
|
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
|
|
|
|
File.WriteAllText(filePath, markdown);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected static string GetPropertyInfo(System.Type type)
|
|
|
|
{
|
|
|
|
string markdown = "";
|
|
|
|
foreach (FieldInfo field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
|
|
|
{
|
|
|
|
TooltipAttribute attribute = (TooltipAttribute)Attribute.GetCustomAttribute(field, typeof(TooltipAttribute));
|
|
|
|
if (attribute == null)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Change field name to how it's displayed in the inspector
|
|
|
|
string propertyName = Regex.Replace(field.Name, "(\\B[A-Z])", " $1");
|
|
|
|
if (propertyName.Length > 1)
|
|
|
|
{
|
|
|
|
propertyName = propertyName.Substring(0, 1).ToUpper() + propertyName.Substring(1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
propertyName = propertyName.ToUpper();
|
|
|
|
}
|
|
|
|
|
|
|
|
markdown += propertyName + " | " + field.FieldType + " | " + attribute.tooltip + "\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
if (markdown.Length > 0)
|
|
|
|
{
|
|
|
|
markdown = "\nProperty | Type | Description\n --- | --- | ---\n" + markdown + "\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
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()
|
|
|
|
{
|
|
|
|
var block = target as Block;
|
|
|
|
var flowchart = (Flowchart)block.GetFlowchart();
|
|
|
|
|
|
|
|
if (flowchart == null)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool showCut = false;
|
|
|
|
bool showCopy = false;
|
|
|
|
bool showDelete = false;
|
|
|
|
bool showPaste = false;
|
|
|
|
bool showPlay = false;
|
|
|
|
|
|
|
|
if (flowchart.SelectedCommands.Count > 0)
|
|
|
|
{
|
|
|
|
showCut = true;
|
|
|
|
showCopy = true;
|
|
|
|
showDelete = true;
|
|
|
|
if (flowchart.SelectedCommands.Count == 1 && Application.isPlaying)
|
|
|
|
{
|
|
|
|
showPlay = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CommandCopyBuffer commandCopyBuffer = CommandCopyBuffer.GetInstance();
|
|
|
|
|
|
|
|
if (commandCopyBuffer.HasCommands())
|
|
|
|
{
|
|
|
|
showPaste = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
GenericMenu commandMenu = new GenericMenu();
|
|
|
|
|
|
|
|
if (showCut)
|
|
|
|
{
|
|
|
|
commandMenu.AddItem(new GUIContent("Cut"), false, Cut);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
commandMenu.AddDisabledItem(new GUIContent("Cut"));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (showCopy)
|
|
|
|
{
|
|
|
|
commandMenu.AddItem(new GUIContent("Copy"), false, Copy);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
commandMenu.AddDisabledItem(new GUIContent("Copy"));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (showPaste)
|
|
|
|
{
|
|
|
|
commandMenu.AddItem(new GUIContent("Paste"), false, Paste);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
commandMenu.AddDisabledItem(new GUIContent("Paste"));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (showDelete)
|
|
|
|
{
|
|
|
|
commandMenu.AddItem(new GUIContent("Delete"), false, Delete);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
commandMenu.AddDisabledItem(new GUIContent("Delete"));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (showPlay)
|
|
|
|
{
|
|
|
|
commandMenu.AddItem(new GUIContent("Play from selected"), false, PlayCommand);
|
|
|
|
commandMenu.AddItem(new GUIContent("Stop all and play"), false, StopAllPlayCommand);
|
|
|
|
}
|
|
|
|
|
|
|
|
commandMenu.AddSeparator("");
|
|
|
|
|
|
|
|
commandMenu.AddItem(new GUIContent("Select All"), false, SelectAll);
|
|
|
|
commandMenu.AddItem(new GUIContent("Select None"), false, SelectNone);
|
|
|
|
|
|
|
|
commandMenu.ShowAsContext();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void SelectAll()
|
|
|
|
{
|
|
|
|
var block = target as Block;
|
|
|
|
var flowchart = (Flowchart)block.GetFlowchart();
|
|
|
|
|
|
|
|
if (flowchart == null ||
|
|
|
|
flowchart.SelectedBlock == null)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
flowchart.ClearSelectedCommands();
|
|
|
|
Undo.RecordObject(flowchart, "Select All");
|
|
|
|
foreach (Command command in flowchart.SelectedBlock.CommandList)
|
|
|
|
{
|
|
|
|
flowchart.AddSelectedCommand(command);
|
|
|
|
}
|
|
|
|
|
|
|
|
Repaint();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void SelectNone()
|
|
|
|
{
|
|
|
|
var block = target as Block;
|
|
|
|
var flowchart = (Flowchart)block.GetFlowchart();
|
|
|
|
|
|
|
|
if (flowchart == null ||
|
|
|
|
flowchart.SelectedBlock == null)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Undo.RecordObject(flowchart, "Select None");
|
|
|
|
flowchart.ClearSelectedCommands();
|
|
|
|
|
|
|
|
Repaint();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void Cut()
|
|
|
|
{
|
|
|
|
Copy();
|
|
|
|
Delete();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void Copy()
|
|
|
|
{
|
|
|
|
var block = target as Block;
|
|
|
|
var flowchart = (Flowchart)block.GetFlowchart();
|
|
|
|
|
|
|
|
if (flowchart == null ||
|
|
|
|
flowchart.SelectedBlock == null)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
CommandCopyBuffer commandCopyBuffer = CommandCopyBuffer.GetInstance();
|
|
|
|
commandCopyBuffer.Clear();
|
|
|
|
|
|
|
|
// Scan through all commands in execution order to see if each needs to be copied
|
|
|
|
foreach (Command command in flowchart.SelectedBlock.CommandList)
|
|
|
|
{
|
|
|
|
if (flowchart.SelectedCommands.Contains(command))
|
|
|
|
{
|
|
|
|
var type = command.GetType();
|
|
|
|
Command newCommand = Undo.AddComponent(commandCopyBuffer.gameObject, type) as Command;
|
|
|
|
var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
|
|
|
|
foreach (var field in fields)
|
|
|
|
{
|
|
|
|
// Copy all public fields
|
|
|
|
bool copy = field.IsPublic;
|
|
|
|
|
|
|
|
// Copy non-public fields that have the SerializeField attribute
|
|
|
|
var attributes = field.GetCustomAttributes(typeof(SerializeField), true);
|
|
|
|
if (attributes.Length > 0)
|
|
|
|
{
|
|
|
|
copy = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (copy)
|
|
|
|
{
|
|
|
|
field.SetValue(newCommand, field.GetValue(command));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void Paste()
|
|
|
|
{
|
|
|
|
var block = target as Block;
|
|
|
|
var flowchart = (Flowchart)block.GetFlowchart();
|
|
|
|
|
|
|
|
if (flowchart == null ||
|
|
|
|
flowchart.SelectedBlock == null)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
CommandCopyBuffer commandCopyBuffer = CommandCopyBuffer.GetInstance();
|
|
|
|
|
|
|
|
// Find where to paste commands in block (either at end or after last selected command)
|
|
|
|
int pasteIndex = flowchart.SelectedBlock.CommandList.Count;
|
|
|
|
if (flowchart.SelectedCommands.Count > 0)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < flowchart.SelectedBlock.CommandList.Count; ++i)
|
|
|
|
{
|
|
|
|
Command command = flowchart.SelectedBlock.CommandList[i];
|
|
|
|
|
|
|
|
foreach (Command selectedCommand in flowchart.SelectedCommands)
|
|
|
|
{
|
|
|
|
if (command == selectedCommand)
|
|
|
|
{
|
|
|
|
pasteIndex = i + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach (Command command in commandCopyBuffer.GetCommands())
|
|
|
|
{
|
|
|
|
// Using the Editor copy / paste functionality instead instead of reflection
|
|
|
|
// because this does a deep copy of the command properties.
|
|
|
|
if (ComponentUtility.CopyComponent(command))
|
|
|
|
{
|
|
|
|
if (ComponentUtility.PasteComponentAsNew(flowchart.gameObject))
|
|
|
|
{
|
|
|
|
Command[] commands = flowchart.GetComponents<Command>();
|
|
|
|
Command pastedCommand = commands.Last<Command>();
|
|
|
|
if (pastedCommand != null)
|
|
|
|
{
|
|
|
|
pastedCommand.ItemId = flowchart.NextItemId();
|
|
|
|
flowchart.SelectedBlock.CommandList.Insert(pasteIndex++, pastedCommand);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// This stops the user pasting the command manually into another game object.
|
|
|
|
ComponentUtility.CopyComponent(flowchart.transform);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Because this is an async call, we need to force prefab instances to record changes
|
|
|
|
PrefabUtility.RecordPrefabInstancePropertyModifications(block);
|
|
|
|
|
|
|
|
Repaint();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void Delete()
|
|
|
|
{
|
|
|
|
var block = target as Block;
|
|
|
|
var flowchart = (Flowchart)block.GetFlowchart();
|
|
|
|
|
|
|
|
if (flowchart == null ||
|
|
|
|
flowchart.SelectedBlock == null)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
int lastSelectedIndex = 0;
|
|
|
|
for (int i = flowchart.SelectedBlock.CommandList.Count - 1; i >= 0; --i)
|
|
|
|
{
|
|
|
|
Command command = flowchart.SelectedBlock.CommandList[i];
|
|
|
|
foreach (Command selectedCommand in flowchart.SelectedCommands)
|
|
|
|
{
|
|
|
|
if (command == selectedCommand)
|
|
|
|
{
|
|
|
|
command.OnCommandRemoved(block);
|
|
|
|
|
|
|
|
// Order of destruction is important here for undo to work
|
|
|
|
Undo.DestroyObjectImmediate(command);
|
|
|
|
|
|
|
|
Undo.RecordObject((Block)flowchart.SelectedBlock, "Delete");
|
|
|
|
flowchart.SelectedBlock.CommandList.RemoveAt(i);
|
|
|
|
|
|
|
|
lastSelectedIndex = i;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Undo.RecordObject(flowchart, "Delete");
|
|
|
|
flowchart.ClearSelectedCommands();
|
|
|
|
|
|
|
|
if (lastSelectedIndex < flowchart.SelectedBlock.CommandList.Count)
|
|
|
|
{
|
|
|
|
var nextCommand = flowchart.SelectedBlock.CommandList[lastSelectedIndex];
|
|
|
|
block.GetFlowchart().AddSelectedCommand(nextCommand);
|
|
|
|
}
|
|
|
|
|
|
|
|
Repaint();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void PlayCommand()
|
|
|
|
{
|
|
|
|
var targetBlock = target as Block;
|
|
|
|
var flowchart = (Flowchart)targetBlock.GetFlowchart();
|
|
|
|
Command command = flowchart.SelectedCommands[0];
|
|
|
|
if (targetBlock.IsExecuting())
|
|
|
|
{
|
|
|
|
// The Block is already executing.
|
|
|
|
// Tell the Block to stop, wait a little while so the executing command has a
|
|
|
|
// chance to stop, and then start execution again from the new command.
|
|
|
|
targetBlock.Stop();
|
|
|
|
flowchart.StartCoroutine(RunBlock(flowchart, targetBlock, command.CommandIndex, 0.2f));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Block isn't executing yet so can start it now.
|
|
|
|
flowchart.ExecuteBlock(targetBlock, command.CommandIndex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void StopAllPlayCommand()
|
|
|
|
{
|
|
|
|
var targetBlock = target as Block;
|
|
|
|
var flowchart = (Flowchart)targetBlock.GetFlowchart();
|
|
|
|
Command command = flowchart.SelectedCommands[0];
|
|
|
|
|
|
|
|
// Stop all active blocks then run the selected block.
|
|
|
|
flowchart.StopAllBlocks();
|
|
|
|
flowchart.StartCoroutine(RunBlock(flowchart, targetBlock, command.CommandIndex, 0.2f));
|
|
|
|
}
|
|
|
|
|
|
|
|
protected IEnumerator RunBlock(Flowchart flowchart, Block targetBlock, int commandIndex, float delay)
|
|
|
|
{
|
|
|
|
yield return new WaitForSeconds(delay);
|
|
|
|
flowchart.ExecuteBlock(targetBlock, commandIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void SelectPrevious()
|
|
|
|
{
|
|
|
|
var block = target as Block;
|
|
|
|
var flowchart = (Flowchart)block.GetFlowchart();
|
|
|
|
|
|
|
|
int firstSelectedIndex = flowchart.SelectedBlock.CommandList.Count;
|
|
|
|
bool firstSelectedCommandFound = false;
|
|
|
|
if (flowchart.SelectedCommands.Count > 0)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < flowchart.SelectedBlock.CommandList.Count; i++)
|
|
|
|
{
|
|
|
|
Command commandInBlock = flowchart.SelectedBlock.CommandList[i];
|
|
|
|
|
|
|
|
foreach (Command selectedCommand in flowchart.SelectedCommands)
|
|
|
|
{
|
|
|
|
if (commandInBlock == selectedCommand)
|
|
|
|
{
|
|
|
|
if (!firstSelectedCommandFound)
|
|
|
|
{
|
|
|
|
firstSelectedIndex = i;
|
|
|
|
firstSelectedCommandFound = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (firstSelectedCommandFound)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (firstSelectedIndex > 0)
|
|
|
|
{
|
|
|
|
flowchart.ClearSelectedCommands();
|
|
|
|
flowchart.AddSelectedCommand(flowchart.SelectedBlock.CommandList[firstSelectedIndex - 1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
Repaint();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void SelectNext()
|
|
|
|
{
|
|
|
|
var block = target as Block;
|
|
|
|
var flowchart = (Flowchart)block.GetFlowchart();
|
|
|
|
|
|
|
|
int lastSelectedIndex = -1;
|
|
|
|
if (flowchart.SelectedCommands.Count > 0)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < flowchart.SelectedBlock.CommandList.Count; i++)
|
|
|
|
{
|
|
|
|
Command commandInBlock = flowchart.SelectedBlock.CommandList[i];
|
|
|
|
|
|
|
|
foreach (Command selectedCommand in flowchart.SelectedCommands)
|
|
|
|
{
|
|
|
|
if (commandInBlock == selectedCommand)
|
|
|
|
{
|
|
|
|
lastSelectedIndex = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (lastSelectedIndex < flowchart.SelectedBlock.CommandList.Count - 1)
|
|
|
|
{
|
|
|
|
flowchart.ClearSelectedCommands();
|
|
|
|
flowchart.AddSelectedCommand(flowchart.SelectedBlock.CommandList[lastSelectedIndex + 1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
Repaint();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|