#if UNITY_EDITOR using MonKey.Editor.Commands; using MonKey.Editor.Console; using MonKey.Editor.Internal; using MonKey.Extensions; using MonKey.Internal; using MonKey.Settings.Internal; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using UnityEditor; using UnityEngine; using UnityEngine.Rendering; using Object = UnityEngine.Object; namespace MonKey.Editor { public static class MonkeyEditorUtils { /* internal static List PreviousOrderedSelectedObjects = new List(); internal static List PreviousOrderedSelectedTransform = new List(); internal static List OrderedSelectedObjectsInt = new List(); internal static List OrderedSelectedTransformInt = new List();*/ /// /// While Unity does not keep track of the order with which you selected objects, MonKey does. /// This Enumerator can help you retrieve the first object, the last, or anything in between, which can be very useful for custom commands. /// public static IEnumerable OrderedSelectedGameObjects { get { if (!MonKeyInternalSettings.Instance.UseSortedSelection) { if (MonKeyInternalSettings.Instance.ShowSortedSelectionWarning) Debug.LogWarning(" Monkey Warning: A command is attempting to use a sorted selection," + " but it was disabled in the monkey preferences:" + " this can lead to unexpected results"); return Selection.gameObjects; } return OrderedSelectedObjects.Where(_ => _ is GameObject) .Convert(_ => _ as GameObject).ToList(); } } /// /// The ordered list of objects. /// public static IEnumerable OrderedSelectedObjects { get { if (!MonKeyInternalSettings.Instance.UseSortedSelection) { if (MonKeyInternalSettings.Instance.ShowSortedSelectionWarning) Debug.LogWarning(" Monkey Warning: A command is attempting to use a sorted selection," + " but it was disabled in the monkey preferences:" + " this can lead to unexpected results"); return Selection.objects; } return MonKeySelectionUtils.OrderedObjects; } } public static object GetParsedParameter(int parameterID) { return CommandConsoleWindow.CurrentPanel.CurrentExecution.GetValueParsed(parameterID); } /// /// The ordered list of transforms. /// Keep in mind that this follows the same logic as Unity's: /// the transforms contained are only the uppermost selected: if you select a transform and some of its children, /// on the parent will appear in that list. /// public static IEnumerable OrderedSelectedTransform { get { if (!MonKeyInternalSettings.Instance.UseSortedSelection) { if (MonKeyInternalSettings.Instance.ShowSortedSelectionWarning) Debug.LogWarning(" Monkey Warning: A command is attempting to use a sorted selection," + " but it was disabled in the monkey preferences:" + " this can lead to unexpected results"); return Selection.transforms; } return MonKeySelectionUtils.OrderedTransforms; } } /// /// Keeps a reference to the mouse position so that it may be accessed outside of a GUI call: this can be useful when creating scene commands. /// public static Vector2 MousePosition { get; internal set; } /// /// Returns a ray that goes from the mouse position onto the scene. /// public static Ray MouseSceneRay { get; private set; /* { Vector3 inv = new Vector3(MousePosition.x, +CurrentSceneView.position.height + weirdConst - MousePosition.y); return HandleUtility.GUIPointToWorldRay(MousePosition); // return HandleUtility.GUIPointToWorldRay(MousePosition); // return CurrentSceneView.camera.ScreenPointToRay(inv); }*/ } public static Vector2 SceneViewMousePosition { get; private set; }/* new Vector3(MousePosition.x, +CurrentSceneView.position.height + weirdConst - MousePosition.y)*/ //Input.mousePosition; /// /// /Returns the raycasted position of the mouse on the scene, /// with the possibility to ignore some objects and to apply an offset. /// This method outputs the normal of the collision point as well. /// /// The objects to ignore when raycasting /// the offset relative to the hit point /// returns true if the ray found a collision /// returns the normal vector on the hit point /// public static Vector3 GetMouseRayCastedPosition(Transform[] transformsToIgnore, float hitOffset, out bool collision, out Vector3 normal, bool ignoreInvisible = false) { if (!CurrentSceneView) { CurrentSceneView = SceneView.currentDrawingSceneView; if (!CurrentSceneView) CurrentSceneView = SceneView.lastActiveSceneView; if (!CurrentSceneView) { SceneView.duringSceneGui -= OnSceneViewGUI; SceneView.duringSceneGui += OnSceneViewGUI; collision = false; normal = Vector3.zero; return Vector3.zero; } } Vector3 position = CurrentSceneView.camera.ScreenToWorldPoint(MousePosition); normal = Vector3.up; float minDistanceToCam = float.MaxValue; collision = false; foreach (var hit in Physics.RaycastAll(MouseSceneRay)) { if (!ConstrainUtilities.GloballyLockedTransforms.Contains(hit.transform) && transformsToIgnore != null && transformsToIgnore.Any(_ => _ == hit.transform)) continue; if (ignoreInvisible && !hit.transform.gameObject.GetComponent() && !hit.transform.gameObject.GetComponent()) continue; collision = true; float distanceToCam = (hit.point - CurrentSceneView.camera.transform.position).magnitude; if (minDistanceToCam > distanceToCam) { minDistanceToCam = distanceToCam; position = hit.point + hitOffset * hit.normal; normal = hit.normal; } } return position; } /// /// Keeps a reference to the last Event that was called on GUI, so that you may use it outside of GUI logics. /// public static Event LastGlobalEvent { get; private set; } /// /// Returns the currently focused scene view. /// public static SceneView CurrentSceneView { get; internal set; } /// /// Returns the folder that is currently focused in the project window. Keep in mind that this is not the same as the selected folder. /// /// public static string GetProjectWindowFocusedFolder() { MethodInfo info = typeof(ProjectWindowUtil).GetMethod("GetActiveFolderPath", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); if (info != null) return info.Invoke(null, null) as string; return ""; } public static double EditorDeltaTime => EditorApplication.timeSinceStartup - lastTimeSinceStartup; private static double lastTimeSinceStartup; private static int SceneGUIMinWidth = 250; private static int SceneGUIMaxWidth = 400; private static int SceneGUIMinHeight = 130; private static int SceneGUIMaxHeight = 300; private static Vector2 scrollPosition = Vector2.zero; public static readonly List CurrentSceneCommands = new List(); private static readonly Dictionary DelegateCommands = new Dictionary(); private static readonly List ToRemove = new List(); private static bool ShouldShowSceneGUI { get { return CurrentSceneCommands.Any(_ => !_.Hidden); } } /// /// Returns the data structure of the command of the specified name. /// CommandInfo contains all the information used by MonKey to display and execute the command. /// /// /// public static CommandInfo GetCommandInfo(string commandName) { return CommandManager.Instance.GetCommandInfo(commandName); } //[InitializeOnLoadMethod] internal static void EditorUpdatePlug(CommandManager manager) { #if UNITY_2017_1_OR_NEWER EditorApplication.playModeStateChanged -= PlayStateChangeCallBack; EditorApplication.playModeStateChanged += PlayStateChangeCallBack; #else EditorApplication.playmodeStateChanged -= PlayStateChangeCallBack; EditorApplication.playmodeStateChanged += PlayStateChangeCallBack; #endif EditorApplication.update -= MonKeyEditorUpdate; EditorApplication.update += MonKeyEditorUpdate; #if UNITY_2019_1_OR_NEWER SceneView.duringSceneGui -= OnSceneViewGUI; SceneView.duringSceneGui += OnSceneViewGUI; #else SceneView.onSceneGUIDelegate -= OnSceneViewGUI; SceneView.onSceneGUIDelegate += OnSceneViewGUI; #endif FieldInfo info = typeof(EditorApplication) .GetField("globalEventHandler", BindingFlags.Static | BindingFlags.NonPublic); EditorApplication.CallbackFunction value = (EditorApplication.CallbackFunction) info.GetValue(null); value -= EditorGlobalEvent; value += EditorGlobalEvent; info.SetValue(null, value); manager.OnCommandLoadingDone += InvokeOnCommandLoadingDone; lastTimeSinceStartup = EditorApplication.timeSinceStartup; ResetSelectionToFirstSelected(); } private static void ResetSelectionToFirstSelected() { if (Selection.transforms.Length > 0 || Selection.objects.Length > 0) { if (Selection.activeContext) Selection.objects = new[] { Selection.activeContext }; else Selection.objects = new[] { Selection.objects[0] }; } } /// /// If you want to associate reserved hotkeys that are used for something external to MonKey, /// you can specify it here, and it will appear visible in the console when a conflict happens. /// /// /// public static void AddCustomReservedHotKeys(string commandName, params string[] hotKeys) { HotKeysManager.RegisterCommandHotKey(new CommandInfo() { CommandName = commandName, HotKey = (new KeyCombination(hotKeys)) }); } #if UNITY_2017_1_OR_NEWER private static void PlayStateChangeCallBack(PlayModeStateChange obj) { foreach (SceneCommand command in CurrentSceneCommands) { if (command.StopOnPlayStateChange) ToRemove.Add(command); } RemoveCommandsToBeRemoved(); } #else private static void PlayStateChangeCallBack() { foreach (SceneCommand command in CurrentSceneCommands) { if (command.StopOnPlayStateChange) ToRemove.Add(command); } RemoveCommandsToBeRemoved(); } #endif private static void EditorGlobalEvent() { LastGlobalEvent = Event.current; } private static void MonKeyEditorUpdate() { CommandUpdate(); lastTimeSinceStartup = EditorApplication.timeSinceStartup; } private static void CommandUpdate() { RemoveCommandsToBeRemoved(); if (CurrentSceneCommands == null) return; foreach (var command in CurrentSceneCommands) { if (command == null) continue; if (command.TimeSinceLastUpdate >= command.TimeBetweenUpdate) { command.Update(); command.TimeSinceLastUpdate = 0; } else { command.TimeSinceLastUpdate += EditorDeltaTime; } } } private static void RemoveCommandsToBeRemoved() { if (ToRemove == null) return; foreach (var command in ToRemove) { CurrentSceneCommands.Remove(command); } if (ToRemove.Count > 0) ToRemove.Clear(); } public static void AddEditorDelegate(Action delegateAction, float timeBetweenUpdate = 0) { if (DelegateCommands.ContainsKey(delegateAction)) RemoveEditorDelegate(delegateAction); DelegateSceneCommand del = new DelegateSceneCommand(delegateAction) { TimeBetweenUpdate = timeBetweenUpdate }; DelegateCommands.Add(delegateAction, del); AddSceneCommand(del); } public static void RemoveEditorDelegate(Action delegateAction) { if (!DelegateCommands.ContainsKey(delegateAction)) return; DelegateCommands[delegateAction].Stop(); DelegateCommands.Remove(delegateAction); } /// /// Adds a scene command to the active ones on the scene. /// /// public static void AddSceneCommand(SceneCommand command) { #if UNITY_2017_1_OR_NEWER EditorApplication.playModeStateChanged -= PlayStateChangeCallBack; EditorApplication.playModeStateChanged += PlayStateChangeCallBack; #else EditorApplication.playmodeStateChanged -= PlayStateChangeCallBack; EditorApplication.playmodeStateChanged += PlayStateChangeCallBack; #endif EditorApplication.update -= MonKeyEditorUpdate; EditorApplication.update += MonKeyEditorUpdate; #if UNITY_2019_1_OR_NEWER SceneView.duringSceneGui -= OnSceneViewGUI; SceneView.duringSceneGui += OnSceneViewGUI; #else SceneView.onSceneGUIDelegate -= OnSceneViewGUI; SceneView.onSceneGUIDelegate += OnSceneViewGUI; #endif command.OnActionDone += NotifyActionRemoved; int i = 1; while (CurrentSceneCommands.Count(_ => _.SceneCommandName == command.SceneCommandName) > 0) { if (command.SceneCommandName.Contains(i.ToString())) command.SceneCommandName = command.SceneCommandName.Replace((i - 1).ToString(), i.ToString()); else { command.SceneCommandName = command.SceneCommandName + " " + i; } i++; } CurrentSceneCommands.Add(command); if (!CurrentSceneView) CurrentSceneView = SceneView.currentDrawingSceneView; if (CurrentSceneView) CurrentSceneView.Focus(); SelectedSceneCommand = command; if (MonKeyInternalSettings.Instance.UseSceneCommandAsEditorWindow) { SceneCommandsWindow.ShowSceneCommandWindow(); } } private static void NotifyActionRemoved(SceneCommand command) { ToRemove.Add(command); } private static bool overCross; public static SceneCommand SelectedSceneCommand; private static void OnSceneViewGUI(SceneView sceneView) { MousePosition = Event.current.mousePosition; MouseSceneRay = HandleUtility.GUIPointToWorldRay(MousePosition); SceneViewMousePosition = HandleUtility.GUIPointToScreenPixelCoordinate(MousePosition); // sceneView.Repaint(); if (!CurrentSceneView) CurrentSceneView = sceneView; SceneCommandSceneViewInterface(); ExecuteCommandOnSceneGUI(); } private static void SceneCommandSceneViewInterface() { if (!ShouldShowSceneGUI) return; if (MonKeyInternalSettings.Instance.UseSceneCommandAsEditorWindow) return; if (!MonkeyStyle.Instance.SearchFieldBackgroundTex) MonkeyStyle.Instance.PostInstanceCreation(); float width = Mathf.Clamp(SceneView.currentDrawingSceneView.position.width * 0.2f, SceneGUIMinWidth, SceneGUIMaxWidth); float height = Mathf.Clamp(SceneView.currentDrawingSceneView.position.height * 0.2f, SceneGUIMinHeight, SceneGUIMaxHeight); Handles.BeginGUI(); GUILayout.BeginArea(new Rect(SceneView.currentDrawingSceneView.position.width - width, SceneView.currentDrawingSceneView.position.height - height, width, height), new GUIStyle() { normal = { background = MonkeyStyle.Instance.WindowBackgroundTex } }); GUILayout.BeginVertical(new GUIStyle() { margin = new RectOffset(0, 4, 0, 5) }); GUILayout.Label("", MonkeyStyle.Instance.HorizontalSideLineStyle); GUILayout.Label("", MonkeyStyle.Instance.HorizontalSideSecondLineStyle); GUILayout.BeginVertical(new GUIStyle() { margin = new RectOffset(0, 2, 0, 5) }); GUILayout.BeginHorizontal(); GUILayout.Label("", MonkeyStyle.Instance.SmallMonkey); GUILayout.BeginHorizontal(new GUIStyle() { margin = new RectOffset(5, 5, 0, 0) }); GUILayout.Label("Active Scene Commands", MonkeyStyle.Instance.VariableHelpTextStyle); GUILayout.EndHorizontal(); GUILayout.EndHorizontal(); GUILayout.Label("", MonkeyStyle.Instance.HorizontalSideLineStyle); GUILayout.Label("", MonkeyStyle.Instance.HorizontalSideSecondLineStyle); GUI.skin = MonkeyStyle.Instance.MonkeyScrollBarStyle; scrollPosition = GUILayout.BeginScrollView(scrollPosition, new GUIStyle() { margin = new RectOffset(4, 2, 0, 2), normal = { background = MonkeyStyle.Instance.ResultFieldTex }, }, GUILayout.Height(60)); foreach (var command in CurrentSceneCommands) { try { if (command.Hidden) continue; GUILayout.BeginVertical(); GUILayout.BeginHorizontal(new GUIStyle() { padding = new RectOffset(5, 5, 2, 2), normal = { background = MonkeyStyle.Instance.ResultFieldTex } }, GUILayout.Height(25)); GUILayout.BeginVertical( new GUIStyle() { margin = new RectOffset(0, 0, 2, 0) }, GUILayout.ExpandHeight(true)); string commandTitle = command.SceneCommandName .Colored(MonkeyStyle.Instance.SearchResultTextColor).Bold(); if (command == SelectedSceneCommand) commandTitle = commandTitle.Insert(0, "|".Colored(MonkeyStyle.Instance.WarningColor).Bold()); GUILayout.Label(commandTitle, MonkeyStyle.Instance.SceneCommandHelpStyle); GUILayout.EndVertical(); GUILayout.FlexibleSpace(); GUILayout.BeginVertical(new GUIStyle() { padding = new RectOffset(5, 5, 2, 2), normal = { background = overCross ? MonkeyStyle.Instance.BlackTex : MonkeyStyle.Instance.HelpVariableTex }, hover = { background = MonkeyStyle.Instance.BlackTex } }); GUILayout.FlexibleSpace(); bool pressed = GUILayout.Button(" X " .Colored(MonkeyStyle.Instance.WarningColor).Bold(), MonkeyStyle.Instance.SceneCommandCrossStyle, GUILayout.ExpandWidth(true)); GUILayout.FlexibleSpace(); GUILayout.EndVertical(); overCross = GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition); if (pressed) { command.Stop(); Event.current.Use(); } /* if (command is ConfirmedCommand) confirmedAction = true;*/ GUILayout.EndHorizontal(); GUILayout.Label("", MonkeyStyle.Instance.HorizontalSearchResultLine1Style); GUILayout.Label("", MonkeyStyle.Instance.HorizontalSearchResultLine2Style); GUILayout.Label("", MonkeyStyle.Instance.HorizontalSearchResultLine3Style); GUILayout.EndVertical(); if (Event.current.type == EventType.MouseDown && GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition)) { SelectedSceneCommand = command == SelectedSceneCommand ? null : command; Event.current.Use(); } } catch (Exception e) { Debug.LogError("An Error was caught when using a command, sorry about that :( " + "Please report the exception under on MonKey's Discord channel"); Debug.LogException(e); //stop the command if it encountered an exception command.Stop(); } } GUILayout.EndScrollView(); GUI.skin = MonkeyStyle.Instance.DefaultStyle; GUILayout.Label("", MonkeyStyle.Instance.HorizontalSideLineStyle); GUILayout.Label("", MonkeyStyle.Instance.HorizontalSideSecondLineStyle); GUILayout.EndVertical(); GUILayout.EndVertical(); GUILayout.Label("", MonkeyStyle.Instance.HorizontalSideLineStyle); GUILayout.Label("", MonkeyStyle.Instance.HorizontalSideSecondLineStyle); GUILayout.EndArea(); if (SelectedSceneCommand != null && !SelectedSceneCommand.HideGUI) { SelectedSceneCommand.DisplayCommandPanel(width, height); } Handles.EndGUI(); return; } private static void ExecuteCommandOnSceneGUI() { foreach (var command in CurrentSceneCommands) { try { command.OnSceneGUI(); if (command.ShouldRepaintSceneView) SceneView.RepaintAll(); } catch (Exception e) { Debug.LogError("An Error was caught when using a command, sorry about that :( " + "Please report the exception under on MonKey's Discord channel"); Debug.LogException(e); command.Stop(); } } } /// /// Calls a command by its name as if it would be selected in the MonKey console. /// /// public static void CallCommand(string commandName) { CommandConsoleWindow.ExecuteCommand(commandName); } /// /// Makes it easier to collapse few undo operations at once. Returns an undoID that you must use together with Undo.CollapseOperation. /// /// /// public static int CreateUndoGroup(string name) { Undo.IncrementCurrentGroup(); Undo.SetCurrentGroupName(name); return Undo.GetCurrentGroup(); } /// /// A Utility to know if a key is pressed by the user, to be used within a GUI block. /// /// /// public static bool IsKeyDown(KeyCode keyCode) { return Event.current.type == EventType.KeyDown && Event.current.keyCode == keyCode; } [Command("Print All Commands", "Prints information " + "about all the commands usable in Monkey in the console", QuickName = "PAL", Category = "Help")] public static void PrintAllCommand( [CommandParameter(Help = "Should the menu items be included in the information?" + " (useful to identify HotKey conflicts)")] bool includeMenuItems = true, [CommandParameter(Help = "Should the commands be printed in one print log" + " (to be all pasted in a file for instance)")] bool printInOneGo = false, [CommandParameter(Help = "Only prints the names of the commands")] bool printOnlyNames = false) { Debug.Log("MonKey List Of Commands:".Bold()); List infoPrinted = new List(); StringBuilder builder = new StringBuilder(); foreach (var name in CommandManager.Instance.CommandNames.OrderBy(_ => _)) { CommandInfo info = CommandManager.Instance.GetCommand(name); if (!includeMenuItems && info.IsMenuItem) continue; if (infoPrinted.Contains(info)) continue; infoPrinted.Add(info); bool conflict = false; if (!printInOneGo) builder = new StringBuilder(); else { builder.Append("\n"); } builder.Append(info.CommandName.Bold()); if (!printOnlyNames) { if (!info.CommandHelp.IsNullOrEmpty()) { builder.Append("\n"); } builder.Append(info.CommandHelp); if (info.IsMenuItem) { builder.Append(" ( "); builder.Append(MonKeyLocManager.CurrentLoc.MenuItemAddedByMonkey); builder.Append(" ) "); } builder.Append("\n"); if (info.HasQuickName) { builder.Append(" Quick Name: "); builder.Append(info.CommandQuickName); } if (info.HasHotKeys) { builder.Append("\n"); builder.Append(" Hot Keys: "); builder.Append(info.HotKey.FormattedKeys); if (info.HotKeyInfo.IsConflictual) { conflict = true; builder.Append(" Conflict with: "); foreach (var commandInfo in info.HotKeyInfo.AssociatedCommands) { if (commandInfo != info) { builder.Append(commandInfo.CommandName); builder.Append(" "); } } } } } if (!printInOneGo) { if (conflict) Debug.Log(builder.ToString().Bold() .Colored(Color.red)); else { Debug.Log(builder.ToString()); } } } if (printInOneGo) Debug.Log(builder.ToString()); } /// /// An event you can register to, it will be called once all the MonKey commands are loaded. /// public static event Action OnCommandLoadingDone; private static void InvokeOnCommandLoadingDone() { OnCommandLoadingDone?.Invoke(); } /// /// Removes the quick name specified from MonKey /// /// public static void UnRegisterQuickName(string quickName) { CommandManager.Instance.CommandsByName.Remove(quickName); } public static void AddWordAliasesForCommandSearch(string word, string alias) { if (CommandManager.Instance.WordAliases.ContainsKey(word)) { Debug.LogWarningFormat("MonKey Commander already has the alias '{0}' " + "for the word '{1}': it will be replaced by '{2}' ", CommandManager.Instance.WordAliases[word], word, alias); CommandManager.Instance.WordAliases[word] = alias; } else CommandManager.Instance.WordAliases.Add(word, alias); } /// /// Removes the specified command from MonKey /// /// public static void UnRegisterCommand(string commandName) { CommandManager.Instance.CommandsByName.Remove(commandName); } [Command("Stop All Scene Commands", "Stops the execution of all the scene commands at once", QuickName = "SAC", Category = "Dev")] public static void StopAllSceneCommands() { foreach (SceneCommand command in CurrentSceneCommands) { command.Stop(); } } public enum PipelineType { HDRP, URP, CUSTOM, LEGACY, } public static PipelineType GetPipelineType() { string assetTypeHDRP = "HDRenderPipelineAsset"; string assetTypeURP = "UniversalRenderPipelineAsset"; if (!GraphicsSettings.renderPipelineAsset) return PipelineType.LEGACY; if (GraphicsSettings.renderPipelineAsset.GetType().Name.Contains(assetTypeHDRP)) { return PipelineType.HDRP; } if (GraphicsSettings.renderPipelineAsset.GetType().Name.Contains(assetTypeURP)) { return PipelineType.URP; } return PipelineType.LEGACY; } } } #endif