diff --git a/Assets/Fungus/Scripts/Editor/FlowchartWindow.cs b/Assets/Fungus/Scripts/Editor/FlowchartWindow.cs index bfe96357..e002b9d1 100644 --- a/Assets/Fungus/Scripts/Editor/FlowchartWindow.cs +++ b/Assets/Fungus/Scripts/Editor/FlowchartWindow.cs @@ -107,11 +107,6 @@ namespace Fungus.EditorUtils protected List copyList = new List(); public static List deleteList = new List(); - protected List windowBlockMap = new List(); - - // The ReorderableList control doesn't drag properly when used with GUI.DragWindow(), - // so we just implement dragging ourselves. - protected int dragWindowId = -1; protected Vector2 startDragPosition; public const float minZoomValue = 0.25f; @@ -121,8 +116,6 @@ namespace Fungus.EditorUtils protected static BlockInspector blockInspector; - protected bool mouseOverVariables = false; - protected int forceRepaintCount; protected Texture2D addTexture; @@ -147,7 +140,9 @@ namespace Fungus.EditorUtils protected Block[] filteredBlocks; protected int blockPopupSelection = -1; protected Vector2 popupScroll; - protected bool mouseOverPopup; + protected Flowchart flowchart; + protected Block[] blocks; + protected Block dragBlock; [MenuItem("Tools/Fungus/Flowchart Window")] static void Init() @@ -247,7 +242,13 @@ namespace Fungus.EditorUtils return fungusState.SelectedFlowchart; } - protected virtual void HandleEarlyEvents(Flowchart flowchart, Event e) + protected void UpdateFilteredBlocks() + { + filteredBlocks = blocks.Where(block => block.BlockName.ToLower().Contains(searchString.ToLower())).ToArray(); + blockPopupSelection = Mathf.Clamp(blockPopupSelection, 0, filteredBlocks.Length - 1); + } + + protected virtual void HandleEarlyEvents(Event e) { switch (e.type) { @@ -307,11 +308,11 @@ namespace Fungus.EditorUtils if (centerBlock && filteredBlocks.Length > 0) { var block = filteredBlocks[blockPopupSelection]; - CenterBlock(flowchart, block); + CenterBlock(block); if (selectBlock) { - SelectBlock(flowchart, block); + SelectBlock(block); } } @@ -327,7 +328,7 @@ namespace Fungus.EditorUtils } else if (e.keyCode == KeyCode.Escape && flowchart.SelectedBlocks.Count > 0) { - DeselectAll(flowchart); + DeselectAll(); e.Use(); } break; @@ -336,70 +337,43 @@ namespace Fungus.EditorUtils protected virtual void OnGUI() { - var flowchart = GetFlowchart(); + flowchart = GetFlowchart(); + if (flowchart == null) { GUILayout.Label("No Flowchart scene object selected"); return; } - // Delete any scheduled objects - foreach (var deleteBlock in deleteList) - { - bool isSelected = (flowchart.SelectedBlocks.Contains(deleteBlock)); + DeleteBlocks(); - var commandList = deleteBlock.CommandList; - foreach (var command in commandList) - { - Undo.DestroyObjectImmediate(command); - } - - if (deleteBlock._EventHandler != null) - { - Undo.DestroyObjectImmediate(deleteBlock._EventHandler); - } - - Undo.DestroyObjectImmediate((Block)deleteBlock); - flowchart.ClearSelectedCommands(); - - if (isSelected) - { - // Deselect - flowchart.SelectedBlocks.Remove(deleteBlock); - - // Revert to showing properties for the Flowchart - Selection.activeGameObject = flowchart.gameObject; - } - } - deleteList.Clear(); + blocks = flowchart.GetComponents(); + UpdateFilteredBlocks(); - HandleEarlyEvents(flowchart, Event.current); + HandleEarlyEvents(Event.current); - // Clear search filter focus - if (Event.current.type == EventType.MouseDown && !searchRect.Contains(Event.current.mousePosition) && - !popupRect.Contains(Event.current.mousePosition)) + // Draw background color / drop shadow + if (Event.current.type == EventType.Repaint) { - CloseBlockPopup(); + UnityEditor.Graphs.Styles.graphBackground.Draw( + new Rect(0, 17, position.width, position.height - 17), false, false, false, false + ); } - if (Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Escape) + // Draw blocks and connections + DrawFlowchartView(); + + // Draw selection box + if (startSelectionBoxPosition.x >= 0 && startSelectionBoxPosition.y >= 0) { - if (GUI.GetNameOfFocusedControl() != searchFieldName && flowchart.SelectedBlocks.Count > 0) - { - DeselectAll(flowchart); - Event.current.Use(); - } + GUI.Box(selectionBox, "", GUI.skin.FindStyle("SelectionRect")); + forceRepaintCount = 1; } - DrawFlowchartView(flowchart); - DrawOverlay(flowchart); - - // Handle selection box events after block and overlay events - HandleSelectionBox(flowchart); - - ValidateCommands(flowchart); - ExecuteCommands(flowchart); + // Draw toolbar, search popup, and variables window + DrawOverlay(); + // Handle events for custom GUI base.HandleEvents(Event.current); if (forceRepaintCount > 0) @@ -409,7 +383,7 @@ namespace Fungus.EditorUtils } } - protected virtual void DrawOverlay(Flowchart flowchart) + protected virtual void DrawOverlay() { // Main toolbar group GUILayout.BeginHorizontal(EditorStyles.toolbar); @@ -419,7 +393,7 @@ namespace Fungus.EditorUtils // Draw add block button if (GUILayout.Button(new GUIContent(addTexture, "Add a new block"), EditorStyles.toolbarButton)) { - DeselectAll(flowchart); + DeselectAll(); Vector2 newNodePosition = new Vector2( 50 / flowchart.Zoom - flowchart.ScrollPos.x, 50 / flowchart.Zoom - flowchart.ScrollPos.y ); @@ -437,13 +411,13 @@ namespace Fungus.EditorUtils if (newZoom != flowchart.Zoom) { - DoZoom(flowchart, newZoom - flowchart.Zoom, Vector2.one * 0.5f); + DoZoom(newZoom - flowchart.Zoom, Vector2.one * 0.5f); } // Draw center button if (GUILayout.Button("Center", EditorStyles.toolbarButton)) { - flowchart.ScrollPos = flowchart.CenterPosition; + CenterFlowchart(); } GUILayout.FlexibleSpace(); @@ -454,11 +428,6 @@ namespace Fungus.EditorUtils if (newString != searchString) { searchString = newString; - filteredBlocks = flowchart.GetComponents().Where(block => { - return block.BlockName.ToLower().Contains(searchString.ToLower()); - }).ToArray(); - - blockPopupSelection = Mathf.Clamp(blockPopupSelection, 0, filteredBlocks.Length - 1); } if (Event.current.type == EventType.Repaint) @@ -548,11 +517,11 @@ namespace Fungus.EditorUtils // Draw block search popup on top of other controls if (GUI.GetNameOfFocusedControl() == searchFieldName && filteredBlocks.Length > 0) { - DrawBlockPopup(flowchart); + DrawBlockPopup(); } } - protected virtual void DrawBlockPopup(Flowchart flowchart) + protected virtual void DrawBlockPopup() { blockPopupSelection = Mathf.Clamp(blockPopupSelection, 0, filteredBlocks.Length - 1); @@ -570,129 +539,361 @@ namespace Fungus.EditorUtils } GUILayout.BeginArea(popupRect); - popupScroll = EditorGUILayout.BeginScrollView(popupScroll, GUIStyle.none, GUI.skin.verticalScrollbar); + { + popupScroll = EditorGUILayout.BeginScrollView(popupScroll, GUIStyle.none, GUI.skin.verticalScrollbar); + { + var normalStyle = new GUIStyle(GUI.skin.FindStyle("MenuItem")); + normalStyle.padding = new RectOffset(8, 0, 0, 0); + normalStyle.imagePosition = ImagePosition.ImageLeft; + var selectedStyle = new GUIStyle(normalStyle); + selectedStyle.normal = selectedStyle.hover; + normalStyle.hover = normalStyle.normal; + + for (int i = 0; i < filteredBlocks.Length; ++i) + { + EditorGUILayout.BeginHorizontal(GUILayout.Height(16)); - var normalStyle = new GUIStyle(GUI.skin.FindStyle("MenuItem")); - normalStyle.padding = new RectOffset(8, 0, 0, 0); - normalStyle.imagePosition = ImagePosition.ImageLeft; - var selectedStyle = new GUIStyle(normalStyle); - selectedStyle.normal = selectedStyle.hover; - normalStyle.hover = normalStyle.normal; + var block = filteredBlocks[i]; + var style = i == blockPopupSelection ? selectedStyle : normalStyle; - for (int i = 0; i < filteredBlocks.Length; ++i) - { - EditorGUILayout.BeginHorizontal(GUILayout.Height(16)); + GUI.contentColor = GetBlockGraphics(block).tint; - var block = filteredBlocks[i]; - var style = i == blockPopupSelection ? selectedStyle : normalStyle; + var buttonPressed = false; + if (GUILayout.Button(FungusEditorResources.BulletPoint, style, GUILayout.Width(16))) + { + buttonPressed = true; + } - GUI.contentColor = GetBlockGraphics(block).tint; + GUI.contentColor = Color.white; - var buttonPressed = false; - if (GUILayout.Button(FungusEditorResources.BulletPoint, style, GUILayout.Width(16))) - { - buttonPressed = true; - } + if (GUILayout.Button(block.BlockName, style)) + { + buttonPressed = true; + } - GUI.contentColor = Color.white; + if (buttonPressed) + { + CenterBlock(block); + SelectBlock(block); + CloseBlockPopup(); + } - if (GUILayout.Button(block.BlockName, style)) - { - buttonPressed = true; + EditorGUILayout.EndHorizontal(); + } } + EditorGUILayout.EndScrollView(); + } + GUILayout.EndArea(); + } + + protected Block GetBlockAtPoint(Vector2 point) + { + for (int i = blocks.Length - 1; i > -1; --i) + { + var block = blocks[i]; + var rect = block._NodeRect; + rect.position += flowchart.ScrollPos; - if (buttonPressed) + if (rect.Contains(point / flowchart.Zoom)) { - CenterBlock(flowchart, block); - SelectBlock(flowchart, block); - CloseBlockPopup(); + return block; } - - EditorGUILayout.EndHorizontal(); } - EditorGUILayout.EndScrollView(); - GUILayout.EndArea(); + return null; } - - protected virtual void DrawFlowchartView(Flowchart flowchart) - { - Block[] blocks = flowchart.GetComponents(); - foreach (var block in blocks) + protected void BringSelectedBlockToFront() + { + var block = flowchart.SelectedBlock; + if (block != null) { - var node = block as Node; - if (node == null) + var index = Array.IndexOf(blocks, block); + for (int i = index; i < blocks.Length - 1; ++i) { - continue; + blocks[i] = blocks[i + 1]; } - - var newRect = new Rect(); - newRect.xMin = Mathf.Min(flowchart.ScrollViewRect.xMin, node._NodeRect.xMin - 400); - newRect.xMax = Mathf.Max(flowchart.ScrollViewRect.xMax, node._NodeRect.xMax + 400); - newRect.yMin = Mathf.Min(flowchart.ScrollViewRect.yMin, node._NodeRect.yMin - 400); - newRect.yMax = Mathf.Max(flowchart.ScrollViewRect.yMax, node._NodeRect.yMax + 400); - flowchart.ScrollViewRect = newRect; + blocks[blocks.Length - 1] = block; } + } - // Draw background color / drop shadow - if (Event.current.type == EventType.Repaint) + protected override void OnMouseDown(Event e) + { + var hitBlock = GetBlockAtPoint(e.mousePosition); + + switch(e.button) { - UnityEditor.Graphs.Styles.graphBackground.Draw( - new Rect(0, 17, position.width, position.height - 17), false, false, false, false - ); + case MouseButton.Left: + if (!e.alt) + { + if (hitBlock != null) + { + startDragPosition = e.mousePosition / flowchart.Zoom - flowchart.ScrollPos; + Undo.RecordObject(flowchart, "Select"); + + if (GetAppendModifierDown()) + { + if (flowchart.SelectedBlocks.Contains(hitBlock)) + { + flowchart.SelectedBlocks.Remove(hitBlock); + } + else + { + flowchart.AddSelectedBlock(hitBlock); + } + } + else + { + if (flowchart.SelectedBlocks.Contains(hitBlock)) + { + SetBlockForInspector(flowchart, hitBlock); + } + else + { + SelectBlock(hitBlock); + } + + dragBlock = hitBlock; + } + + BringSelectedBlockToFront(); + e.Use(); + GUIUtility.keyboardControl = 0; // Fix for textarea not refeshing (change focus) + } + else if (!(UnityEditor.Tools.current == Tool.View && UnityEditor.Tools.viewTool == ViewTool.Zoom)) + { + if (!GetAppendModifierDown()) + { + DeselectAll(); + } + + startSelectionBoxPosition = e.mousePosition; + mouseDownSelectionState = new List(flowchart.SelectedBlocks); + e.Use(); + } + } + break; + + case MouseButton.Right: + rightClickDown = e.mousePosition; + e.Use(); + break; } - // Calc rect for script view - Rect scriptViewRect = new Rect(0, 0, this.position.width / flowchart.Zoom, this.position.height / flowchart.Zoom); + } - // Update right click start outside of EditorZoomArea - if (Event.current.button == 1) + protected override void OnMouseDrag(Event e) + { + var drag = false; + switch (e.button) { - if (Event.current.type == EventType.MouseDown) + case MouseButton.Left: + // Block dragging + if (dragBlock != null) + { + for (int i = 0; i < flowchart.SelectedBlocks.Count; ++i) + { + var block = flowchart.SelectedBlocks[i]; + var tempRect = block._NodeRect; + tempRect.position += e.delta / flowchart.Zoom; + block._NodeRect = tempRect; + } + e.Use(); + } + // Pan tool or alt + left click + else if (UnityEditor.Tools.current == Tool.View && UnityEditor.Tools.viewTool == ViewTool.Pan || e.alt) { - rightClickDown = Event.current.mousePosition; + drag = true; } - else if (Event.current.type == EventType.MouseDrag) + else if (UnityEditor.Tools.current == Tool.View && UnityEditor.Tools.viewTool == ViewTool.Zoom) { - if (Vector2.Distance(rightClickDown, Event.current.mousePosition) > rightClickTolerance) + DoZoom(-Event.current.delta.y * 0.01f, Vector2.one * 0.5f); + e.Use(); + } + // Selection box + else if (startSelectionBoxPosition.x >= 0 && startSelectionBoxPosition.y >= 0) + { + var topLeft = Vector2.Min(startSelectionBoxPosition, e.mousePosition); + var bottomRight = Vector2.Max(startSelectionBoxPosition, e.mousePosition); + selectionBox = Rect.MinMaxRect(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y); + + Rect zoomSelectionBox = selectionBox; + zoomSelectionBox.position -= flowchart.ScrollPos * flowchart.Zoom; + zoomSelectionBox.position /= flowchart.Zoom; + zoomSelectionBox.size /= flowchart.Zoom; + + for (int i = 0; i < blocks.Length; ++i) { - rightClickDown = -Vector2.one; + var block = blocks[i]; + if (zoomSelectionBox.Overlaps(block._NodeRect)) + { + if (mouseDownSelectionState.Contains(block)) + { + flowchart.SelectedBlocks.Remove(block); + } + else + { + flowchart.AddSelectedBlock(block); + } + } + else if (mouseDownSelectionState.Contains(block)) + { + flowchart.AddSelectedBlock(block); + } + else + { + flowchart.SelectedBlocks.Remove(block); + } } + e.Use(); } - } + break; - EditorZoomArea.Begin(flowchart.Zoom, scriptViewRect); + case MouseButton.Right: + if (Vector2.Distance(rightClickDown, e.mousePosition) > rightClickTolerance) + { + rightClickDown = -Vector2.one; + } + drag = true; + break; - if (Event.current.type == EventType.Repaint) - { - DrawGrid(flowchart); + case MouseButton.Middle: + drag = true; + break; } - - // The center of the Flowchart depends on the block positions and window dimensions, so we calculate it - // here in the FlowchartWindow class and store it on the Flowchart object for use later. - if (flowchart != null && blocks.Length > 0) + + if (drag) { - CalcFlowchartCenter(flowchart, blocks); + flowchart.ScrollPos += e.delta / flowchart.Zoom; + e.Use(); } + } - // Draw connections - foreach (var block in blocks) + protected override void OnRawMouseUp(Event e) + { + var hitBlock = GetBlockAtPoint(e.mousePosition); + + switch (e.button) { - DrawConnections(flowchart, block, false); + case MouseButton.Left: + + if (dragBlock != null) + { + for (int i = 0; i < flowchart.SelectedBlocks.Count; ++i) + { + var block = flowchart.SelectedBlocks[i]; + var tempRect = block._NodeRect; + var distance = e.mousePosition / flowchart.Zoom - flowchart.ScrollPos - startDragPosition; + tempRect.position -= distance; + block._NodeRect = tempRect; + Undo.RecordObject(block, "Block Position"); + tempRect.position += distance; + block._NodeRect = tempRect; + } + + dragBlock = null; + } + + // Check to see if selection actually changed? + if (selectionBox.size.x > 0 && selectionBox.size.y > 0) + { + var tempList = new List(flowchart.SelectedBlocks); + flowchart.SelectedBlocks = mouseDownSelectionState; + Undo.RecordObject(flowchart, "Select"); + flowchart.SelectedBlocks = tempList; + + if (flowchart.SelectedBlock != null) + { + SetBlockForInspector(flowchart, flowchart.SelectedBlock); + } + } + break; + + case MouseButton.Right: + if (rightClickDown != -Vector2.one) + { + var menu = new GenericMenu(); + var mousePosition = rightClickDown; + + // Clicked on a block + if (hitBlock != null) + { + flowchart.AddSelectedBlock(hitBlock); + + // Use a copy because flowchart.SelectedBlocks gets modified + var blockList = new List(flowchart.SelectedBlocks); + menu.AddItem(new GUIContent ("Copy"), false, () => Copy()); + menu.AddItem(new GUIContent ("Cut"), false, () => Cut()); + menu.AddItem(new GUIContent ("Duplicate"), false, () => Duplicate()); + menu.AddItem(new GUIContent ("Delete"), false, () => AddToDeleteList(blockList)); + } + else + { + DeselectAll(); + + menu.AddItem(new GUIContent("Add Block"), false, () => CreateBlock(flowchart, mousePosition / flowchart.Zoom - flowchart.ScrollPos)); + + if (copyList.Count > 0) + { + menu.AddItem(new GUIContent("Paste"), false, () => Paste(mousePosition)); + } + else + { + menu.AddDisabledItem(new GUIContent("Paste")); + } + } + + var menuRect = new Rect(); + menuRect.position = new Vector2(mousePosition.x, mousePosition.y - 12f); + menu.DropDown(menuRect); + Event.current.Use(); + } + break; } - foreach (var block in blocks) + + // Selection box + selectionBox.size = Vector2.zero; + selectionBox.position = -Vector2.one; + startSelectionBoxPosition = selectionBox.position; + } + + protected override void OnScrollWheel(Event e) + { + if (selectionBox.size == Vector2.zero) { - DrawConnections(flowchart, block, true); + Vector2 zoomCenter; + zoomCenter.x = e.mousePosition.x / flowchart.Zoom / position.width; + zoomCenter.y = e.mousePosition.y / flowchart.Zoom / position.height; + zoomCenter *= flowchart.Zoom; + + DoZoom(-Event.current.delta.y * 0.01f, zoomCenter); + e.Use(); } + } + + protected virtual void DrawFlowchartView() + { + var e = Event.current; - GUIStyle windowStyle = new GUIStyle(); - windowStyle.stretchHeight = true; + // Calc rect for script view + Rect scriptViewRect = new Rect(0, 0, this.position.width / flowchart.Zoom, this.position.height / flowchart.Zoom); - BeginWindows(); + EditorZoomArea.Begin(flowchart.Zoom, scriptViewRect); + + if (e.type == EventType.Repaint) + { + DrawGrid(); + + // Draw connections + foreach (var block in blocks) + { + DrawConnections(block, false); + } + foreach (var block in blocks) + { + DrawConnections(block, true); + } + } - windowBlockMap.Clear(); - bool useEvent = false; - bool endDrag = false; for (int i = 0; i < blocks.Length; ++i) { var block = blocks[i]; @@ -704,67 +905,62 @@ namespace Fungus.EditorUtils nodeWidthB = nodeStyle.CalcSize(new GUIContent(block._EventHandler.GetSummary())).x + 10; } - if (Event.current.button == 0) - { - Rect tempRect = block._NodeRect; - tempRect.width = Mathf.Max(Mathf.Max(nodeWidthA, nodeWidthB), 120); - tempRect.height = 40; + Rect tempRect = block._NodeRect; + tempRect.width = Mathf.Max(Mathf.Max(nodeWidthA, nodeWidthB), 120); + tempRect.height = 40; + block._NodeRect = tempRect; - if (dragWindowId > -1 && flowchart.SelectedBlocks.Contains(block)) - { - if (Event.current.type == EventType.MouseDrag) - { - tempRect.x += Event.current.delta.x; - tempRect.y += Event.current.delta.y; + Rect windowRect = new Rect(block._NodeRect); + windowRect.position += flowchart.ScrollPos; - forceRepaintCount = 6; - useEvent = true; - } - else if (Event.current.rawType == EventType.MouseUp) - { - Vector2 newPos = new Vector2(tempRect.x, tempRect.y); - tempRect.x = startDragPosition.x + (newPos.x - blocks[dragWindowId]._NodeRect.position.x); - tempRect.y = startDragPosition.y + (newPos.y - blocks[dragWindowId]._NodeRect.position.y); - - block._NodeRect = tempRect; - - Undo.RecordObject(block, "Node Position"); - - tempRect.x = newPos.x; - tempRect.y = newPos.y; - - forceRepaintCount = 6; - useEvent = true; - endDrag = true; - } - } + // Draw blocks + bool selected = flowchart.SelectedBlocks.Contains(block); - block._NodeRect = tempRect; - } + GUIStyle nodeStyleCopy = new GUIStyle(nodeStyle); + var graphics = GetBlockGraphics(block); - Rect windowRect = new Rect(block._NodeRect); - windowRect.x += flowchart.ScrollPos.x; - windowRect.y += flowchart.ScrollPos.y; + // Make sure node is wide enough to fit the node name text + float width = nodeStyleCopy.CalcSize(new GUIContent(block.BlockName)).x; + tempRect = block._NodeRect; + tempRect.width = Mathf.Max(block._NodeRect.width, width); + block._NodeRect = tempRect; - GUILayout.Window(i, windowRect, DrawWindow, "", windowStyle); + // Draw untinted highlight + if (selected) + { + GUI.backgroundColor = Color.white; + nodeStyleCopy.normal.background = graphics.onTexture; + GUI.Box(windowRect, "", nodeStyleCopy); + } - GUI.backgroundColor = Color.white; + // Draw tinted block; ensure text is readable + var brightness = graphics.tint.r * 0.3 + graphics.tint.g * 0.59 + graphics.tint.b * 0.11; + nodeStyleCopy.normal.textColor = brightness >= 0.5 ? Color.black : Color.white; - windowBlockMap.Add(block); - } + if (GUI.GetNameOfFocusedControl() == searchFieldName && !filteredBlocks.Contains(block)) + { + graphics.tint.a *= 0.2f; + } - dragWindowId = endDrag ? -1 : dragWindowId; + nodeStyleCopy.normal.background = graphics.offTexture; + GUI.backgroundColor = graphics.tint; + GUI.Box(windowRect, block.BlockName, nodeStyleCopy); - if (useEvent) - { - Event.current.Use(); - } + GUI.backgroundColor = Color.white; - EndWindows(); + if (block.Description.Length > 0) + { + GUIStyle descriptionStyle = new GUIStyle(EditorStyles.helpBox); + descriptionStyle.wordWrap = true; + var content = new GUIContent(block.Description); + windowRect.y += windowRect.height; + windowRect.height = descriptionStyle.CalcHeight(content, windowRect.width); + GUI.Label(windowRect, content, descriptionStyle); + } - // Draw Event Handler labels - foreach (var block in blocks) - { + GUI.backgroundColor = Color.white; + + // Draw Event Handler labels if (block._EventHandler != null) { string handlerLabel = ""; @@ -792,13 +988,14 @@ namespace Fungus.EditorUtils // Draw play icons beside all executing blocks if (Application.isPlaying) { - foreach (var b in blocks) + for (int i = 0; i < blocks.Length; ++i) { + var b = blocks[i]; if (b.IsExecuting()) { b.ExecutingIconTimer = Time.realtimeSinceStartup + FungusConstants.ExecutingIconFadeTime; b.ActiveCommand.ExecutingIconTimer = Time.realtimeSinceStartup + FungusConstants.ExecutingIconFadeTime; - forceRepaintCount = 6; + forceRepaintCount = 1; } if (b.ExecutingIconTimer > Time.realtimeSinceStartup) @@ -819,7 +1016,7 @@ namespace Fungus.EditorUtils if (GUI.Button(rect, FungusEditorResources.PlayBig, new GUIStyle())) { - SelectBlock(flowchart, b); + SelectBlock(b); } GUI.color = Color.white; @@ -827,77 +1024,10 @@ namespace Fungus.EditorUtils } } - PanAndZoom(flowchart); - EditorZoomArea.End(); - - // Handle right click up outside of EditorZoomArea to avoid strange offsets - if (Event.current.type == EventType.MouseUp && Event.current.button == 1 && - rightClickDown != -Vector2.one && !mouseOverVariables) - { - var menu = new GenericMenu(); - var mousePosition = rightClickDown; - - Block hitBlock = null; - foreach (var block in blocks) - { - if (block._NodeRect.Contains(rightClickDown / flowchart.Zoom - flowchart.ScrollPos)) - { - hitBlock = block; - break; - } - } - // Clicked on a block - if (hitBlock != null) - { - flowchart.AddSelectedBlock(hitBlock); - - // Use a copy because flowchart.SelectedBlocks gets modified - var blockList = new List(flowchart.SelectedBlocks); - menu.AddItem(new GUIContent ("Copy"), false, () => Copy(flowchart)); - menu.AddItem(new GUIContent ("Cut"), false, () => Cut(flowchart)); - menu.AddItem(new GUIContent ("Duplicate"), false, () => Duplicate(flowchart)); - menu.AddItem(new GUIContent ("Delete"), false, DeleteBlocks, blockList); - } - // Clicked on empty space in grid - else - { - DeselectAll(flowchart); - - menu.AddItem(new GUIContent("Add Block"), false, () => CreateBlock(flowchart, mousePosition / flowchart.Zoom - flowchart.ScrollPos)); - - if (copyList.Count > 0) - { - menu.AddItem(new GUIContent("Paste"), false, () => Paste(flowchart, mousePosition)); - } - else - { - menu.AddDisabledItem(new GUIContent("Paste")); - } - } - - var menuRect = new Rect(); - menuRect.position = new Vector2(mousePosition.x, mousePosition.y - 12f); - menu.DropDown(menuRect); - Event.current.Use(); - } - - // If event has yet to be used and user isn't multiselecting or panning, clear selection - bool validModifier = Event.current.alt || GetAppendModifierDown(); - if (Event.current.type == EventType.MouseDown && Event.current.button == 0 && !validModifier) - { - DeselectAll(flowchart); - } - - // Draw selection box - if (startSelectionBoxPosition.x >= 0 && startSelectionBoxPosition.y >= 0) - { - GUI.Box(selectionBox, "", (GUIStyle) "SelectionRect"); - forceRepaintCount = 6; - } } - public virtual Vector2 GetBlockCenter(Flowchart flowchart, Block[] blocks) + public virtual Vector2 GetBlockCenter(Block[] blocks) { if (blocks.Length == 0) { @@ -907,8 +1037,9 @@ namespace Fungus.EditorUtils Vector2 min = blocks[0]._NodeRect.min; Vector2 max = blocks[0]._NodeRect.max; - foreach (var block in blocks) + for (int i = 0; i < blocks.Length; ++i) { + var block = blocks[i]; min.x = Mathf.Min(min.x, block._NodeRect.center.x); min.y = Mathf.Min(min.y, block._NodeRect.center.y); max.x = Mathf.Max(max.x, block._NodeRect.center.x); @@ -918,147 +1049,20 @@ namespace Fungus.EditorUtils return (min + max) * 0.5f; } - public virtual void CalcFlowchartCenter(Flowchart flowchart, Block[] blocks) - { - var center = -GetBlockCenter(flowchart, blocks); - center.x += position.width * 0.5f / flowchart.Zoom; - center.y += position.height * 0.5f / flowchart.Zoom; - - flowchart.CenterPosition = center; - } - - protected virtual void HandleSelectionBox(Flowchart flowchart) - { - if (Event.current.button == 0 && Event.current.modifiers != EventModifiers.Alt && - !(UnityEditor.Tools.current == Tool.View && UnityEditor.Tools.viewTool == ViewTool.Pan)) - { - switch (Event.current.type) - { - case EventType.MouseDown: - if (!mouseOverVariables) - { - startSelectionBoxPosition = Event.current.mousePosition; - mouseDownSelectionState = new List(flowchart.SelectedBlocks); - Event.current.Use(); - } - break; - - case EventType.MouseDrag: - if (startSelectionBoxPosition.x >= 0 && startSelectionBoxPosition.y >= 0) - { - var topLeft = Vector2.Min(startSelectionBoxPosition, Event.current.mousePosition); - var bottomRight = Vector2.Max(startSelectionBoxPosition, Event.current.mousePosition); - selectionBox = Rect.MinMaxRect(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y); - - Rect zoomSelectionBox = selectionBox; - zoomSelectionBox.position -= flowchart.ScrollPos * flowchart.Zoom; - zoomSelectionBox.position /= flowchart.Zoom; - zoomSelectionBox.size /= flowchart.Zoom; - - foreach (var block in flowchart.GetComponents()) - { - if (zoomSelectionBox.Overlaps(block._NodeRect)) - { - if (mouseDownSelectionState.Contains(block)) - { - flowchart.SelectedBlocks.Remove(block); - } - else - { - flowchart.AddSelectedBlock(block); - } - } - else if (mouseDownSelectionState.Contains(block)) - { - flowchart.AddSelectedBlock(block); - } - else - { - flowchart.SelectedBlocks.Remove(block); - } - } - } - Event.current.Use(); - break; - } - - if (Event.current.rawType == EventType.MouseUp) - { - selectionBox.size = Vector2.zero; - selectionBox.position = -Vector2.one; - startSelectionBoxPosition = selectionBox.position; - - var tempList = new List(flowchart.SelectedBlocks); - flowchart.SelectedBlocks = mouseDownSelectionState; - Undo.RecordObject(flowchart, "Select"); - flowchart.SelectedBlocks = tempList; - - if (flowchart.SelectedBlock != null) - { - SetBlockForInspector(flowchart, flowchart.SelectedBlock); - } - } - } - } - - protected virtual void PanAndZoom(Flowchart flowchart) + protected virtual void CenterFlowchart() { - // Right click to drag view - bool drag = false; - - // Pan tool - if (UnityEditor.Tools.current == Tool.View && UnityEditor.Tools.viewTool == ViewTool.Pan && - Event.current.button == 0 && Event.current.type == EventType.MouseDrag) - { - drag = true; - } - - // Right or middle button drag - if (Event.current.button > 0 && Event.current.type == EventType.MouseDrag) - { - drag = true; - } - - // Alt + left mouse drag - if (Event.current.alt && - Event.current.button == 0 && Event.current.type == EventType.MouseDrag) - { - drag = true; - } - - if (drag) - { - flowchart.ScrollPos += Event.current.delta; - forceRepaintCount = 6; - } - - bool zoom = false; - - // Scroll wheel - if (Event.current.type == EventType.ScrollWheel && !mouseOverPopup) - { - zoom = true; - } - - // Zoom tool - if (UnityEditor.Tools.current == Tool.View && UnityEditor.Tools.viewTool == ViewTool.Zoom && - Event.current.button == 0 && Event.current.type == EventType.MouseDrag) + if (blocks.Length > 0) { - zoom = true; - } - - if (zoom && selectionBox.size == Vector2.zero) - { - Vector2 zoomCenter; - zoomCenter.x = Event.current.mousePosition.x / position.width; - zoomCenter.y = Event.current.mousePosition.y / position.height; - zoomCenter *= flowchart.Zoom; + var center = -GetBlockCenter(blocks); + center.x += position.width * 0.5f / flowchart.Zoom; + center.y += position.height * 0.5f / flowchart.Zoom; - DoZoom(flowchart, -Event.current.delta.y * 0.01f, zoomCenter); + flowchart.CenterPosition = center; + flowchart.ScrollPos = flowchart.CenterPosition; } } - protected virtual void DoZoom(Flowchart flowchart, float delta, Vector2 center) + protected virtual void DoZoom(float delta, Vector2 center) { var prevZoom = flowchart.Zoom; flowchart.Zoom += delta; @@ -1066,10 +1070,10 @@ namespace Fungus.EditorUtils var deltaSize = position.size / prevZoom - position.size / flowchart.Zoom; var offset = -Vector2.Scale(deltaSize, center); flowchart.ScrollPos += offset; - forceRepaintCount = 6; + forceRepaintCount = 1; } - protected virtual void DrawGrid(Flowchart flowchart) + protected virtual void DrawGrid() { float width = this.position.width / flowchart.Zoom; float height = this.position.height / flowchart.Zoom; @@ -1098,14 +1102,14 @@ namespace Fungus.EditorUtils Handles.color = Color.white; } - protected virtual void SelectBlock(Flowchart flowchart, Block block) + protected virtual void SelectBlock(Block block) { // Select the block and also select currently executing command flowchart.SelectedBlock = block; SetBlockForInspector(flowchart, block); } - protected virtual void DeselectAll(Flowchart flowchart) + protected virtual void DeselectAll() { Undo.RecordObject(flowchart, "Deselect"); flowchart.ClearSelectedCommands(); @@ -1125,117 +1129,7 @@ namespace Fungus.EditorUtils return newBlock; } - protected virtual void DrawWindow(int windowId) - { - var block = windowBlockMap[windowId]; - var flowchart = (Flowchart)block.GetFlowchart(); - - if (flowchart == null) - { - return; - } - - // Select block when node is clicked - if (Event.current.button == 0 && - Event.current.type == EventType.MouseDown && - !mouseOverVariables) - { - // Check if might be start of a window drag - if (Event.current.button == 0 && - Event.current.alt == false) - { - if (!GetAppendModifierDown()) - { - dragWindowId = windowId; - - startDragPosition.x = block._NodeRect.x; - startDragPosition.y = block._NodeRect.y; - } - - Event.current.Use(); - } - - if (windowId < windowBlockMap.Count) - { - Undo.RecordObject(flowchart, "Select"); - - if (GetAppendModifierDown()) - { - if (flowchart.SelectedBlocks.Contains(block)) - { - flowchart.SelectedBlocks.Remove(block); - } - else - { - flowchart.AddSelectedBlock(block); - } - } - else - { - if (flowchart.SelectedBlocks.Contains(block)) - { - SetBlockForInspector(flowchart, block); - } - else - { - SelectBlock(flowchart, block); - } - } - - GUIUtility.keyboardControl = 0; // Fix for textarea not refeshing (change focus) - } - } - - bool selected = false; - if (flowchart.SelectedBlocks.Contains(block)) - { - selected = true; - } - - GUIStyle nodeStyleCopy = new GUIStyle(nodeStyle); - var graphics = GetBlockGraphics(block); - - // Make sure node is wide enough to fit the node name text - var n = block as Node; - float width = nodeStyleCopy.CalcSize(new GUIContent(block.BlockName)).x; - Rect tempRect = n._NodeRect; - tempRect.width = Mathf.Max (n._NodeRect.width, width); - n._NodeRect = tempRect; - - Rect boxRect = GUILayoutUtility.GetRect(n._NodeRect.width, n._NodeRect.height); - - // Draw untinted highlight - if (selected) - { - GUI.backgroundColor = Color.white; - nodeStyleCopy.normal.background = graphics.onTexture; - GUI.Box(boxRect, "", nodeStyleCopy); - } - - // Draw tinted block; ensure text is readable - var brightness = graphics.tint.r * 0.3 + graphics.tint.g * 0.59 + graphics.tint.b * 0.11; - nodeStyleCopy.normal.textColor = brightness >= 0.5 ? Color.black : Color.white; - - if (GUI.GetNameOfFocusedControl() == searchFieldName && !filteredBlocks.Contains(block)) - { - graphics.tint.a *= 0.2f; - } - - nodeStyleCopy.normal.background = graphics.offTexture; - GUI.backgroundColor = graphics.tint; - GUI.Box(boxRect, block.BlockName, nodeStyleCopy); - - GUI.backgroundColor = Color.white; - - if (block.Description.Length > 0) - { - GUIStyle descriptionStyle = new GUIStyle(EditorStyles.helpBox); - descriptionStyle.wordWrap = true; - GUILayout.Label(block.Description, descriptionStyle); - } - } - - protected virtual void DrawConnections(Flowchart flowchart, Block block, bool highlightedOnly) + protected virtual void DrawConnections(Block block, bool highlightedOnly) { if (block == null) { @@ -1382,10 +1276,47 @@ namespace Fungus.EditorUtils return rt * rt * rt * s + 3 * rt * rtt * st + 3 * rtt * t * et + t * t * t * e; } - public static void DeleteBlocks(object obj) + protected void AddToDeleteList(List blocks) { - var blocks = obj as List; - blocks.ForEach(block => FlowchartWindow.deleteList.Add(block)); + for (int i = 0; i < blocks.Count; ++i) + { + FlowchartWindow.deleteList.Add(blocks[i]); + } + } + + public void DeleteBlocks() + { + // Delete any scheduled objects + for (int i = 0; i < deleteList.Count; ++i) + { + var deleteBlock = deleteList[i]; + bool isSelected = (flowchart.SelectedBlocks.Contains(deleteBlock)); + + var commandList = deleteBlock.CommandList; + for (int j = 0; j < commandList.Count; ++j) + { + Undo.DestroyObjectImmediate(commandList[j]); + } + + if (deleteBlock._EventHandler != null) + { + Undo.DestroyObjectImmediate(deleteBlock._EventHandler); + } + + Undo.DestroyObjectImmediate(deleteBlock); + flowchart.ClearSelectedCommands(); + + if (isSelected) + { + // Deselect + flowchart.SelectedBlocks.Remove(deleteBlock); + + // Revert to showing properties for the Flowchart + Selection.activeGameObject = flowchart.gameObject; + } + } + + deleteList.Clear(); } protected static void ShowBlockInspector(Flowchart flowchart) @@ -1430,7 +1361,7 @@ namespace Fungus.EditorUtils return Event.current.shift || EditorGUI.actionKey; } - protected virtual void Copy(Flowchart flowchart) + protected virtual void Copy() { copyList.Clear(); @@ -1440,18 +1371,18 @@ namespace Fungus.EditorUtils } } - protected virtual void Cut(Flowchart flowchart) + protected virtual void Cut() { - Copy(flowchart); + Copy(); Undo.RecordObject(flowchart, "Cut"); - DeleteBlocks(flowchart.SelectedBlocks); + AddToDeleteList(flowchart.SelectedBlocks); } // Center is position in unscaled window space - protected virtual void Paste(Flowchart flowchart, Vector2 center, bool relative = false) + protected virtual void Paste(Vector2 center, bool relative = false) { Undo.RecordObject(flowchart, "Deselect"); - DeselectAll(flowchart); + DeselectAll(); var pasteList = new List(); @@ -1460,7 +1391,7 @@ namespace Fungus.EditorUtils pasteList.Add(copy.PasteBlock(flowchart)); } - var copiedCenter = GetBlockCenter(flowchart, pasteList.ToArray()) + flowchart.ScrollPos; + var copiedCenter = GetBlockCenter(pasteList.ToArray()) + flowchart.ScrollPos; var delta = relative ? center : (center / flowchart.Zoom - copiedCenter); foreach (var block in pasteList) @@ -1471,95 +1402,93 @@ namespace Fungus.EditorUtils } } - protected virtual void Duplicate(Flowchart flowchart) + protected virtual void Duplicate() { var tempCopyList = new List(copyList); - Copy(flowchart); - Paste(flowchart, new Vector2(20, 0), true); + Copy(); + Paste(new Vector2(20, 0), true); copyList = tempCopyList; } - protected virtual void ValidateCommands(Flowchart flowchart) + protected override void OnValidateCommand(Event e) { - if (Event.current.type == EventType.ValidateCommand) + if (e.type == EventType.ValidateCommand) { - var c = Event.current.commandName; + var c = e.commandName; if (c == "Copy" || c == "Cut" || c == "Delete" || c == "Duplicate") { if (flowchart.SelectedBlocks.Count > 0) { - Event.current.Use(); + e.Use(); } } else if (c == "Paste") { if (copyList.Count > 0) { - Event.current.Use(); + e.Use(); } } else if (c == "SelectAll" || c == "Find") { - Event.current.Use(); + e.Use(); } } } - protected virtual void ExecuteCommands(Flowchart flowchart) + protected override void OnExecuteCommand(Event e) { - if (Event.current.type == EventType.ExecuteCommand) + switch (e.commandName) { - switch (Event.current.commandName) - { - case "Copy": - Copy(flowchart); - Event.current.Use(); - break; - - case "Cut": - Cut(flowchart); - Event.current.Use(); - break; - - case "Paste": - Paste(flowchart, position.center - position.position); - Event.current.Use(); - break; - - case "Delete": - DeleteBlocks(flowchart.SelectedBlocks); - Event.current.Use(); - break; - - case "Duplicate": - Duplicate(flowchart); - Event.current.Use(); - break; - - case "SelectAll": - Undo.RecordObject(flowchart, "Selection"); - flowchart.ClearSelectedBlocks(); - foreach (var block in flowchart.GetComponents()) - { - flowchart.AddSelectedBlock(block); - } - Event.current.Use(); - break; + case "Copy": + Copy(); + e.Use(); + break; + + case "Cut": + Cut(); + e.Use(); + break; - case "Find": - blockPopupSelection = 0; - EditorGUI.FocusTextInControl(searchFieldName); - Event.current.Use(); - break; + case "Paste": + Paste(position.center - position.position); + e.Use(); + break; + + case "Delete": + AddToDeleteList(flowchart.SelectedBlocks); + e.Use(); + break; + + case "Duplicate": + Duplicate(); + e.Use(); + break; + + case "SelectAll": + Undo.RecordObject(flowchart, "Selection"); + flowchart.ClearSelectedBlocks(); + for (int i = 0; i < blocks.Length; ++i) + { + flowchart.AddSelectedBlock(blocks[i]); } + e.Use(); + break; + + case "Find": + blockPopupSelection = 0; + popupScroll = Vector2.zero; + EditorGUI.FocusTextInControl(searchFieldName); + e.Use(); + break; } } - protected virtual void CenterBlock(Flowchart flowchart, Block block) + protected virtual void CenterBlock(Block block) { if (flowchart.Zoom < 1) { - DoZoom(flowchart, 1 - flowchart.Zoom, Vector2.one * 0.5f); + DoZoom(1 - flowchart.Zoom, Vector2.one * 0.5f); } flowchart.ScrollPos = -block._NodeRect.center + position.size * 0.5f / flowchart.Zoom;