using System; using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.SceneManagement; using UnityObject = UnityEngine.Object; namespace Unity.VisualScripting { internal class DropdownOptions { internal bool interactable = true; internal string label; internal string tooltip; internal Action callback; } internal class SplitDropdown { private bool reset; private List listOfOptions; internal SplitDropdown(List listOfOptions) { this.listOfOptions = listOfOptions; } internal DropdownOptions GetOption(int index) { return listOfOptions[index]; } internal void Reset() { reset = true; } internal bool Draw(Rect area, string label, ref bool toggleState) { GUI.Box(area, string.Empty, EmptyGraphWindow.buttonStyle); if (GUI.Button(new Rect(area.x, area.y, area.width - area.height, area.height), label, EmptyGraphWindow.labelStyleButton)) { toggleState = false; return true; } Rect toggleRect = new Rect(area.x + area.width - area.height - 2, area.y - 1, area.height + 3, area.height + 3); toggleState = GUI.Toggle(toggleRect, toggleState, "", EmptyGraphWindow.labelStyleButton); GUI.Label(toggleRect, EmptyGraphWindow.dropdownIcon, EmptyGraphWindow.titleStyle); if (toggleState) { GUI.BeginGroup(new Rect(area.x, area.y + area.height - 1, area.width, area.height * 2), EmptyGraphWindow.buttonStyle); Rect areaChild = new Rect(0, 0, EmptyGraphWindow.buttonWidth, EmptyGraphWindow.buttonHeight); GUIContent contentOption; foreach (DropdownOptions dropdownOption in listOfOptions) { EditorGUI.BeginDisabledGroup(!dropdownOption.interactable); if (dropdownOption.interactable) { contentOption = new GUIContent(dropdownOption.label); } else { contentOption = new GUIContent(dropdownOption.label, dropdownOption.tooltip); } if (GUI.Button(areaChild, contentOption, EmptyGraphWindow.labelStyleDropdownOptions)) { toggleState = false; dropdownOption.callback(); } EditorGUI.EndDisabledGroup(); areaChild.y += areaChild.height; } GUI.EndGroup(); } if (reset) { toggleState = false; reset = false; } return false; } } internal class EmptyGraphWindow : EditorWindow { internal static GUIContent dropdownIcon; private bool toggleOnScript = false; private bool toggleOnState = false; internal static GUIStyle buttonStyle; internal static GUIStyle labelStyleButton; internal static GUIStyle labelStyleDropdownOptions; internal static GUIStyle titleStyle; internal static int titleHeight = 120; internal static int buttonWidth = 200; internal static int buttonHeight = 22; private bool shouldCloseWindow; private Vector2 scrollPosition; private Rect scrollArea; private SplitDropdown splitDropdownScriptGraph; private SplitDropdown splitDropdownStateGraph; const string k_OnSelectedGameObject = "...on selected GameObject"; const string k_OnNewGameObject = "...on new GameObject"; const string k_SelectedGameObject = "Please, select a GameObject"; [MenuItem("Window/Visual Scripting/Visual Scripting Graph", false, 3010)] private static void ShowWindow() { EmptyGraphWindow window = GetWindow(); window.titleContent = new GUIContent("Visual Scripting"); } private void OnEnable() { string pathRoot = PathUtility.GetPackageRootPath(); UnityObject icon = EditorGUIUtility.Load(Path.Combine(pathRoot, "Editor/VisualScripting.Shared/EditorAssetResources/SplitButtonArrow.png")); dropdownIcon = new GUIContent(icon as Texture2D); scrollArea = new Rect(0, 0, 630, 300); toggleOnScript = false; toggleOnState = false; shouldCloseWindow = false; var listOfOptions = new List { new DropdownOptions { label = k_OnSelectedGameObject, tooltip = k_SelectedGameObject, callback = CreateScriptGraphOnSelectedGameObject }, new DropdownOptions { label = k_OnNewGameObject, callback = CreateScriptGraphOnNewGameObject } }; splitDropdownScriptGraph = new SplitDropdown(listOfOptions); listOfOptions = new List { new DropdownOptions { label = k_OnSelectedGameObject, tooltip = k_SelectedGameObject, callback = CreateStateGraphOnSelectedGameObject }, new DropdownOptions { label = k_OnNewGameObject, callback = CreateStateGraphOnNewGameObject } }; splitDropdownStateGraph = new SplitDropdown(listOfOptions); } private void OpenGraphAsset(UnityObject unityObject, bool shouldSetSceneAsDirty) { shouldCloseWindow = true; ScriptGraphAsset scriptGraphAsset = unityObject as ScriptGraphAsset; GraphReference graphReference = null; if (scriptGraphAsset != null) { graphReference = GraphReference.New(scriptGraphAsset, true); } else { StateGraphAsset stateGraphAsset = unityObject as StateGraphAsset; if (stateGraphAsset != null) { graphReference = GraphReference.New(stateGraphAsset, true); } } if (shouldSetSceneAsDirty) { EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene()); } GraphWindow.OpenActive(graphReference); } private void OpenGraphFromPath(string path, bool shouldSetSceneAsDirty) { path = path.Replace(Application.dataPath, "Assets"); UnityObject unityObject = AssetDatabase.LoadAssetAtPath(path, typeof(UnityObject)); OpenGraphAsset(unityObject, shouldSetSceneAsDirty); } private void OpenGraph() { EditorGUIUtility.ShowObjectPicker(null, false, String.Empty, EditorGUIUtility.GetControlID(FocusType.Passive)); } private bool CreateScriptGraphAsset(GameObject gameObject = null, bool updateName = false) { var path = EditorUtility.SaveFilePanelInProject("Save Graph", "New Script Graph", "asset", null); if (string.IsNullOrEmpty(path)) { return false; } VSUsageUtility.isVisualScriptingUsed = true; var macro = (IMacro)CreateInstance(typeof(ScriptGraphAsset)); var macroObject = (UnityObject)macro; macro.graph = FlowGraph.WithStartUpdate(); if (gameObject != null) { ScriptMachine flowMachine = gameObject.AddComponent(); flowMachine.nest.macro = (ScriptGraphAsset)macro; } string filename = Path.GetFileNameWithoutExtension(path); if (updateName) { gameObject.name = filename; } macroObject.name = filename; AssetDatabase.CreateAsset(macroObject, path); bool shouldSetSceneAsDirty = gameObject != null; OpenGraphFromPath(path, shouldSetSceneAsDirty); return true; } private void CreateScriptGraph() { Selection.activeGameObject = null; if (CreateScriptGraphAsset()) { shouldCloseWindow = true; } } private void CreateScriptGraphOnNewGameObject() { Selection.activeGameObject = null; GameObject newGameObject = new GameObject(); if (!CreateScriptGraphAsset(newGameObject, true)) { DestroyImmediate(newGameObject); } } private void CreateScriptGraphOnSelectedGameObject() { if (Selection.activeGameObject != null) { if (CreateScriptGraphAsset(Selection.activeGameObject)) { shouldCloseWindow = true; } } } private bool CreateStateGraphAsset(GameObject gameObject = null, bool updateName = false) { var path = EditorUtility.SaveFilePanelInProject("Save Graph", "New State Graph", "asset", null); if (string.IsNullOrEmpty(path)) { return false; } VSUsageUtility.isVisualScriptingUsed = true; var macro = (IMacro)CreateInstance(typeof(StateGraphAsset)); var macroObject = (UnityObject)macro; macro.graph = StateGraph.WithStart(); if (gameObject != null) { StateMachine stateMachine = gameObject.AddComponent(); stateMachine.nest.macro = (StateGraphAsset)macro; } string filename = Path.GetFileNameWithoutExtension(path); if (updateName) { gameObject.name = filename; } macroObject.name = filename; AssetDatabase.CreateAsset(macroObject, path); bool shouldSetSceneAsDirty = gameObject != null; OpenGraphFromPath(path, shouldSetSceneAsDirty); return true; } private void CreateStateGraph() { Selection.activeGameObject = null; if (CreateStateGraphAsset()) { shouldCloseWindow = true; } } private void CreateStateGraphOnNewGameObject() { Selection.activeGameObject = null; GameObject newGameObject = new GameObject(); if (!CreateStateGraphAsset(newGameObject, true)) { DestroyImmediate(newGameObject); } } private void CreateStateGraphOnSelectedGameObject() { if (Selection.activeGameObject != null) { if (CreateStateGraphAsset(Selection.activeGameObject)) { shouldCloseWindow = true; } } } private void CreateStyles() { if (buttonStyle == null) { buttonStyle = new GUIStyle("Button"); } if (labelStyleButton == null) { labelStyleButton = new GUIStyle("Label") { alignment = TextAnchor.MiddleCenter }; } if (labelStyleDropdownOptions == null) { labelStyleDropdownOptions = new GUIStyle("ToolbarButton") { alignment = TextAnchor.MiddleLeft, padding = new RectOffset(20, 0, 0, 0) }; } if (titleStyle == null) { titleStyle = new GUIStyle("Label") { alignment = TextAnchor.MiddleCenter, fontSize = 20 }; } } private void ResetToggle() { if (Event.current.rawType == EventType.MouseUp) { if (toggleOnScript) { splitDropdownScriptGraph.Reset(); Repaint(); } if (toggleOnState) { splitDropdownStateGraph.Reset(); Repaint(); } } } private void OpenGraphFromPicker() { if (Event.current.commandName == "ObjectSelectorUpdated") { UnityObject selectedObject = EditorGUIUtility.GetObjectPickerObject(); OpenGraphAsset(selectedObject, false); } } private void OnGUI() { CreateStyles(); ResetToggle(); DropAreaGUI(); OpenGraphFromPicker(); scrollPosition = GUI.BeginScrollView(new Rect(0, 0, position.width, position.height), scrollPosition, scrollArea); GUILayout.BeginVertical(GUILayout.ExpandHeight(true)); Vector2 groupSize = new Vector2(scrollArea.width, 280); GUI.BeginGroup(new Rect((position.width / 2) - (groupSize.x / 2), (position.height / 2) - (groupSize.y / 2), groupSize.x, groupSize.y)); GUI.Label(new Rect(0, 0, groupSize.x, titleHeight), "Drag and drop a Visual Scripting Graph asset here\nor", titleStyle); int buttonX = 10; if (GUI.Button(new Rect(buttonX, titleHeight, buttonWidth, buttonHeight), "Browse to open a Graph")) { OpenGraph(); } buttonX += (buttonWidth + 10); Rect area = new Rect(buttonX, titleHeight, buttonWidth, buttonHeight); splitDropdownScriptGraph.GetOption(0).interactable = Selection.activeGameObject != null; if (splitDropdownScriptGraph.Draw(area, "Create new Script Graph", ref toggleOnScript)) { CreateScriptGraph(); } if (toggleOnScript) { toggleOnState = false; } buttonX += (buttonWidth + 10); area = new Rect(buttonX, titleHeight, buttonWidth, buttonHeight); splitDropdownStateGraph.GetOption(0).interactable = Selection.activeGameObject != null; if (splitDropdownStateGraph.Draw(area, "Create new State Graph", ref toggleOnState)) { CreateStateGraph(); } if (toggleOnState) { toggleOnScript = false; } GUI.EndGroup(); GUILayout.EndVertical(); GUI.EndScrollView(); if (shouldCloseWindow) { Close(); } } private void DropAreaGUI() { Event currentEvent = Event.current; Rect activeArea = GUILayoutUtility.GetRect(0, 0, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true)); switch (currentEvent.type) { case EventType.DragUpdated: case EventType.DragPerform: if (!activeArea.Contains(currentEvent.mousePosition)) { return; } DragAndDrop.visualMode = DragAndDropVisualMode.Copy; if (currentEvent.type == EventType.DragPerform) { DragAndDrop.AcceptDrag(); foreach (UnityObject draggedObject in DragAndDrop.objectReferences) { ScriptGraphAsset scriptGraphAsset = draggedObject as ScriptGraphAsset; if (scriptGraphAsset != null) { shouldCloseWindow = true; GraphWindow.OpenActive(GraphReference.New(scriptGraphAsset, true)); break; } } } break; } } } }