diff --git a/Assets/Fungus/EditorResources/FungusEditorResources.asset b/Assets/Fungus/EditorResources/FungusEditorResources.asset index 594b27ed..05ea3abc 100644 --- a/Assets/Fungus/EditorResources/FungusEditorResources.asset +++ b/Assets/Fungus/EditorResources/FungusEditorResources.asset @@ -30,6 +30,9 @@ MonoBehaviour: up: free: {fileID: 2800000, guid: 8f1242ad894201f43b2b6d52fd990f77, type: 3} pro: {fileID: 2800000, guid: 2a76a781db2994b33b83cd84b8835da7, type: 3} + bullet_point: + free: {fileID: 2800000, guid: 4ef739c68bb234717a60a2bb83ff8602, type: 3} + pro: {fileID: 0} choice_node_off: free: {fileID: 2800000, guid: 7b6fc04aac74540e39e9502da5312ce7, type: 3} pro: {fileID: 0} diff --git a/Assets/Fungus/EditorResources/Textures/bullet_point.png b/Assets/Fungus/EditorResources/Textures/bullet_point.png new file mode 100644 index 00000000..bcafc733 Binary files /dev/null and b/Assets/Fungus/EditorResources/Textures/bullet_point.png differ diff --git a/Assets/Fungus/EditorResources/Textures/bullet_point.png.meta b/Assets/Fungus/EditorResources/Textures/bullet_point.png.meta new file mode 100644 index 00000000..300a162f --- /dev/null +++ b/Assets/Fungus/EditorResources/Textures/bullet_point.png.meta @@ -0,0 +1,55 @@ +fileFormatVersion: 2 +guid: 4ef739c68bb234717a60a2bb83ff8602 +timeCreated: 1479950896 +licenseType: Pro +TextureImporter: + fileIDToRecycleName: {} + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + linearTexture: 1 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + cubemapConvolution: 0 + cubemapConvolutionSteps: 7 + cubemapConvolutionExponent: 1.5 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 2048 + textureSettings: + filterMode: 0 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + rGBM: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: .5, y: .5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaIsTransparency: 1 + textureType: 2 + buildTargetSettings: [] + spriteSheet: + sprites: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fungus/Scripts/Editor/FlowchartWindow.cs b/Assets/Fungus/Scripts/Editor/FlowchartWindow.cs index f7d3b90b..11050ce3 100644 --- a/Assets/Fungus/Scripts/Editor/FlowchartWindow.cs +++ b/Assets/Fungus/Scripts/Editor/FlowchartWindow.cs @@ -97,6 +97,13 @@ namespace Fungus.EditorUtils } } + protected struct BlockGraphics + { + internal Color tint; + internal Texture2D onTexture; + internal Texture2D offTexture; + } + protected List copyList = new List(); public static List deleteList = new List(); @@ -131,8 +138,17 @@ namespace Fungus.EditorUtils // 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 readonly float rightClickTolerance = 5f; - + protected const float rightClickTolerance = 5f; + + protected const string searchFieldName = "search"; + private string searchString = string.Empty; + protected Rect searchRect; + protected Rect popupRect; + protected Block[] filteredBlocks; + protected int blockPopupSelection = -1; + protected Vector2 popupScroll; + protected bool mouseOverPopup; + [MenuItem("Tools/Fungus/Flowchart Window")] static void Init() { @@ -159,6 +175,8 @@ namespace Fungus.EditorUtils gridLineColor.a = EditorGUIUtility.isProSkin ? 0.5f : 0.25f; copyList.Clear(); + + wantsMouseMove = true; // For hover selection in block search popup } protected virtual void OnInspectorUpdate() @@ -268,6 +286,22 @@ namespace Fungus.EditorUtils } deleteList.Clear(); + // Clear search filter focus + if (Event.current.type == EventType.MouseDown && !searchRect.Contains(Event.current.mousePosition) && + !popupRect.Contains(Event.current.mousePosition)) + { + CloseBlockPopup(); + } + + if (Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Escape) + { + if (GUI.GetNameOfFocusedControl() != searchFieldName && flowchart.SelectedBlocks.Count > 0) + { + DeselectAll(flowchart); + Event.current.Use(); + } + } + DrawFlowchartView(flowchart); DrawOverlay(flowchart); @@ -318,6 +352,100 @@ namespace Fungus.EditorUtils GUILayout.FlexibleSpace(); + var blocks = flowchart.GetComponents(); + + // Intercept mouse and keyboard events before search field uses them + if (GUI.GetNameOfFocusedControl() == searchFieldName) + { + if (Event.current.type == EventType.KeyDown) + { + var centerBlock = false; + var selectBlock = false; + var closePopup = false; + var useEvent = false; + + switch (Event.current.keyCode) + { + case KeyCode.DownArrow: + ++blockPopupSelection; + centerBlock = true; + useEvent = true; + break; + + case KeyCode.UpArrow: + --blockPopupSelection; + centerBlock = true; + useEvent = true; + break; + + case KeyCode.Return: + centerBlock = true; + selectBlock = true; + closePopup = true; + useEvent = true; + break; + + case KeyCode.Escape: + closePopup = true; + useEvent = true; + break; + } + + blockPopupSelection = Mathf.Clamp(blockPopupSelection, 0, filteredBlocks.Length - 1); + + if (centerBlock && filteredBlocks.Length > 0) + { + var block = filteredBlocks[blockPopupSelection]; + CenterBlock(flowchart, block); + + if (selectBlock) + { + SelectBlock(flowchart, block); + } + } + + if (closePopup) + { + CloseBlockPopup(); + } + + if (useEvent) + { + Event.current.Use(); + } + } + } + else if (Event.current.type == EventType.MouseDown && Event.current.button == 0 && + searchRect.Contains(Event.current.mousePosition)) + { + blockPopupSelection = 0; + } + + GUI.SetNextControlName(searchFieldName); + var newString = EditorGUILayout.TextField(searchString, GUI.skin.FindStyle("ToolbarSeachTextField"), GUILayout.Width(150)); + if (newString != searchString) + { + searchString = newString; + } + + // Update this every frame in case of redo/undo while popup is open + filteredBlocks = blocks.Where(block => block.BlockName.ToLower().Contains(searchString.ToLower())).ToArray(); + blockPopupSelection = Mathf.Clamp(blockPopupSelection, 0, filteredBlocks.Length - 1); + + if (Event.current.type == EventType.Repaint) + { + searchRect = GUILayoutUtility.GetLastRect(); + popupRect = searchRect; + popupRect.width += 12; + popupRect.y += popupRect.height; + popupRect.height = Mathf.Min(filteredBlocks.Length * 16, position.height - 22); + } + + if (GUILayout.Button("", GUI.skin.FindStyle("ToolbarSeachCancelButton"))) + { + CloseBlockPopup(); + } + GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); @@ -365,7 +493,9 @@ namespace Fungus.EditorUtils if (Event.current.type == EventType.Repaint) { Rect toolbarRect = new Rect(0, 0, position.width, 18); - mouseOverVariables = variableWindowRect.Contains(Event.current.mousePosition) || toolbarRect.Contains(rawMousePosition); + mouseOverPopup = (GUI.GetNameOfFocusedControl() == searchFieldName && popupRect.Contains(rawMousePosition)); + mouseOverVariables = variableWindowRect.Contains(Event.current.mousePosition) || + toolbarRect.Contains(rawMousePosition) || mouseOverPopup; } GUILayout.EndScrollView(); @@ -375,6 +505,75 @@ namespace Fungus.EditorUtils GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); + + // Draw block search popup on top of other controls + if (GUI.GetNameOfFocusedControl() == searchFieldName && filteredBlocks.Length > 0) + { + DrawBlockPopup(flowchart); + } + } + + protected virtual void DrawBlockPopup(Flowchart flowchart) + { + blockPopupSelection = Mathf.Clamp(blockPopupSelection, 0, filteredBlocks.Length - 1); + + GUI.Box(popupRect, "", GUI.skin.FindStyle("sv_iconselector_back")); + + if (Event.current.type == EventType.MouseMove) + { + if (popupRect.Contains(Event.current.mousePosition)) + { + var relativeY = Event.current.mousePosition.y - popupRect.yMin + popupScroll.y; + blockPopupSelection = (int) (relativeY / 16); + } + + Event.current.Use(); + } + + GUILayout.BeginArea(popupRect); + 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 block = filteredBlocks[i]; + var style = i == blockPopupSelection ? selectedStyle : normalStyle; + + GUI.contentColor = GetBlockGraphics(block).tint; + + var buttonPressed = false; + if (GUILayout.Button(FungusEditorResources.BulletPoint, style, GUILayout.Width(16))) + { + buttonPressed = true; + } + + GUI.contentColor = Color.white; + + if (GUILayout.Button(block.BlockName, style)) + { + buttonPressed = true; + } + + if (buttonPressed) + { + CenterBlock(flowchart, block); + SelectBlock(flowchart, block); + CloseBlockPopup(); + } + + EditorGUILayout.EndHorizontal(); + } + + EditorGUILayout.EndScrollView(); + GUILayout.EndArea(); } protected virtual void DrawFlowchartView(Flowchart flowchart) @@ -797,7 +996,7 @@ namespace Fungus.EditorUtils bool zoom = false; // Scroll wheel - if (Event.current.type == EventType.ScrollWheel) + if (Event.current.type == EventType.ScrollWheel && !mouseOverPopup) { zoom = true; } @@ -955,44 +1154,7 @@ namespace Fungus.EditorUtils } GUIStyle nodeStyleCopy = new GUIStyle(nodeStyle); - Texture2D offTex; - Texture2D onTex; - Color defaultColor; - - if (block._EventHandler != null) - { - offTex = FungusEditorResources.EventNodeOff; - onTex = FungusEditorResources.EventNodeOn; - defaultColor = FungusConstants.DefaultEventBlockTint; - } - else - { - // Count the number of unique connections (excluding self references) - var uniqueList = new List(); - var connectedBlocks = block.GetConnectedBlocks(); - foreach (var connectedBlock in connectedBlocks) - { - if (connectedBlock == block || - uniqueList.Contains(connectedBlock)) - { - continue; - } - uniqueList.Add(connectedBlock); - } - - if (uniqueList.Count > 1) - { - offTex = FungusEditorResources.ChoiceNodeOff; - onTex = FungusEditorResources.ChoiceNodeOn; - defaultColor = FungusConstants.DefaultChoiceBlockTint; - } - else - { - offTex = FungusEditorResources.ProcessNodeOff; - onTex = FungusEditorResources.ProcessNodeOn; - defaultColor = FungusConstants.DefaultProcessBlockTint; - } - } + var graphics = GetBlockGraphics(block); // Make sure node is wide enough to fit the node name text var n = block as Node; @@ -1002,22 +1164,26 @@ namespace Fungus.EditorUtils n._NodeRect = tempRect; Rect boxRect = GUILayoutUtility.GetRect(n._NodeRect.width, n._NodeRect.height); - var tintColor = n.UseCustomTint ? n.Tint : defaultColor; // Draw untinted highlight if (selected) { GUI.backgroundColor = Color.white; - nodeStyleCopy.normal.background = onTex; + nodeStyleCopy.normal.background = graphics.onTexture; GUI.Box(boxRect, "", nodeStyleCopy); } // Draw tinted block; ensure text is readable - var brightness = tintColor.r * 0.3 + tintColor.g * 0.59 + tintColor.b * 0.11; + 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; - nodeStyleCopy.normal.background = offTex; - GUI.backgroundColor = tintColor; + 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; @@ -1293,7 +1459,7 @@ namespace Fungus.EditorUtils Event.current.Use(); } } - else if (c == "SelectAll") + else if (c == "SelectAll" || c == "Find") { Event.current.Use(); } @@ -1340,8 +1506,75 @@ namespace Fungus.EditorUtils } Event.current.Use(); break; + + case "Find": + blockPopupSelection = 0; + EditorGUI.FocusTextInControl(searchFieldName); + Event.current.Use(); + break; } } } + + protected virtual void CenterBlock(Flowchart flowchart, Block block) + { + if (flowchart.Zoom < 1) + { + DoZoom(flowchart, 1 - flowchart.Zoom, Vector2.one * 0.5f); + } + + flowchart.ScrollPos = -block._NodeRect.center + position.size * 0.5f / flowchart.Zoom; + } + + protected virtual void CloseBlockPopup() + { + GUIUtility.keyboardControl = 0; + searchString = string.Empty; + } + + protected virtual BlockGraphics GetBlockGraphics(Block block) + { + var graphics = new BlockGraphics(); + + Color defaultTint; + if (block._EventHandler != null) + { + graphics.offTexture = FungusEditorResources.EventNodeOff; + graphics.onTexture = FungusEditorResources.EventNodeOn; + defaultTint = FungusConstants.DefaultEventBlockTint; + } + else + { + // Count the number of unique connections (excluding self references) + var uniqueList = new List(); + var connectedBlocks = block.GetConnectedBlocks(); + foreach (var connectedBlock in connectedBlocks) + { + if (connectedBlock == block || + uniqueList.Contains(connectedBlock)) + { + continue; + } + uniqueList.Add(connectedBlock); + } + + if (uniqueList.Count > 1) + { + graphics.offTexture = FungusEditorResources.ChoiceNodeOff; + graphics.onTexture = FungusEditorResources.ChoiceNodeOn; + defaultTint = FungusConstants.DefaultChoiceBlockTint; + } + else + { + graphics.offTexture = FungusEditorResources.ProcessNodeOff; + graphics.onTexture = FungusEditorResources.ProcessNodeOn; + defaultTint = FungusConstants.DefaultProcessBlockTint; + } + } + + graphics.tint = block.UseCustomTint ? block.Tint : defaultTint; + + return graphics; + } } } \ No newline at end of file diff --git a/Assets/Fungus/Scripts/Editor/FungusEditorResourcesGenerated.cs b/Assets/Fungus/Scripts/Editor/FungusEditorResourcesGenerated.cs index 7ea1ea0c..f7a0641b 100644 --- a/Assets/Fungus/Scripts/Editor/FungusEditorResourcesGenerated.cs +++ b/Assets/Fungus/Scripts/Editor/FungusEditorResourcesGenerated.cs @@ -13,6 +13,7 @@ namespace Fungus.EditorUtils [SerializeField] private EditorTexture down; [SerializeField] private EditorTexture duplicate; [SerializeField] private EditorTexture up; + [SerializeField] private EditorTexture bullet_point; [SerializeField] private EditorTexture choice_node_off; [SerializeField] private EditorTexture choice_node_on; [SerializeField] private EditorTexture command_background; @@ -30,6 +31,7 @@ namespace Fungus.EditorUtils public static Texture2D Down { get { return Instance.down.Texture2D; } } public static Texture2D Duplicate { get { return Instance.duplicate.Texture2D; } } public static Texture2D Up { get { return Instance.up.Texture2D; } } + public static Texture2D BulletPoint { get { return Instance.bullet_point.Texture2D; } } public static Texture2D ChoiceNodeOff { get { return Instance.choice_node_off.Texture2D; } } public static Texture2D ChoiceNodeOn { get { return Instance.choice_node_on.Texture2D; } } public static Texture2D CommandBackground { get { return Instance.command_background.Texture2D; } }