diff --git a/Assets/Fungus/Scripts/Components/Block.cs b/Assets/Fungus/Scripts/Components/Block.cs index d38aa89a..40d97dde 100644 --- a/Assets/Fungus/Scripts/Components/Block.cs +++ b/Assets/Fungus/Scripts/Components/Block.cs @@ -375,6 +375,12 @@ namespace Fungus public virtual List GetConnectedBlocks() { var connectedBlocks = new List(); + GetConnectedBlocks(ref connectedBlocks); + return connectedBlocks; + } + + public virtual void GetConnectedBlocks(ref List connectedBlocks) + { for (int i = 0; i < commandList.Count; i++) { var command = commandList[i]; @@ -383,7 +389,6 @@ namespace Fungus command.GetConnectedBlocks(ref connectedBlocks); } } - return connectedBlocks; } /// diff --git a/Assets/Fungus/Scripts/Editor/EditorZoomArea.cs b/Assets/Fungus/Scripts/Editor/EditorZoomArea.cs index 4491006a..1a9155ce 100644 --- a/Assets/Fungus/Scripts/Editor/EditorZoomArea.cs +++ b/Assets/Fungus/Scripts/Editor/EditorZoomArea.cs @@ -52,6 +52,22 @@ namespace Fungus.EditorUtils result.y += pivotPoint.y; return result; } + + public static Rect SnapPosition(this Rect rect, float snapInterval) + { + var tmp = rect; + var x = tmp.position.x; + var y = tmp.position.y; + tmp.position = new Vector2(Mathf.RoundToInt(x / snapInterval) * snapInterval, Mathf.RoundToInt(y / snapInterval) * snapInterval); + return tmp; + } + + public static Rect SnapWidth(this Rect rect, float snapInterval) + { + var tmp = rect; + tmp.width = Mathf.RoundToInt(tmp.width / snapInterval) * snapInterval; + return tmp; + } } public class EditorZoomArea diff --git a/Assets/Fungus/Scripts/Editor/FlowchartWindow.cs b/Assets/Fungus/Scripts/Editor/FlowchartWindow.cs index ca308f26..b3efbadb 100644 --- a/Assets/Fungus/Scripts/Editor/FlowchartWindow.cs +++ b/Assets/Fungus/Scripts/Editor/FlowchartWindow.cs @@ -172,12 +172,29 @@ namespace Fungus.EditorUtils } } + + + public const float GridLineSpacingSize = 120; + public const float GridObjectSnap = 20; + public const float DefaultBlockHeight = 40; + public const float BlockMinWidth = 60; + public const float BlockMaxWidth = 240; + public const float MinZoomValue = 0.25f; + public const float MaxZoomValue = 1f; + public const int HorizontalPad = 20; + public const int VerticalPad = 5; + //defines the distance between a down and up for a right click to be a click rather than a drag + public const float RightClickTolerance = 5f; + public const string SearchFieldName = "search"; + + + protected readonly Color connectionColor = new Color(0.65f, 0.65f, 0.65f, 1.0f); + + protected List copyList = new List(); public static List deleteList = new List(); protected Vector2 startDragPosition; - public const float minZoomValue = 0.25f; - public const float maxZoomValue = 1f; - protected GUIStyle nodeStyle = new GUIStyle(); + protected GUIStyle nodeStyle, descriptionStyle, handlerStyle; protected static BlockInspector blockInspector; protected int forceRepaintCount; protected Texture2D addTexture; @@ -187,12 +204,9 @@ namespace Fungus.EditorUtils protected Vector2 startSelectionBoxPosition = -Vector2.one; protected List mouseDownSelectionState = new List(); protected Color gridLineColor = Color.black; - protected readonly Color connectionColor = new Color(0.65f, 0.65f, 0.65f, 1.0f); // Context Click occurs on MouseDown which interferes with panning // Track right click positions manually to show menus on MouseUp protected Vector2 rightClickDown = -Vector2.one; - protected const float rightClickTolerance = 5f; - protected const string searchFieldName = "search"; private string searchString = string.Empty; protected Rect searchRect; protected Rect popupRect; @@ -245,13 +259,6 @@ namespace Fungus.EditorUtils protected virtual void OnEnable() { - // All block nodes use the same GUIStyle, but with a different background - nodeStyle.border = new RectOffset(20, 20, 5, 5); - nodeStyle.padding = nodeStyle.border; - nodeStyle.contentOffset = Vector2.zero; - nodeStyle.alignment = TextAnchor.MiddleCenter; - nodeStyle.wordWrap = true; - addTexture = FungusEditorResources.AddSmall; addButtonContent = new GUIContent(addTexture, "Add a new block"); connectionPointTexture = FungusEditorResources.ConnectionPoint; @@ -272,6 +279,37 @@ namespace Fungus.EditorUtils #endif } + //cache styles here, rather than duping them for every block we may ever draw, + // does mean any modifications made to the style when drawing must be undone as you go + protected void InitStyles() + { + if (nodeStyle == null) + { + nodeStyle = new GUIStyle(); + // All block nodes use the same GUIStyle, but with a different background + nodeStyle.border = new RectOffset(HorizontalPad, HorizontalPad, VerticalPad, VerticalPad); + nodeStyle.padding = nodeStyle.border; + nodeStyle.contentOffset = Vector2.zero; + nodeStyle.alignment = TextAnchor.MiddleCenter; + nodeStyle.wordWrap = true; + } + + if (EditorStyles.helpBox != null && descriptionStyle == null) + { + descriptionStyle = new GUIStyle(EditorStyles.helpBox); + descriptionStyle.wordWrap = true; + } + + if (EditorStyles.whiteLabel != null && handlerStyle == null) + { + handlerStyle = new GUIStyle(EditorStyles.whiteLabel); + handlerStyle.wordWrap = true; + handlerStyle.margin.top = 0; + handlerStyle.margin.bottom = 0; + handlerStyle.alignment = TextAnchor.MiddleCenter; + } + } + protected virtual void OnDisable() { EditorApplication.update -= OnEditorUpdate; @@ -530,7 +568,7 @@ namespace Fungus.EditorUtils break; case EventType.KeyDown: - if (GUI.GetNameOfFocusedControl() == searchFieldName) + if (GUI.GetNameOfFocusedControl() == SearchFieldName) { var centerBlock = false; var selectBlock = false; @@ -715,6 +753,8 @@ namespace Fungus.EditorUtils return; } + InitStyles(); + DeleteBlocks(); UpdateFilteredBlocks(); @@ -787,7 +827,7 @@ namespace Fungus.EditorUtils // Draw scale bar and labels GUILayout.Label("Scale", EditorStyles.miniLabel); var newZoom = GUILayout.HorizontalSlider( - flowchart.Zoom, minZoomValue, maxZoomValue, GUILayout.MinWidth(40), GUILayout.MaxWidth(100) + flowchart.Zoom, MinZoomValue, MaxZoomValue, GUILayout.MinWidth(40), GUILayout.MaxWidth(100) ); GUILayout.Label(flowchart.Zoom.ToString("0.0#x"), EditorStyles.miniLabel, GUILayout.Width(30)); @@ -805,7 +845,7 @@ namespace Fungus.EditorUtils GUILayout.FlexibleSpace(); // Draw search bar - GUI.SetNextControlName(searchFieldName); + GUI.SetNextControlName(SearchFieldName); var newString = EditorGUILayout.TextField(searchString, ToolbarSeachTextFieldStyle, GUILayout.Width(150)); if (newString != searchString) { @@ -861,7 +901,7 @@ namespace Fungus.EditorUtils // Draw block search popup on top of other controls - if (GUI.GetNameOfFocusedControl() == searchFieldName && filteredBlocks.Length > 0) + if (GUI.GetNameOfFocusedControl() == SearchFieldName && filteredBlocks.Length > 0) { DrawBlockPopup(e); } @@ -1152,7 +1192,7 @@ namespace Fungus.EditorUtils break; case MouseButton.Right: - if (Vector2.Distance(rightClickDown, e.mousePosition) > rightClickTolerance) + if (Vector2.Distance(rightClickDown, e.mousePosition) > RightClickTolerance) { rightClickDown = -Vector2.one; } @@ -1207,6 +1247,11 @@ namespace Fungus.EditorUtils Undo.RecordObject(block, "Block Position"); tempRect.position += distance; block._NodeRect = tempRect; + if (FungusEditorPreferences.useGridSnap) + { + block._NodeRect = block._NodeRect.SnapPosition(GridObjectSnap); + } + Repaint(); } dragBlock = null; @@ -1345,12 +1390,6 @@ namespace Fungus.EditorUtils { DrawGrid(); - // Draw connections - foreach (var block in blocks) - { - DrawConnections(block); - } - //draw all non selected for (int i = 0; i < blocks.Length; ++i) { @@ -1469,13 +1508,14 @@ namespace Fungus.EditorUtils { var prevZoom = flowchart.Zoom; flowchart.Zoom += delta; - flowchart.Zoom = Mathf.Clamp(flowchart.Zoom, minZoomValue, maxZoomValue); + flowchart.Zoom = Mathf.Clamp(flowchart.Zoom, MinZoomValue, MaxZoomValue); var deltaSize = position.size / prevZoom - position.size / flowchart.Zoom; var offset = -Vector2.Scale(deltaSize, center); flowchart.ScrollPos += offset; forceRepaintCount = 1; } + //Potentially could be faster using https://forum.unity.com/threads/how-do-i-access-the-background-image-used-for-the-animator.501876/ protected virtual void DrawGrid() { float width = this.position.width / flowchart.Zoom; @@ -1483,23 +1523,22 @@ namespace Fungus.EditorUtils Handles.color = gridLineColor; - float gridSize = 128f; - float x = flowchart.ScrollPos.x % gridSize; + float x = flowchart.ScrollPos.x % GridLineSpacingSize; while (x < width) { Handles.DrawLine(new Vector2(x, 0), new Vector2(x, height)); - x += gridSize; + x += GridLineSpacingSize; } - float y = (flowchart.ScrollPos.y % gridSize); + float y = (flowchart.ScrollPos.y % GridLineSpacingSize); while (y < height) { if (y >= 0) { Handles.DrawLine(new Vector2(0, y), new Vector2(width, y)); } - y += gridSize; + y += GridLineSpacingSize; } Handles.color = Color.white; @@ -1543,6 +1582,9 @@ namespace Fungus.EditorUtils return newBlock; } + //prevent every DrawConnections from allocating a new list for all of its connections + protected List connectedBlocksWorkSpace = new List(); + protected virtual void DrawConnections(Block block) { if (block == null) @@ -1550,7 +1592,6 @@ namespace Fungus.EditorUtils return; } - var connectedBlocks = new List(); bool blockIsSelected = flowchart.SelectedBlock == block; @@ -1578,10 +1619,10 @@ namespace Fungus.EditorUtils bool highlight = command.IsExecuting || (blockIsSelected && commandIsSelected); - connectedBlocks.Clear(); - command.GetConnectedBlocks(ref connectedBlocks); + connectedBlocksWorkSpace.Clear(); + command.GetConnectedBlocks(ref connectedBlocksWorkSpace); - foreach (var blockB in connectedBlocks) + foreach (var blockB in connectedBlocksWorkSpace) { if (blockB == null || block == blockB || @@ -1945,7 +1986,7 @@ namespace Fungus.EditorUtils case "Find": blockPopupSelection = 0; popupScroll = Vector2.zero; - EditorGUI.FocusTextInControl(searchFieldName); + EditorGUI.FocusTextInControl(SearchFieldName); e.Use(); break; } @@ -1967,10 +2008,14 @@ namespace Fungus.EditorUtils searchString = string.Empty; } + static protected List blockGraphicsUniqueListWorkSpace = new List(); + static protected List blockGraphicsConnectedWorkSpace = new List(); protected virtual BlockGraphics GetBlockGraphics(Block block) { var graphics = new BlockGraphics(); + blockGraphicsUniqueListWorkSpace.Clear(); + blockGraphicsConnectedWorkSpace.Clear(); Color defaultTint; if (block._EventHandler != null) { @@ -1981,19 +2026,18 @@ namespace Fungus.EditorUtils else { // Count the number of unique connections (excluding self references) - var uniqueList = new List(); - var connectedBlocks = block.GetConnectedBlocks(); - foreach (var connectedBlock in connectedBlocks) + block.GetConnectedBlocks(ref blockGraphicsConnectedWorkSpace); + foreach (var connectedBlock in blockGraphicsConnectedWorkSpace) { if (connectedBlock == block || - uniqueList.Contains(connectedBlock)) + blockGraphicsUniqueListWorkSpace.Contains(connectedBlock)) { continue; } - uniqueList.Add(connectedBlock); + blockGraphicsUniqueListWorkSpace.Add(connectedBlock); } - if (uniqueList.Count > 1) + if (blockGraphicsUniqueListWorkSpace.Count > 1) { graphics.offTexture = FungusEditorResources.ChoiceNodeOff; graphics.onTexture = FungusEditorResources.ChoiceNodeOn; @@ -2015,79 +2059,81 @@ namespace Fungus.EditorUtils private void DrawBlock(Block block, Rect scriptViewRect) { float nodeWidthA = nodeStyle.CalcSize(new GUIContent(block.BlockName)).x + 10; - float nodeWidthB = 0f; - if (block._EventHandler != null) + + Rect tempRect = block._NodeRect; + tempRect.width = Mathf.Clamp(nodeWidthA, BlockMinWidth, BlockMaxWidth); + tempRect.height = DefaultBlockHeight; + if (FungusEditorPreferences.useGridSnap) { - nodeWidthB = nodeStyle.CalcSize(new GUIContent(block._EventHandler.GetSummary())).x + 10; + tempRect = tempRect.SnapWidth(GridObjectSnap); } - - Rect tempRect = block._NodeRect; - tempRect.width = Mathf.Max(Mathf.Max(nodeWidthA, nodeWidthB), 120); - tempRect.height = 40; block._NodeRect = tempRect; - Rect windowRect = new Rect(block._NodeRect); - windowRect.position += flowchart.ScrollPos; + // Draw blocks + var graphics = GetBlockGraphics(block); + + Rect windowRelativeRect = new Rect(block._NodeRect); + if (FungusEditorPreferences.useGridSnap) + { + windowRelativeRect = windowRelativeRect.SnapPosition(GridObjectSnap); + } + windowRelativeRect.position += flowchart.ScrollPos; //skip if outside of view - if (!scriptViewRect.Overlaps(windowRect)) + if (!scriptViewRect.Overlaps(windowRelativeRect)) return; - // Draw blocks - GUIStyle nodeStyleCopy = new GUIStyle(nodeStyle); - var graphics = GetBlockGraphics(block); - - // 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; + var tmpNormBg = nodeStyle.normal.background; // Draw untinted highlight if (block.IsSelected && !block.IsControlSelected) { GUI.backgroundColor = Color.white; - nodeStyleCopy.normal.background = graphics.onTexture; - GUI.Box(windowRect, "", nodeStyleCopy); + nodeStyle.normal.background = graphics.onTexture; + GUI.Box(windowRelativeRect, "", nodeStyle); + nodeStyle.normal.background = tmpNormBg; } if (block.IsControlSelected && !block.IsSelected) { GUI.backgroundColor = Color.white; - nodeStyleCopy.normal.background = graphics.onTexture; + nodeStyle.normal.background = graphics.onTexture; var c = GUI.backgroundColor; c.a = 0.5f; GUI.backgroundColor = c; - GUI.Box(windowRect, "", nodeStyleCopy); + GUI.Box(windowRelativeRect, "", nodeStyle); + nodeStyle.normal.background = tmpNormBg; } // 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; + var tmpNormTxtCol = nodeStyle.normal.textColor; + nodeStyle.normal.textColor = brightness >= 0.5 ? Color.black : Color.white; - if (GUI.GetNameOfFocusedControl() == searchFieldName && !block.IsFiltered) + if (GUI.GetNameOfFocusedControl() == SearchFieldName && !block.IsFiltered) { graphics.tint.a *= 0.2f; } - nodeStyleCopy.normal.background = graphics.offTexture; + nodeStyle.normal.background = graphics.offTexture; GUI.backgroundColor = graphics.tint; - GUI.Box(windowRect, block.BlockName, nodeStyleCopy); + GUI.Box(windowRelativeRect, block.BlockName, nodeStyle); GUI.backgroundColor = Color.white; 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); + windowRelativeRect.y += windowRelativeRect.height; + windowRelativeRect.height = descriptionStyle.CalcHeight(content, windowRelativeRect.width); + GUI.Label(windowRelativeRect, content, descriptionStyle); } GUI.backgroundColor = Color.white; + nodeStyle.normal.textColor = tmpNormTxtCol; + nodeStyle.normal.background = tmpNormBg; + // Draw Event Handler labels if (block._EventHandler != null) { @@ -2097,13 +2143,7 @@ namespace Fungus.EditorUtils { handlerLabel = "<" + info.EventHandlerName + "> "; } - - GUIStyle handlerStyle = new GUIStyle(EditorStyles.whiteLabel); - handlerStyle.wordWrap = true; - handlerStyle.margin.top = 0; - handlerStyle.margin.bottom = 0; - handlerStyle.alignment = TextAnchor.MiddleCenter; - + Rect rect = new Rect(block._NodeRect); rect.height = handlerStyle.CalcHeight(new GUIContent(handlerLabel), block._NodeRect.width); rect.x += flowchart.ScrollPos.x; @@ -2111,6 +2151,9 @@ namespace Fungus.EditorUtils GUI.Label(rect, handlerLabel, handlerStyle); } + + + DrawConnections(block); } } } \ No newline at end of file diff --git a/Assets/Fungus/Scripts/Editor/FungusEditorPreferences.cs b/Assets/Fungus/Scripts/Editor/FungusEditorPreferences.cs index da3fe24f..48c910b1 100644 --- a/Assets/Fungus/Scripts/Editor/FungusEditorPreferences.cs +++ b/Assets/Fungus/Scripts/Editor/FungusEditorPreferences.cs @@ -21,9 +21,11 @@ namespace Fungus private static bool prefsLoaded = false; private const string HIDE_MUSH_KEY = "hideMushroomInHierarchy"; private const string USE_LEGACY_MENUS = "useLegacyMenus"; + private const string USE_GRID_SNAP = "useGridSnap"; public static bool hideMushroomInHierarchy; public static bool useLegacyMenus; + public static bool useGridSnap; static FungusEditorPreferences() { @@ -63,6 +65,7 @@ namespace Fungus // Preferences GUI hideMushroomInHierarchy = EditorGUILayout.Toggle("Hide Mushroom Flowchart Icon", hideMushroomInHierarchy); useLegacyMenus = EditorGUILayout.Toggle(new GUIContent("Legacy Menus", "Force Legacy menus for Event, Add Variable and Add Command menus"), useLegacyMenus); + useGridSnap = EditorGUILayout.Toggle(new GUIContent("Grid Snap", "Align and Snap block positions and widths in the flowchart window to the grid"), useGridSnap); EditorGUILayout.Space(); //ideally if any are null, but typically it is all or nothing that have broken links due to version changes or moving files external to Unity @@ -113,6 +116,7 @@ namespace Fungus { EditorPrefs.SetBool(HIDE_MUSH_KEY, hideMushroomInHierarchy); EditorPrefs.SetBool(USE_LEGACY_MENUS, useLegacyMenus); + EditorPrefs.SetBool(USE_GRID_SNAP, useGridSnap); } } @@ -120,6 +124,7 @@ namespace Fungus { hideMushroomInHierarchy = EditorPrefs.GetBool(HIDE_MUSH_KEY, false); useLegacyMenus = EditorPrefs.GetBool(USE_LEGACY_MENUS, false); + useGridSnap = EditorPrefs.GetBool(USE_GRID_SNAP, false); prefsLoaded = true; } }