diff --git a/.gitignore b/.gitignore index 1a753be..d2f4e0b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ *.suo *.user *.sln.docstates +.vs # Build results [Dd]ebug/ diff --git a/Debugger/App.config b/Debugger/App.config index 75c4622..6a8ba55 100644 --- a/Debugger/App.config +++ b/Debugger/App.config @@ -1,5 +1,5 @@ - - - + + + \ No newline at end of file diff --git a/Debugger/AppearanceConfig.cs b/Debugger/AppearanceConfig.cs new file mode 100644 index 0000000..f1e2725 --- /dev/null +++ b/Debugger/AppearanceConfig.cs @@ -0,0 +1,151 @@ +using System; +using ModTools.UI; +using UnityEngine; + +namespace ModTools +{ + internal sealed class AppearanceConfig : GUIWindow + { + private readonly string[] availableFonts; + private int selectedFont; + + public AppearanceConfig() + : base("Appearance configuration", new Rect(16.0f, 16.0f, 600.0f, 490.0f), Skin, resizable: false) + { + availableFonts = Font.GetOSInstalledFontNames(); + selectedFont = Array.IndexOf(availableFonts, MainWindow.Instance.Config.FontName); + } + + protected override void DrawWindow() + { + var config = MainWindow.Instance.Config; + + GUILayout.BeginHorizontal(); + GUILayout.Label("Font"); + GUILayout.FlexibleSpace(); + + var newSelectedFont = GUIComboBox.Box(selectedFont, availableFonts, "AppearanceSettingsFonts"); + if (newSelectedFont != selectedFont && newSelectedFont >= 0) + { + config.FontName = availableFonts[newSelectedFont]; + selectedFont = newSelectedFont; + UpdateFont(); + } + + GUILayout.EndHorizontal(); + GUILayout.BeginHorizontal(); + + GUILayout.Label("Font size"); + + var newFontSize = (int)GUILayout.HorizontalSlider(config.FontSize, 13.0f, 39.0f, GUILayout.Width(256)); + + if (newFontSize != config.FontSize) + { + config.FontSize = newFontSize; + UpdateFont(); + } + + GUILayout.EndHorizontal(); + + var newColor = DrawColorControl("Background", config.BackgroundColor); + if (newColor != config.BackgroundColor) + { + config.BackgroundColor = newColor; + BgTexture.SetPixel(0, 0, config.BackgroundColor); + BgTexture.Apply(); + } + + newColor = DrawColorControl("Title bar", config.TitleBarColor); + if (newColor != config.TitleBarColor) + { + config.TitleBarColor = newColor; + MoveNormalTexture.SetPixel(0, 0, config.TitleBarColor); + MoveNormalTexture.Apply(); + + MoveHoverTexture.SetPixel(0, 0, config.TitleBarColor * 1.2f); + MoveHoverTexture.Apply(); + } + + config.TitleBarTextColor = DrawColorControl("Title bar text", config.TitleBarTextColor); + + config.GameObjectColor = DrawColorControl("GameObject", config.GameObjectColor); + config.EnabledComponentColor = DrawColorControl("Component (enabled)", config.EnabledComponentColor); + config.DisabledComponentColor = DrawColorControl("Component (disabled)", config.DisabledComponentColor); + config.SelectedComponentColor = DrawColorControl("Selected component", config.SelectedComponentColor); + config.KeywordColor = DrawColorControl("Keyword", config.KeywordColor); + config.NameColor = DrawColorControl("Member name", config.NameColor); + config.TypeColor = DrawColorControl("Member type", config.TypeColor); + config.ModifierColor = DrawColorControl("Member modifier", config.ModifierColor); + config.MemberTypeColor = DrawColorControl("Field type", config.MemberTypeColor); + config.ValueColor = DrawColorControl("Member value", config.ValueColor); + + GUILayout.FlexibleSpace(); + + GUILayout.BeginHorizontal(); + + GUILayout.FlexibleSpace(); + + if (GUILayout.Button("OK", GUILayout.Width(100))) + { + MainWindow.Instance.SaveConfig(); + Visible = false; + } + + if (GUILayout.Button("Defaults", GUILayout.Width(100))) + { + ResetDoDefault(config); + selectedFont = Array.IndexOf(availableFonts, config.FontName); + } + + GUILayout.EndHorizontal(); + } + + protected override void HandleException(Exception ex) + { + Logger.Error("Exception in AppearanceConfig - " + ex.Message); + Visible = false; + } + + private static void ResetDoDefault(ModConfiguration config) + { + var template = new ModConfiguration(); + + config.BackgroundColor = template.BackgroundColor; + BgTexture.SetPixel(0, 0, config.BackgroundColor); + BgTexture.Apply(); + + config.TitleBarColor = template.TitleBarColor; + MoveNormalTexture.SetPixel(0, 0, config.TitleBarColor); + MoveNormalTexture.Apply(); + + MoveHoverTexture.SetPixel(0, 0, config.TitleBarColor * 1.2f); + MoveHoverTexture.Apply(); + + config.TitleBarTextColor = template.TitleBarTextColor; + + config.GameObjectColor = template.GameObjectColor; + config.EnabledComponentColor = template.EnabledComponentColor; + config.DisabledComponentColor = template.DisabledComponentColor; + config.SelectedComponentColor = template.SelectedComponentColor; + config.NameColor = template.NameColor; + config.TypeColor = template.TypeColor; + config.ModifierColor = template.ModifierColor; + config.MemberTypeColor = template.MemberTypeColor; + config.ValueColor = template.ValueColor; + config.FontName = template.FontName; + config.FontSize = template.FontSize; + + UpdateFont(); + } + + private Color DrawColorControl(string name, Color value) + { + GUILayout.BeginHorizontal(); + GUILayout.Label(name); + GUILayout.FlexibleSpace(); + var newColor = GUIControls.CustomValueField(name, string.Empty, GUIControls.PresentColor, value); + GUILayout.EndHorizontal(); + return newColor; + } + } +} \ No newline at end of file diff --git a/Debugger/Configuration.cs b/Debugger/Configuration.cs deleted file mode 100644 index 600a4fd..0000000 --- a/Debugger/Configuration.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System; -using System.IO; -using System.Xml.Serialization; -using UnityEngine; - -namespace ModTools -{ - - public class Configuration - { - - public Rect mainWindowRect = new Rect(128, 128, 356, 300); - public bool mainWindowVisible = false; - - public Rect consoleRect = new Rect(16.0f, 16.0f, 512.0f, 256.0f); - public bool consoleVisible = false; - - public int consoleMaxHistoryLength = 1024; - public string consoleFormatString = "[{{type}}] {{caller}}: {{message}}"; - public bool showConsoleOnMessage = false; - public bool showConsoleOnWarning = false; - public bool showConsoleOnError = true; - public bool consoleAutoScrollToBottom = true; - - public bool customPrefabsObject = true; - - public Rect sceneExplorerRect = new Rect(128, 440, 800, 500); - public bool sceneExplorerVisible = false; - - public Rect watchesRect = new Rect(504, 128, 800, 300); - public bool watchesVisible = false; - - public bool logExceptionsToConsole = true; - public bool evaluatePropertiesAutomatically = true; - public bool extendGamePanels = true; - public bool useModToolsConsole = true; - - public Color backgroundColor = new Color(0.321f, 0.321f, 0.321f, 1.0f); - public Color titlebarColor = new Color(0.247f, 0.282f, 0.364f, 1.0f); - public Color titlebarTextColor = new Color(0.85f, 0.85f, 0.85f, 1.0f); - - public Color gameObjectColor = new Color(165.0f / 255.0f, 186.0f / 255.0f, 229.0f / 255.0f, 1.0f); - public Color enabledComponentColor = Color.white; - public Color disabledComponentColor = new Color(127.0f / 255.0f, 127.0f / 255.0f, 127.0f / 255.0f, 1.0f); - public Color selectedComponentColor = new Color(233.0f / 255.0f, 138.0f / 255.0f, 23.0f / 255.0f, 1.0f); - - public Color nameColor = new Color(148.0f / 255.0f, 196.0f / 255.0f, 238.0f / 255.0f, 1.0f); - public Color typeColor = new Color(58.0f / 255.0f, 179.0f / 255.0f, 58.0f / 255.0f, 1.0f); - public Color keywordColor = new Color(233.0f / 255.0f, 102.0f / 255.0f, 47.0f / 255.0f, 1.0f); - public Color modifierColor = new Color(84.0f / 255.0f, 109.0f / 255.0f, 57.0f / 255.0f, 1.0f); - public Color memberTypeColor = new Color(86.0f / 255.0f, 127.0f / 255.0f, 68.0f / 255.0f, 1.0f); - public Color valueColor = Color.white; - - public Color consoleMessageColor = Color.white; - public Color consoleWarningColor = Color.yellow; - public Color consoleErrorColor = new Color(0.7f, 0.1f, 0.1f, 1.0f); - public Color consoleExceptionColor = new Color(1.0f, 0.0f, 0.0f, 1.0f); - - public bool sceneExplorerShowFields = true; - public bool sceneExplorerShowProperties = true; - public bool sceneExplorerShowMethods = false; - public bool sceneExplorerShowModifiers = false; - public bool sceneExplorerShowInheritedMembers = false; - public bool sceneExplorerEvaluatePropertiesAutomatically = true; - public bool sceneExplorerSortAlphabetically = true; - public float sceneExplorerTreeIdentSpacing = 16.0f; - public int sceneExplorerMaxHierarchyDepth = 32; - - - public string scriptEditorWorkspacePath = ""; - - public string fontName = "Courier New Bold"; - public int fontSize = 14; - - public int hiddenNotifications = 0; - public int logLevel = 0; - - public void OnPreSerialize() - { - - } - - public void OnPostDeserialize() - { - - } - - public static void Serialize(string filename, Configuration config) - { - var serializer = new XmlSerializer(typeof(Configuration)); - - using (var writer = new StreamWriter(filename)) - { - config.OnPreSerialize(); - serializer.Serialize(writer, config); - } - } - - public static Configuration Deserialize(string filename) - { - var serializer = new XmlSerializer(typeof(Configuration)); - - try - { - using (var reader = new StreamReader(filename)) - { - var config = (Configuration) serializer.Deserialize(reader); - config.OnPostDeserialize(); - return config; - } - } - catch (Exception e) - { - UnityEngine.Debug.LogError("Error happened when deserializing config"); - UnityEngine.Debug.LogException(e); - } - - return null; - } - } - -} diff --git a/Debugger/Console/ConsoleMessage.cs b/Debugger/Console/ConsoleMessage.cs new file mode 100644 index 0000000..2fd905a --- /dev/null +++ b/Debugger/Console/ConsoleMessage.cs @@ -0,0 +1,27 @@ +using System.Diagnostics; +using UnityEngine; + +namespace ModTools.Console +{ + internal sealed class ConsoleMessage + { + public ConsoleMessage(string caller, string message, LogType type, StackTrace trace) + { + Caller = caller; + Message = message; + Type = type; + Count = 1; + Trace = trace; + } + + public string Caller { get; } + + public string Message { get; } + + public LogType Type { get; } + + public int Count { get; set; } + + public StackTrace Trace { get; } + } +} \ No newline at end of file diff --git a/Debugger/Console.cs b/Debugger/Console/CustomConsole.cs similarity index 56% rename from Debugger/Console.cs rename to Debugger/Console/CustomConsole.cs index e42f44d..a18726e 100644 --- a/Debugger/Console.cs +++ b/Debugger/Console/CustomConsole.cs @@ -4,63 +4,61 @@ using System.Linq; using System.Reflection; using ColossalFramework.UI; +using ModTools.Scripting; +using ModTools.UI; using UnityEngine; -namespace ModTools +namespace ModTools.Console { - - public class ConsoleMessage - { - public string caller; - public string message; - public LogType type; - public int count; - public StackTrace trace; - } - - public class Console : GUIWindow + internal sealed class CustomConsole : GUIWindow, ILogger, IGameObject { + public const string DefaultSource = @" +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System.IO; +using System.Linq; +using ColossalFramework.UI; +using UnityEngine; - private static Configuration config => ModTools.Instance.config; +namespace ModTools +{{ + class ModToolsCommandLineRunner : IModEntryPoint + {{ + public void OnModLoaded() + {{ + {0} + }} - private GUIArea headerArea; - private GUIArea consoleArea; - private GUIArea commandLineArea; - private bool focusCommandLineArea = false; - private bool emptyCommandLineArea = true; - private bool setCommandLinePosition = false; - private int commandLinePosition; + public void OnModUnloaded() + {{ + }} + }} +}}"; - private float headerHeightCompact = 0.5f; - private float headerHeightExpanded = 8.0f; - private bool headerExpanded = false; + private const float HeaderHeightCompact = 0.5f; + private const float HeaderHeightExpanded = 8.0f; + private const float CommandLineAreaHeightCompact = 45.0f; + private const float CommandLineAreaHeightExpanded = 120.0f; - private float commandLineAreaHeightCompact = 45.0f; - private float commandLineAreaHeightExpanded = 120.0f; + private readonly Color orangeColor = new Color(1.0f, 0.647f, 0.0f, 1.0f); - private bool commandLineAreaExpanded - { - get - { - var command = commandHistory[currentCommandHistoryIndex]; - if (command.Length == 0) - { - return false; - } + private readonly GUIArea headerArea; + private readonly GUIArea consoleArea; + private readonly GUIArea commandLineArea; - if (command.Contains('\n')) - { - return true; - } + private readonly object historyLock = new object(); + private readonly List history = new List(); + private readonly List commandHistory = new List() { string.Empty }; - return command.Length >= 64; - } - } + private bool focusCommandLineArea; + private bool emptyCommandLineArea = true; + private bool setCommandLinePosition; + private int commandLinePosition; + private bool headerExpanded; - private object historyLock = new object(); - private List history = new List(); - private List commandHistory = new List() { "" }; - private int currentCommandHistoryIndex = 0; + private int currentCommandHistoryIndex; private Vector2 consoleScrollPosition = Vector2.zero; private Vector2 commandLineScrollPosition = Vector2.zero; @@ -68,79 +66,35 @@ private bool commandLineAreaExpanded private DebugOutputPanel vanillaPanel; private Transform oldVanillaPanelParent; - private List> userNotifications; + private List> userNotifications; - public Console() : base("Debug console", config.consoleRect, skin) + public CustomConsole() + : base("Debug console", Config.ConsoleRect, Skin) { - onDraw = DrawWindow; - onException = HandleException; - onUnityDestroy = HandleDestroy; + headerArea = new GUIArea(this) + .OffsetBy(vertical: 16f) + .ChangeSizeRelative(height: 0); - headerArea = new GUIArea(this); consoleArea = new GUIArea(this); - commandLineArea = new GUIArea(this); - RecalculateAreas(); + commandLineArea = new GUIArea(this) + .ChangeSizeRelative(height: 0); - onUnityGUI = () => KeyboardCallback(); + RecalculateAreas(); } - void KeyboardCallback() + private static ModConfiguration Config => MainWindow.Instance.Config; + + private bool CommandLineAreaExpanded { - Event e = Event.current; - if (e.type != EventType.KeyUp || GUI.GetNameOfFocusedControl() != "ModToolsConsoleCommandLine") - { - return; - } - if (setCommandLinePosition) - { - // return has been hit with control pressed in previous GUI event - // reset the position to the remembered one - setCommandLinePosition = false; - TextEditor editor = (TextEditor)GUIUtility.GetStateObject(typeof(TextEditor), GUIUtility.keyboardControl); -#if OLDVERSION - editor.pos = editor.selectPos = commandLinePosition - 1; -#else - editor.cursorIndex = editor.selectIndex = commandLinePosition - 1; -#endif - } - if (e.keyCode == KeyCode.Return) + get { - if (e.shift) - { - return; - } - - // event.Use() does not consume the event, work around the enter being inserted into the textbox by deleting the line break - TextEditor editor = (TextEditor)GUIUtility.GetStateObject(typeof(TextEditor), GUIUtility.keyboardControl); -#if OLDVERSION - int pos = editor.selectPos; -#else - int pos = editor.selectIndex; -#endif - String currentCommand = commandHistory[currentCommandHistoryIndex]; - String fixedCommand = currentCommand.Substring(0, pos - 1) + currentCommand.Substring(pos, currentCommand.Length - pos); - commandHistory[currentCommandHistoryIndex] = fixedCommand; - - // if control is pressed when hitting return, do not empty the command line area - // remember the currently selected position and reset it after the next redraw - if (e.control) - { - emptyCommandLineArea = false; - setCommandLinePosition = true; - commandLinePosition = pos; - } - - RunCommandLine(); + var command = commandHistory[currentCommandHistoryIndex]; + return command.Length == 0 ? false : command.Contains('\n') || command.Length >= 64; } } - void HandleDestroy() - { - vanillaPanel.transform.parent = oldVanillaPanelParent; - vanillaPanel = null; - } - void Update() + public void Update() { if (vanillaPanel == null) { @@ -149,117 +103,184 @@ void Update() { return; } + vanillaPanel = panel; oldVanillaPanelParent = vanillaPanel.transform.parent; vanillaPanel.transform.parent = transform; } } - public void AddMessage(string message, LogType type = LogType.Log, bool _internal = false) + public void Log(string message, LogType type) { lock (historyLock) { if (history.Count > 0) { - var last = history.Last(); - if (message == last.message && type == last.type) + var lastMessage = history[history.Count - 1]; + if (message == lastMessage.Message && type == lastMessage.Type) { - last.count++; + lastMessage.Count++; return; } } } - string caller = ""; + var caller = string.Empty; - StackTrace trace = new StackTrace(_internal ? 0 : 8); + var trace = new StackTrace(8); - if (!_internal) + for (var i = 0; i < trace.FrameCount; i++) { - int i; - for (i = 0; i < trace.FrameCount; i++) - { - MethodBase callingMethod = null; + MethodBase callingMethod = null; - var frame = trace.GetFrame(i); - if (frame != null) - { - callingMethod = frame.GetMethod(); - } + var frame = trace.GetFrame(i); + if (frame != null) + { + callingMethod = frame.GetMethod(); + } - if (callingMethod == null) - { - continue; - } - caller = callingMethod.DeclaringType != null ? $"{callingMethod.DeclaringType}.{callingMethod.Name}()" : $"{callingMethod}()"; - break; + if (callingMethod == null) + { + continue; } - } - else - { - caller = "ModTools"; + + caller = callingMethod.DeclaringType != null ? $"{callingMethod.DeclaringType}.{callingMethod.Name}()" : $"{callingMethod}()"; + break; } lock (historyLock) { - history.Add(new ConsoleMessage() { count = 1, caller = caller, message = message, type = type, trace = trace }); + history.Add(new ConsoleMessage(caller, message, type, trace)); - if (history.Count >= config.consoleMaxHistoryLength) + if (history.Count >= Config.ConsoleMaxHistoryLength) { history.RemoveAt(0); } } - if (type == LogType.Log && config.showConsoleOnMessage) + if (type == LogType.Log && Config.ShowConsoleOnMessage) { - visible = true; + Visible = true; } - else if (type == LogType.Warning && config.showConsoleOnWarning) + else if (type == LogType.Warning && Config.ShowConsoleOnWarning) { - visible = true; + Visible = true; } - else if ((type == LogType.Exception || type == LogType.Error) && config.showConsoleOnError) + else if ((type == LogType.Exception || type == LogType.Error) && Config.ShowConsoleOnError) { - visible = true; + Visible = true; } - if (config.consoleAutoScrollToBottom) + if (Config.ConsoleAutoScrollToBottom) { consoleScrollPosition.y = float.MaxValue; } } - void RecalculateAreas() + public void DrawHeader() { - float headerHeight = headerExpanded ? headerHeightExpanded : headerHeightCompact; - headerHeight *= config.fontSize; - headerHeight += 32.0f; + headerArea.Begin(); + + if (headerExpanded) + { + DrawExpandedHeader(); + } + else + { + DrawCompactHeader(); + } + + headerArea.End(); + } + + protected override void DrawWindow() + { + RecalculateAreas(); + DrawHeader(); + DrawConsole(); + DrawCommandLineArea(); + } + + protected override void OnWindowDrawn() + { + var e = Event.current; + if (e.type != EventType.KeyUp || GUI.GetNameOfFocusedControl() != "ModToolsConsoleCommandLine") + { + return; + } - headerArea.relativeSize.x = 1.0f; - headerArea.absolutePosition.y = 16.0f; - headerArea.absoluteSize.y = headerHeight; + if (setCommandLinePosition) + { + // return has been hit with control pressed in previous GUI event + // reset the position to the remembered one + setCommandLinePosition = false; + var editor = (TextEditor)GUIUtility.GetStateObject(typeof(TextEditor), GUIUtility.keyboardControl); + editor.selectIndex = commandLinePosition - 1; + editor.cursorIndex = editor.selectIndex; + } - var commandLineAreaHeight = commandLineAreaExpanded - ? commandLineAreaHeightExpanded - : commandLineAreaHeightCompact; + if (e.keyCode == KeyCode.Return) + { + if (e.shift) + { + return; + } + + // event.Use() does not consume the event, work around the enter being inserted into the textbox by + // deleting the line break + var editor = (TextEditor)GUIUtility.GetStateObject(typeof(TextEditor), GUIUtility.keyboardControl); + var pos = editor.selectIndex; + var currentCommand = commandHistory[currentCommandHistoryIndex]; + commandHistory[currentCommandHistoryIndex] + = currentCommand.Substring(0, pos - 1) + currentCommand.Substring(pos, currentCommand.Length - pos); + + // if control is pressed when hitting return, do not empty the command line area + // remember the currently selected position and reset it after the next redraw + if (e.control) + { + emptyCommandLineArea = false; + setCommandLinePosition = true; + commandLinePosition = pos; + } - consoleArea.absolutePosition.y = 16.0f + headerHeight; - consoleArea.relativeSize.x = 1.0f; - consoleArea.relativeSize.y = 1.0f; - consoleArea.absoluteSize.y = -(commandLineAreaHeight + headerHeight + 16.0f); + RunCommandLine(); + } + } - commandLineArea.relativePosition.y = 1.0f; - commandLineArea.absolutePosition.y = -commandLineAreaHeight; - commandLineArea.relativeSize.x = 1.0f; - commandLineArea.absoluteSize.y = commandLineAreaHeight; + protected override void OnWindowDestroyed() + { + if (vanillaPanel != null) + { + vanillaPanel.transform.parent = oldVanillaPanelParent; + vanillaPanel = null; + } } - void HandleException(Exception ex) + protected override void HandleException(Exception ex) => Log("Exception in ModTools Console - " + ex.Message, LogType.Exception); + + private void RecalculateAreas() { - AddMessage("Exception in ModTools Console - " + ex.Message, LogType.Exception); + var headerHeight = headerExpanded ? HeaderHeightExpanded : HeaderHeightCompact; + headerHeight *= Config.FontSize; + headerHeight += 32.0f; + + headerArea.ChangeSizeBy(height: headerHeight); + + var commandLineAreaHeight = CommandLineAreaExpanded + ? CommandLineAreaHeightExpanded + : CommandLineAreaHeightCompact; + + consoleArea + .OffsetBy(vertical: 16f + headerHeight) + .ChangeSizeBy(height: -(commandLineAreaHeight + headerHeight + 16f)); + + commandLineArea + .OffsetRelative(vertical: 1f) + .OffsetBy(vertical: -commandLineAreaHeight) + .ChangeSizeBy(height: commandLineAreaHeight); } - void DrawCompactHeader() + private void DrawCompactHeader() { GUILayout.BeginHorizontal(); @@ -284,36 +305,36 @@ void DrawCompactHeader() GUILayout.EndHorizontal(); } - void DrawExpandedHeader() + private void DrawExpandedHeader() { GUILayout.BeginHorizontal(); GUILayout.Label("Log message format:", GUILayout.ExpandWidth(false)); - config.consoleFormatString = GUILayout.TextField(config.consoleFormatString, GUILayout.ExpandWidth(true)); + Config.ConsoleFormatString = GUILayout.TextField(Config.ConsoleFormatString, GUILayout.ExpandWidth(true)); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); GUILayout.Label("Max items in history:", GUILayout.ExpandWidth(false)); - GUIControls.IntField("ConsoleMaxItemsInHistory", "", ref config.consoleMaxHistoryLength, 0.0f, true, true); + Config.ConsoleMaxHistoryLength = GUIControls.PrimitiveValueField("ConsoleMaxItemsInHistory", string.Empty, Config.ConsoleMaxHistoryLength); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); GUILayout.Label("Show console on:", GUILayout.ExpandWidth(false)); GUILayout.FlexibleSpace(); GUILayout.Label("Message", GUILayout.ExpandWidth(false)); - config.showConsoleOnMessage = GUILayout.Toggle(config.showConsoleOnMessage, "", GUILayout.ExpandWidth(false)); + Config.ShowConsoleOnMessage = GUILayout.Toggle(Config.ShowConsoleOnMessage, string.Empty, GUILayout.ExpandWidth(false)); GUILayout.FlexibleSpace(); GUILayout.Label("Warning", GUILayout.ExpandWidth(false)); - config.showConsoleOnWarning = GUILayout.Toggle(config.showConsoleOnWarning, "", GUILayout.ExpandWidth(false)); + Config.ShowConsoleOnWarning = GUILayout.Toggle(Config.ShowConsoleOnWarning, string.Empty, GUILayout.ExpandWidth(false)); GUILayout.FlexibleSpace(); GUILayout.Label("Error", GUILayout.ExpandWidth(false)); - config.showConsoleOnError = GUILayout.Toggle(config.showConsoleOnError, "", GUILayout.ExpandWidth(false)); - + Config.ShowConsoleOnError = GUILayout.Toggle(Config.ShowConsoleOnError, string.Empty, GUILayout.ExpandWidth(false)); + GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); - + GUILayout.BeginHorizontal(); GUILayout.Label("Auto-scroll on new messages:"); - config.consoleAutoScrollToBottom = GUILayout.Toggle(config.consoleAutoScrollToBottom, ""); + Config.ConsoleAutoScrollToBottom = GUILayout.Toggle(Config.ConsoleAutoScrollToBottom, string.Empty); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); @@ -331,19 +352,19 @@ void DrawExpandedHeader() if (GUILayout.Button("Save")) { - ModTools.Instance.SaveConfig(); + MainWindow.Instance.SaveConfig(); } if (GUILayout.Button("Reset")) { - var template = new Configuration(); - config.consoleMaxHistoryLength = template.consoleMaxHistoryLength; - config.consoleFormatString = template.consoleFormatString; - config.showConsoleOnMessage = template.showConsoleOnMessage; - config.showConsoleOnWarning = template.showConsoleOnWarning; - config.showConsoleOnError = template.showConsoleOnError; + var template = new ModConfiguration(); + Config.ConsoleMaxHistoryLength = template.ConsoleMaxHistoryLength; + Config.ConsoleFormatString = template.ConsoleFormatString; + Config.ShowConsoleOnMessage = template.ShowConsoleOnMessage; + Config.ShowConsoleOnWarning = template.ShowConsoleOnWarning; + Config.ShowConsoleOnError = template.ShowConsoleOnError; - ModTools.Instance.SaveConfig(); + MainWindow.Instance.SaveConfig(); } if (GUILayout.Button("Clear", GUILayout.ExpandWidth(false))) @@ -357,25 +378,7 @@ void DrawExpandedHeader() GUILayout.EndHorizontal(); } - public void DrawHeader() - { - headerArea.Begin(); - - if (headerExpanded) - { - DrawExpandedHeader(); - } - else - { - DrawCompactHeader(); - } - - headerArea.End(); - } - - private Color orangeColor = new Color(1.0f, 0.647f, 0.0f, 1.0f); - - void DrawConsole() + private void DrawConsole() { userNotifications = UserNotifications.GetNotifications(); @@ -385,7 +388,7 @@ void DrawConsole() foreach (var item in userNotifications) { - GUILayout.BeginHorizontal(skin.box); + GUILayout.BeginHorizontal(Skin.box); GUI.contentColor = Color.cyan; GUILayout.Label(item.Value); @@ -406,28 +409,31 @@ void DrawConsole() messages = history.ToArray(); } - foreach (ConsoleMessage item in messages) + foreach (var item in messages) { - GUILayout.BeginHorizontal(skin.box); + GUILayout.BeginHorizontal(Skin.box); - string msg = config.consoleFormatString.Replace("{{type}}", item.type.ToString()) - .Replace("{{caller}}", item.caller) - .Replace("{{message}}", item.message); + var msg = Config.ConsoleFormatString.Replace("{{type}}", item.Type.ToString()) + .Replace("{{caller}}", item.Caller) + .Replace("{{message}}", item.Message); - switch (item.type) + switch (item.Type) { case LogType.Log: - GUI.contentColor = config.consoleMessageColor; + GUI.contentColor = Config.ConsoleMessageColor; break; + case LogType.Warning: - GUI.contentColor = config.consoleWarningColor; + GUI.contentColor = Config.ConsoleWarningColor; break; + case LogType.Error: - GUI.contentColor = config.consoleErrorColor; + GUI.contentColor = Config.ConsoleErrorColor; break; + case LogType.Assert: case LogType.Exception: - GUI.contentColor = config.consoleExceptionColor; + GUI.contentColor = Config.ConsoleExceptionColor; break; } @@ -435,24 +441,24 @@ void DrawConsole() GUILayout.FlexibleSpace(); - if (item.count > 1) + if (item.Count > 1) { GUI.contentColor = orangeColor; - if (item.count > 1024) + if (item.Count > 1024) { GUI.contentColor = Color.red; } - GUILayout.Label(item.count.ToString(), skin.box); + GUILayout.Label(item.Count.ToString(), Skin.box); } else { - GUILayout.Label(""); + GUILayout.Label(string.Empty); } GUI.contentColor = Color.white; - var stackTrace = item.trace; + var stackTrace = item.Trace; if (stackTrace != null) { GUIStackTrace.StackTraceButton(stackTrace); @@ -466,7 +472,7 @@ void DrawConsole() consoleArea.End(); } - void DrawCommandLineArea() + private void DrawCommandLineArea() { commandLineArea.Begin(); @@ -483,6 +489,7 @@ void DrawCommandLineArea() { GUI.enabled = false; } + GUILayout.EndScrollView(); GUILayout.EndVertical(); @@ -522,7 +529,6 @@ void DrawCommandLineArea() GUILayout.EndVertical(); GUILayout.EndHorizontal(); - commandLineArea.End(); // refocus the command line area after enter has been pressed @@ -533,15 +539,15 @@ void DrawCommandLineArea() } } - void RunCommandLine() + private void RunCommandLine() { var commandLine = commandHistory[currentCommandHistoryIndex]; if (emptyCommandLineArea) { - if (commandHistory.Last() != "") + if (commandHistory.Count > 0 && !string.IsNullOrEmpty(commandHistory[commandHistory.Count - 1])) { - commandHistory.Add(""); + commandHistory.Add(string.Empty); currentCommandHistoryIndex = commandHistory.Count - 1; } else @@ -549,64 +555,26 @@ void RunCommandLine() currentCommandHistoryIndex = commandHistory.Count - 1; } } + emptyCommandLineArea = true; - var source = String.Format(defaultSource, commandLine); - var file = new ScriptEditorFile() { path = "ModToolsCommandLineScript.cs", source = source }; - string errorMessage; - IModEntryPoint instance; - if (!ScriptCompiler.RunSource(new List() { file }, out errorMessage, out instance)) + var source = string.Format(DefaultSource, commandLine); + var file = new ScriptEditorFile(source, "ModToolsCommandLineScript.cs"); + if (!ScriptCompiler.RunSource(new List() { file }, out _, out var instance)) { - Log.Error("Failed to compile command-line!"); + Logger.Error("Failed to compile command-line!"); + } + else if (instance != null) + { + Logger.Message("Executing command-line.."); + instance.OnModLoaded(); } else { - if (instance != null) - { - Log.Message("Executing command-line.."); - instance.OnModLoaded(); - } - else - { - Log.Error("Error executing command-line.."); - } + Logger.Error("Error executing command-line.."); } - commandLine = ""; - focusCommandLineArea = true; - } - void DrawWindow() - { - RecalculateAreas(); - DrawHeader(); - DrawConsole(); - DrawCommandLineArea(); + focusCommandLineArea = true; } - - private readonly string defaultSource = @" -using System; -using System.Collections; -using System.Collections.Generic; -using System.Reflection; -using System.IO; -using System.Linq; -using ColossalFramework.UI; -using UnityEngine; - -namespace ModTools -{{ - class ModToolsCommandLineRunner : IModEntryPoint - {{ - public void OnModLoaded() - {{ - {0} - }} - - public void OnModUnloaded() - {{ - }} - }} -}}"; - } -} +} \ No newline at end of file diff --git a/Debugger/StackTraceViewer.cs b/Debugger/Console/StackTraceViewer.cs similarity index 60% rename from Debugger/StackTraceViewer.cs rename to Debugger/Console/StackTraceViewer.cs index 86a6875..17bf287 100644 --- a/Debugger/StackTraceViewer.cs +++ b/Debugger/Console/StackTraceViewer.cs @@ -1,48 +1,37 @@ using System; using System.Diagnostics; +using ModTools.UI; using UnityEngine; -namespace ModTools +namespace ModTools.Console { - public class StackTraceViewer : GUIWindow + internal sealed class StackTraceViewer : GUIWindow { + private StackTrace trace; + + private Vector2 scrollPos = Vector2.zero; - private static Configuration config + private StackTraceViewer() + : base("Stack-trace viewer", new Rect(16.0f, 16.0f, 512.0f, 256.0f), Skin) { - get { return ModTools.Instance.config; } } - private StackTrace trace; - - private Vector2 scrollPos = Vector2.zero; + private static ModConfiguration Config => MainWindow.Instance.Config; public static StackTraceViewer CreateStackTraceViewer(StackTrace trace) { var go = new GameObject("StackTraceViewer"); - go.transform.parent = ModTools.Instance.transform; + go.transform.parent = MainWindow.Instance.transform; var viewer = go.AddComponent(); viewer.trace = trace; return viewer; } - private StackTraceViewer() : base("Stack-trace viewer", new Rect(16.0f, 16.0f, 512.0f, 256.0f), skin) - { - onDraw = DrawWindow; - onException = HandleException; - onClose = HandleClosed; - } - - void HandleClosed() - { - Destroy(this); - } + protected override void OnWindowClosed() => Destroy(this); - void HandleException(Exception ex) - { - Log.Error("Exception in StackTraceViewer - " + ex.Message); - } + protected override void HandleException(Exception ex) => Logger.Error("Exception in StackTraceViewer - " + ex.Message); - void DrawWindow() + protected override void DrawWindow() { if (trace == null) { @@ -57,19 +46,19 @@ void DrawWindow() scrollPos = GUILayout.BeginScrollView(scrollPos); - int count = 0; + var count = 0; foreach (var frame in stackFrames) { - GUILayout.BeginHorizontal(skin.box); + GUILayout.BeginHorizontal(Skin.box); var method = frame.GetMethod(); GUILayout.Label(count.ToString(), GUILayout.ExpandWidth(false)); - - GUI.contentColor = config.nameColor; + + GUI.contentColor = Config.NameColor; GUILayout.Label(method.ToString(), GUILayout.ExpandWidth(false)); - GUI.contentColor = config.typeColor; + GUI.contentColor = Config.TypeColor; if (method.DeclaringType != null) { @@ -84,7 +73,5 @@ void DrawWindow() GUILayout.EndScrollView(); } - } - -} +} \ No newline at end of file diff --git a/Debugger/CustomPrefabs.cs b/Debugger/CustomPrefabs.cs index 913f6d7..8560dad 100644 --- a/Debugger/CustomPrefabs.cs +++ b/Debugger/CustomPrefabs.cs @@ -5,20 +5,26 @@ namespace ModTools { - public class CustomPrefabs : MonoBehaviour + [System.Diagnostics.CodeAnalysis.SuppressMessage("Code Quality", "IDE0052", Justification = "Intended for self-reflection in-game")] + internal sealed class CustomPrefabs : MonoBehaviour, IDestroyableObject, IAwakingObject { - - private static bool bootstrapped = false; private static GameObject thisGameObject; - public VehicleInfo[] m_vehicles; - public BuildingInfo[] m_buildings; - public PropInfo[] m_props; - public TreeInfo[] m_trees; - public NetInfo[] m_nets; - public EventInfo[] m_events; - public TransportInfo[] m_transports; - public CitizenInfo[] m_citizens; + private VehicleInfo[] vehicles; + + private BuildingInfo[] buildings; + + private PropInfo[] props; + + private TreeInfo[] trees; + + private NetInfo[] nets; + + private EventInfo[] events; + + private TransportInfo[] transports; + + private CitizenInfo[] citizens; public static void Bootstrap() { @@ -27,11 +33,6 @@ public static void Bootstrap() thisGameObject = new GameObject("Custom Prefabs"); thisGameObject.AddComponent(); } - if (bootstrapped) - { - return; - } - bootstrapped = true; } public static void Revert() @@ -41,53 +42,50 @@ public static void Revert() Destroy(thisGameObject); thisGameObject = null; } - - if (!bootstrapped) - { - return; - } - bootstrapped = false; } - public void Awake() { - m_vehicles = GetCustomPrefabs(); - m_buildings = GetCustomPrefabs(); - m_props = GetCustomPrefabs(); - m_trees = GetCustomPrefabs(); - m_nets = GetCustomPrefabs(); - m_events = GetCustomPrefabs(); - m_transports = GetCustomPrefabs(); - m_citizens = GetCustomPrefabs(); + vehicles = GetCustomPrefabs(); + buildings = GetCustomPrefabs(); + props = GetCustomPrefabs(); + trees = GetCustomPrefabs(); + nets = GetCustomPrefabs(); + events = GetCustomPrefabs(); + transports = GetCustomPrefabs(); + citizens = GetCustomPrefabs(); } public void OnDestroy() { - m_vehicles = null; - m_buildings = null; - m_props = null; - m_trees = null; - m_nets = null; - m_events = null; - m_transports = null; - m_citizens = null; + vehicles = null; + buildings = null; + props = null; + trees = null; + nets = null; + events = null; + transports = null; + citizens = null; } - private static T[] GetCustomPrefabs() where T: PrefabInfo + private static T[] GetCustomPrefabs() + where T : PrefabInfo { - var result = new List(); var count = PrefabCollection.LoadedCount(); + var result = new List(count); for (uint i = 0; i < count; i++) { var prefab = PrefabCollection.GetPrefab(i); - if(prefab == null || (!prefab.m_isCustomContent && prefab.name != null && !prefab.name.Contains('.'))) + if (prefab == null || !prefab.m_isCustomContent && prefab.name?.Contains('.') == false) { continue; } + result.Add(prefab); } - return result.OrderBy(p => p.name).ToArray(); - } + + result.Sort((x, y) => string.CompareOrdinal(x?.name, y?.name)); + return result.ToArray(); + } } } \ No newline at end of file diff --git a/Debugger/DebugRenderer.cs b/Debugger/DebugRenderer.cs index 7f82af3..4e77616 100644 --- a/Debugger/DebugRenderer.cs +++ b/Debugger/DebugRenderer.cs @@ -1,67 +1,68 @@ using System; using System.Collections.Generic; -using System.Linq; using ColossalFramework.UI; +using ModTools.Explorer; +using ModTools.Utils; using UnityEngine; namespace ModTools { - public class DebugRenderer : MonoBehaviour + internal sealed class DebugRenderer : MonoBehaviour, IGameObject, IUIObject { - - public bool drawDebugInfo = false; + private readonly List hoveredComponents = new List(); private GUIStyle normalRectStyle; private GUIStyle hoveredRectStyle; private GUIStyle infoWindowStyle; private UIComponent hoveredComponent; - private readonly List hoveredComponents = new List(); + private long previousHash = 0; - void Update() + public bool DrawDebugInfo { get; set; } + + public void Update() { var hoveredLocal = hoveredComponent; - if (drawDebugInfo && hoveredLocal != null) + if (!DrawDebugInfo || hoveredLocal == null) { - if (Input.GetKey(KeyCode.LeftControl) && Input.GetKeyDown(KeyCode.F)) + return; + } + + if (Input.GetKey(KeyCode.LeftControl) && Input.GetKeyDown(KeyCode.F)) + { + var uiView = FindObjectOfType(); + if (uiView == null) { - var uiView = FindObjectOfType(); - if (uiView == null) - { - return; - } - - var current = hoveredLocal; - var refChain = new ReferenceChain(); - refChain = refChain.Add(current); - while (current != null) - { - refChain = refChain.Add(current.gameObject); - current = current.parent; - } - ; - refChain = refChain.Add(uiView.gameObject); - - var sceneExplorer = FindObjectOfType(); - sceneExplorer.ExpandFromRefChain(refChain.Reverse); - sceneExplorer.visible = true; + return; } - if (Input.GetKey(KeyCode.LeftControl) && Input.GetKeyDown(KeyCode.G)) + + var current = hoveredLocal; + var refChain = new ReferenceChain(); + refChain = refChain.Add(current); + while (current != null) { - if (hoveredComponents.Count > 1 && hoveredComponent != null) - { - var index = hoveredComponents.IndexOf(hoveredComponent); - var newIndex = (index + hoveredComponents.Count + 1) % hoveredComponents.Count; - hoveredComponent = hoveredComponents[newIndex]; - } + refChain = refChain.Add(current.gameObject); + current = current.parent; } + + refChain = refChain.Add(uiView.gameObject); + + var sceneExplorer = FindObjectOfType(); + sceneExplorer.Show(refChain.GetReversedCopy()); + } + + if (Input.GetKey(KeyCode.LeftControl) && Input.GetKeyDown(KeyCode.G) && hoveredComponents.Count > 1 && hoveredComponent != null) + { + var index = hoveredComponents.IndexOf(hoveredComponent); + var newIndex = (index + hoveredComponents.Count + 1) % hoveredComponents.Count; + hoveredComponent = hoveredComponents[newIndex]; } } - void OnGUI() + public void OnGUI() { - if (!drawDebugInfo) + if (!DrawDebugInfo) { return; } @@ -91,7 +92,7 @@ void OnGUI() normal = { background = null }, hover = { background = null }, active = { background = null }, - focused = { background = null } + focused = { background = null }, }; } @@ -102,7 +103,7 @@ void OnGUI() return; } - UIComponent[] components = GetComponentsInChildren(); + var components = GetComponentsInChildren(); Array.Sort(components, RenderSortFunc); var mouse = Input.mousePosition; @@ -110,7 +111,7 @@ void OnGUI() hoveredComponents.Clear(); long hash = 0; - for (int i = components.Length - 1; i > 0; i--) + for (var i = components.Length - 1; i > 0; i--) { var component = components[i]; @@ -138,17 +139,18 @@ void OnGUI() hoveredComponents.Add(component); } } + if (hoveredComponent != null && hash != previousHash) { hoveredComponent = null; previousHash = hash; } + if (hoveredComponent == null && hoveredComponents.Count > 0) { - hoveredComponent = hoveredComponents.First(); + hoveredComponent = hoveredComponents[0]; } - foreach (var component in components) { if (!component.isVisible) @@ -160,7 +162,7 @@ void OnGUI() var size = component.size; var rect = CalculateRealComponentRect(position, size); - GUI.Box(rect, "", hoveredComponent == component ? hoveredRectStyle : normalRectStyle); + GUI.Box(rect, string.Empty, hoveredComponent == component ? hoveredRectStyle : normalRectStyle); } if (hoveredComponent != null) @@ -179,11 +181,11 @@ void OnGUI() coords.y = Screen.height - size.y; } - GUI.Window(81871, new Rect(coords.x, coords.y, size.x, size.y), DoInfoWindow, "", infoWindowStyle); + GUI.Window(81871, new Rect(coords.x, coords.y, size.x, size.y), DoInfoWindow, string.Empty, infoWindowStyle); } } - Rect CalculateRealComponentRect(Vector3 absolutePosition, Vector2 size) + private static Rect CalculateRealComponentRect(Vector3 absolutePosition, Vector2 size) { var dx = Screen.width / 1920.0f; var dy = Screen.height / 1080.0f; @@ -194,12 +196,16 @@ Rect CalculateRealComponentRect(Vector3 absolutePosition, Vector2 size) return new Rect(absolutePosition.x, absolutePosition.y, size.x, size.y); } - void DoInfoWindow(int i) + private static long CalculateHash(UIComponent c) + => HashUtil.HashRect(new Rect(c.relativePosition.x, c.relativePosition.y, c.size.x, c.size.y)); + + private void DoInfoWindow(int i) { if (hoveredComponent == null) { return; } + GUI.color = Color.red; GUILayout.Label("[Press Ctrl+F to open it in SceneExplorer]"); GUI.color = Color.blue; @@ -222,32 +228,25 @@ void DoInfoWindow(int i) { GUILayout.Label($"atlas.name: {interactiveComponent.atlas.name}"); } + var sprite = hoveredComponent as UISprite; if (sprite != null) { GUILayout.Label($"atlas.name: {sprite.atlas?.name}"); GUILayout.Label($"spriteName: {sprite.spriteName}"); } + var textureSprite = hoveredComponent as UITextureSprite; if (textureSprite != null) { GUILayout.Label($"texture.name: {textureSprite.texture?.name}"); } + GUILayout.Label($"zOrder: {hoveredComponent.zOrder}"); var hash = CalculateHash(hoveredComponent); GUILayout.Label($"hash: {HashUtil.HashToString(hash)}"); } - private long CalculateHash(UIComponent c) - { - return HashUtil.HashRect(new Rect(c.relativePosition.x, c.relativePosition.y, - c.size.x, c.size.y)); - } - - private int RenderSortFunc(UIComponent lhs, UIComponent rhs) - { - return lhs.renderOrder.CompareTo(rhs.renderOrder); - } - + private int RenderSortFunc(UIComponent lhs, UIComponent rhs) => lhs.renderOrder.CompareTo(rhs.renderOrder); } } \ No newline at end of file diff --git a/Debugger/Explorer/GUIButtons.cs b/Debugger/Explorer/GUIButtons.cs new file mode 100644 index 0000000..4def68a --- /dev/null +++ b/Debugger/Explorer/GUIButtons.cs @@ -0,0 +1,205 @@ +using System; +using ModTools.Utils; +using UnityEngine; + +namespace ModTools.Explorer +{ + internal static class GUIButtons + { + private static object buffer; + + public static void SetupButtons(ReferenceChain refChain, Type type, object value, int valueIndex) + { + switch (value) + { + case null: + return; + + case PrefabInfo prefabInfo: + SetupButtonsForPrefab(prefabInfo); + goto default; + + case MilestoneInfo milestoneInfo: + if (GUILayout.Button("Unlock")) + { + var wrapper = new MilestonesWrapper(UnlockManager.instance); + wrapper.UnlockMilestone(milestoneInfo.name); + } + + return; + + case NetInfo.Segment segmentInfo: + SetupMeshPreviewButton(name: null, segmentInfo.m_mesh, segmentInfo.m_material, segmentInfo.m_lodMesh, segmentInfo.m_lodMaterial); + goto default; + + case NetInfo.Node nodeInfo: + SetupMeshPreviewButton(name: null, nodeInfo.m_mesh, nodeInfo.m_material, nodeInfo.m_lodMesh, nodeInfo.m_lodMaterial); + goto default; + + case CitizenInstance instance + when valueIndex > 0 && (instance.m_flags & (CitizenInstance.Flags.Created | CitizenInstance.Flags.Deleted)) == CitizenInstance.Flags.Created: + + InstanceID citizenInstanceInst = default; + citizenInstanceInst.CitizenInstance = (ushort)valueIndex; + SetupGotoButton(citizenInstanceInst, instance.GetLastFramePosition()); + goto default; + + case Citizen citizen when citizen.m_instance > 0: + ref var citizenInstance = ref CitizenManager.instance.m_instances.m_buffer[citizen.m_instance]; + if ((citizenInstance.m_flags & (CitizenInstance.Flags.Created | CitizenInstance.Flags.Deleted)) == CitizenInstance.Flags.Created) + { + InstanceID citizenInstanceInst2 = default; + citizenInstanceInst2.CitizenInstance = citizen.m_instance; + SetupGotoButton(citizenInstanceInst2, citizenInstance.GetLastFramePosition()); + } + + goto default; + + case Vehicle vehicle when valueIndex > 0 && (vehicle.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) == Vehicle.Flags.Created: + InstanceID vehicleInst = default; + vehicleInst.Vehicle = (ushort)valueIndex; + SetupGotoButton(vehicleInst, vehicle.GetLastFramePosition()); + break; + + case VehicleParked parkedVehicle when valueIndex > 0 && (parkedVehicle.m_flags & 3) == 1: + InstanceID parkedVehicleInst = default; + parkedVehicleInst.ParkedVehicle = (ushort)valueIndex; + SetupGotoButton(parkedVehicleInst, parkedVehicle.m_position); + goto default; + + case Building building when valueIndex > 0 && (building.m_flags & (Building.Flags.Created | Building.Flags.Deleted)) == Building.Flags.Created: + InstanceID buildingInst = default; + buildingInst.Building = (ushort)valueIndex; + SetupGotoButton(buildingInst, building.m_position); + goto default; + + default: + if (GUILayout.Button("Copy")) + { + buffer = value; + } + + return; + } + + SetupTextureOrMeshButtons(type, value, refChain); + + if (GUILayout.Button("Copy")) + { + buffer = value; + } + } + + public static bool SetupPasteButon(Type type, out object paste) + { + paste = null; + if (buffer != null && type.IsInstanceOfType(buffer) && GUILayout.Button("Paste")) + { + paste = buffer; + return true; + } + + return GUILayout.Button("Unset"); + } + + private static void SetupButtonsForPrefab(PrefabInfo prefabInfo) + { + switch (prefabInfo) + { + case VehicleInfo vehicleInfo: + SetupMeshPreviewButton(vehicleInfo.name, vehicleInfo.m_mesh, vehicleInfo.m_material, vehicleInfo.m_lodMesh, vehicleInfo.m_lodMaterial); + break; + + case NetInfo _: + SetupPlopButton(prefabInfo); + break; + + case BuildingInfo buildingInfo: + SetupPlopButton(prefabInfo); + SetupMeshPreviewButton(buildingInfo.name, buildingInfo.m_mesh, buildingInfo.m_material, buildingInfo.m_lodMesh, buildingInfo.m_lodMaterial); + break; + + case PropInfo propInfo: + SetupPlopButton(prefabInfo); + SetupMeshPreviewButton(propInfo.name, propInfo.m_mesh, propInfo.m_material, propInfo.m_lodMesh, propInfo.m_lodMaterial); + break; + + case TreeInfo treeInfo: + SetupPlopButton(prefabInfo); + SetupMeshPreviewButton(treeInfo.name, treeInfo.m_mesh, treeInfo.m_material, lodMesh: null, lodMaterial: null); + break; + + case CitizenInfo citizenInfo: + SetupMeshPreviewButton( + citizenInfo.name, + citizenInfo.m_skinRenderer?.sharedMesh, + citizenInfo.m_skinRenderer?.material, + citizenInfo.m_lodMesh, + citizenInfo.m_lodMaterial); + break; + } + } + + private static void SetupMeshPreviewButton(string name, Mesh mesh, Material material, Mesh lodMesh, Material lodMaterial) + { + if (mesh != null && GUILayout.Button("Preview")) + { + MeshViewer.CreateMeshViewer(name, mesh, material); + } + + if (lodMesh != null && GUILayout.Button("Preview LOD")) + { + MeshViewer.CreateMeshViewer(name + "_LOD", lodMesh, lodMaterial); + } + } + + private static void SetupTextureOrMeshButtons(Type type, object value, ReferenceChain refChain) + { + if (TypeUtil.IsTextureType(type)) + { + var texture = (Texture)value; + if (GUILayout.Button("Preview")) + { + TextureViewer.CreateTextureViewer(texture); + } + + if (GUILayout.Button("Dump .png")) + { + TextureUtil.DumpTextureToPNG(texture); + } + } + else if (TypeUtil.IsMeshType(type)) + { + var mesh = (Mesh)value; + + if (GUILayout.Button("Preview")) + { + MeshViewer.CreateMeshViewer(null, mesh, null); + } + + if (mesh.isReadable && GUILayout.Button("Dump .obj")) + { + var outPath = refChain.ToString().Replace(' ', '_'); + DumpUtil.DumpMeshAndTextures(outPath, mesh); + } + } + } + + private static void SetupPlopButton(object @object) + { + var info = @object as PrefabInfo; + if (info != null && GUILayout.Button("Plop")) + { + Plopper.StartPlopping(info); + } + } + + private static void SetupGotoButton(InstanceID instance, Vector3 position) + { + if (GUILayout.Button("Go to")) + { + ToolsModifierControl.cameraController.SetTarget(instance, position, true); + } + } + } +} \ No newline at end of file diff --git a/Debugger/GUIExplorer/GUICollection.cs b/Debugger/Explorer/GUICollection.cs similarity index 52% rename from Debugger/GUIExplorer/GUICollection.cs rename to Debugger/Explorer/GUICollection.cs index 33734f7..70ae2e4 100644 --- a/Debugger/GUIExplorer/GUICollection.cs +++ b/Debugger/Explorer/GUICollection.cs @@ -1,18 +1,20 @@ using System; using System.Collections; -using System.Collections.Generic; +using ModTools.Utils; using UnityEngine; namespace ModTools.Explorer { - public static class GUICollection + internal static class GUICollection { - public static void OnSceneTreeReflectICollection(SceneExplorerState state, ReferenceChain refChain, System.Object myProperty) + public static void OnSceneTreeReflectICollection(SceneExplorerState state, ReferenceChain refChain, object myProperty) { - if (!SceneExplorerCommon.SceneTreeCheckDepth(refChain)) return; + if (!SceneExplorerCommon.SceneTreeCheckDepth(refChain)) + { + return; + } - var collection = myProperty as ICollection; - if (collection == null) + if (!(myProperty is ICollection collection)) { return; } @@ -30,11 +32,12 @@ public static void OnSceneTreeReflectICollection(SceneExplorerState state, Refer return; } + var collectionItemType = collection.GetType().GetElementType(); + var flagsField = collectionItemType?.GetField("m_flags"); + var flagIsEnum = flagsField?.FieldType.IsEnum == true && Type.GetTypeCode(flagsField.FieldType) == TypeCode.Int32; - int arrayStart; - int arrayEnd; - GUICollectionNavigation.SetUpCollectionNavigation("Collection", state, refChain, oldRefChain, collectionSize, out arrayStart, out arrayEnd); - int count = 0; + GUICollectionNavigation.SetUpCollectionNavigation("Collection", state, refChain, oldRefChain, collectionSize, out var arrayStart, out var arrayEnd); + var count = 0; foreach (var value in collection) { if (count < arrayStart) @@ -44,18 +47,26 @@ public static void OnSceneTreeReflectICollection(SceneExplorerState state, Refer } refChain = oldRefChain.Add(count); - var type = value.GetType(); GUILayout.BeginHorizontal(); - GUILayout.Space(ModTools.Instance.config.sceneExplorerTreeIdentSpacing * refChain.Ident); + SceneExplorerCommon.InsertIndent(refChain.Ident); - GUIExpander.ExpanderControls(state, refChain, type); + var isNullOrEmpty = value == null || flagIsEnum && Convert.ToInt32(flagsField.GetValue(value)) == 0; + + var type = value?.GetType() ?? collectionItemType; + if (type != null) + { + if (!isNullOrEmpty) + { + GUIExpander.ExpanderControls(state, refChain, type); + } - GUI.contentColor = ModTools.Instance.config.typeColor; + GUI.contentColor = MainWindow.Instance.Config.TypeColor; - GUILayout.Label(type.ToString() + " "); + GUILayout.Label(type.ToString() + " "); + } - GUI.contentColor = ModTools.Instance.config.nameColor; + GUI.contentColor = MainWindow.Instance.Config.NameColor; GUILayout.Label($"{oldRefChain.LastItemName}.[{count}]"); @@ -63,28 +74,32 @@ public static void OnSceneTreeReflectICollection(SceneExplorerState state, Refer GUILayout.Label(" = "); - GUI.contentColor = ModTools.Instance.config.valueColor; - GUILayout.Label(value == null ? "null" : value.ToString()); + GUI.contentColor = MainWindow.Instance.Config.ValueColor; + GUILayout.Label(value == null ? "null" : isNullOrEmpty ? "empty" : value.ToString()); GUI.contentColor = Color.white; GUILayout.FlexibleSpace(); - GUIButtons.SetupButtons(type, value, refChain); + + if (!isNullOrEmpty) + { + GUIButtons.SetupButtons(refChain, type, value, count); + } + GUILayout.EndHorizontal(); - if (!TypeUtil.IsSpecialType(type) && state.expandedObjects.ContainsKey(refChain)) + if (!isNullOrEmpty && !TypeUtil.IsSpecialType(type) && state.ExpandedObjects.Contains(refChain.UniqueId)) { - if (value is GameObject) + if (value is GameObject go) { - var go = value as GameObject; foreach (var component in go.GetComponents()) { GUIComponent.OnSceneTreeComponent(state, refChain, component); } } - else if (value is Transform) + else if (value is Transform transforms) { - GUITransform.OnSceneTreeReflectUnityEngineTransform(refChain, (Transform)value); + GUITransform.OnSceneTreeReflectUnityEngineTransform(refChain, transforms); } else { diff --git a/Debugger/GUIExplorer/GUICollectionNavigation.cs b/Debugger/Explorer/GUICollectionNavigation.cs similarity index 52% rename from Debugger/GUIExplorer/GUICollectionNavigation.cs rename to Debugger/Explorer/GUICollectionNavigation.cs index 86211fe..74a8159 100644 --- a/Debugger/GUIExplorer/GUICollectionNavigation.cs +++ b/Debugger/Explorer/GUICollectionNavigation.cs @@ -1,31 +1,37 @@ -using System.Collections.Generic; +using ModTools.UI; using UnityEngine; namespace ModTools.Explorer { - public static class GUICollectionNavigation + internal static class GUICollectionNavigation { - public static void SetUpCollectionNavigation(string collectionLabel, SceneExplorerState state, ReferenceChain refChain, ReferenceChain oldRefChain, int collectionSize, out int arrayStart, - out int arrayEnd) + public static void SetUpCollectionNavigation( + string collectionLabel, + SceneExplorerState state, + ReferenceChain refChain, + ReferenceChain oldRefChain, + int collectionSize, + out int arrayStart, + out int arrayEnd) { GUILayout.BeginHorizontal(); - GUILayout.Space(ModTools.Instance.config.sceneExplorerTreeIdentSpacing * refChain.Ident); + SceneExplorerCommon.InsertIndent(refChain.Ident); + GUILayout.Label($"{collectionLabel} size: {collectionSize}"); - if (!state.selectedArrayStartIndices.ContainsKey(refChain)) + if (!state.SelectedArrayStartIndices.TryGetValue(refChain.UniqueId, out arrayStart)) { - state.selectedArrayStartIndices.Add(refChain, 0); + state.SelectedArrayStartIndices.Add(refChain.UniqueId, 0); } - if (!state.selectedArrayEndIndices.ContainsKey(refChain)) + if (!state.SelectedArrayEndIndices.TryGetValue(refChain.UniqueId, out arrayEnd)) { - state.selectedArrayEndIndices.Add(refChain, 32); + state.SelectedArrayEndIndices.Add(refChain.UniqueId, 32); + arrayEnd = 32; } - arrayStart = state.selectedArrayStartIndices[refChain]; - arrayEnd = state.selectedArrayEndIndices[refChain]; - GUIControls.IntField($"{oldRefChain}.arrayStart", "Start index", ref arrayStart, 0.0f, true, true); - GUIControls.IntField($"{oldRefChain}.arrayEnd", "End index", ref arrayEnd, 0.0f, true, true); + arrayStart = GUIControls.PrimitiveValueField($"{oldRefChain}.arrayStart", "Start index", arrayStart); + arrayEnd = GUIControls.PrimitiveValueField($"{oldRefChain}.arrayEnd", "End index", arrayEnd); GUILayout.Label("(32 items max)"); var pageSize = Mathf.Clamp(arrayEnd - arrayStart + 1, 1, Mathf.Min(32, collectionSize - arrayStart, arrayEnd + 1)); if (GUILayout.Button("◄", GUILayout.ExpandWidth(false))) @@ -33,11 +39,13 @@ public static void SetUpCollectionNavigation(string collectionLabel, SceneExplor arrayStart -= pageSize; arrayEnd -= pageSize; } + if (GUILayout.Button("►", GUILayout.ExpandWidth(false))) { arrayStart += pageSize; arrayEnd += pageSize; } + arrayStart = Mathf.Clamp(arrayStart, 0, collectionSize - pageSize); arrayEnd = Mathf.Clamp(arrayEnd, pageSize - 1, collectionSize - 1); if (arrayStart > arrayEnd) @@ -50,8 +58,9 @@ public static void SetUpCollectionNavigation(string collectionLabel, SceneExplor arrayEnd = arrayStart + 32; arrayEnd = Mathf.Clamp(arrayEnd, 32, collectionSize - 1); } - state.selectedArrayStartIndices[refChain] = arrayStart; - state.selectedArrayEndIndices[refChain] = arrayEnd; + + state.SelectedArrayStartIndices[refChain.UniqueId] = arrayStart; + state.SelectedArrayEndIndices[refChain.UniqueId] = arrayEnd; GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); } diff --git a/Debugger/GUIExplorer/GUIComponent.cs b/Debugger/Explorer/GUIComponent.cs similarity index 55% rename from Debugger/GUIExplorer/GUIComponent.cs rename to Debugger/Explorer/GUIComponent.cs index 43acf20..f609fda 100644 --- a/Debugger/GUIExplorer/GUIComponent.cs +++ b/Debugger/Explorer/GUIComponent.cs @@ -2,11 +2,14 @@ namespace ModTools.Explorer { - public static class GUIComponent + internal static class GUIComponent { public static void OnSceneTreeComponent(SceneExplorerState state, ReferenceChain refChain, Component component) { - if (!SceneExplorerCommon.SceneTreeCheckDepth(refChain)) return; + if (!SceneExplorerCommon.SceneTreeCheckDepth(refChain)) + { + return; + } if (component == null) { @@ -15,31 +18,26 @@ public static void OnSceneTreeComponent(SceneExplorerState state, ReferenceChain } GUILayout.BeginHorizontal(); - GUILayout.Space(ModTools.Instance.config.sceneExplorerTreeIdentSpacing * refChain.Ident); + SceneExplorerCommon.InsertIndent(refChain.Ident); - if (Util.ComponentIsEnabled(component)) - { - GUI.contentColor = ModTools.Instance.config.enabledComponentColor; - } - else - { - GUI.contentColor = ModTools.Instance.config.disabledComponentColor; - } + GUI.contentColor = GameObjectUtil.ComponentIsEnabled(component) + ? MainWindow.Instance.Config.EnabledComponentColor + : MainWindow.Instance.Config.DisabledComponentColor; - if (state.currentRefChain == null || !state.currentRefChain.Equals(refChain.Add(component))) + if (state.CurrentRefChain?.IsSameChain(refChain.Add(component)) != true) { if (GUILayout.Button(">", GUILayout.ExpandWidth(false))) { - state.currentRefChain = refChain.Add(component); - state.currentRefChain.IdentOffset = -(refChain.Length + 1); + state.CurrentRefChain = refChain.Add(component); + state.CurrentRefChain.IdentOffset = -(refChain.Length + 1); } } else { - GUI.contentColor = ModTools.Instance.config.selectedComponentColor; + GUI.contentColor = MainWindow.Instance.Config.SelectedComponentColor; if (GUILayout.Button("<", GUILayout.ExpandWidth(false))) { - state.currentRefChain = null; + state.CurrentRefChain = null; } } diff --git a/Debugger/GUIExplorer/GUIEnumerable.cs b/Debugger/Explorer/GUIEnumerable.cs similarity index 59% rename from Debugger/GUIExplorer/GUIEnumerable.cs rename to Debugger/Explorer/GUIEnumerable.cs index b0f701d..5de3198 100644 --- a/Debugger/GUIExplorer/GUIEnumerable.cs +++ b/Debugger/Explorer/GUIEnumerable.cs @@ -1,41 +1,44 @@ -using System; -using System.Collections; -using System.Collections.Generic; +using System.Collections; +using ModTools.Utils; using UnityEngine; namespace ModTools.Explorer { - public static class GUIEnumerable + internal static class GUIEnumerable { public static void OnSceneTreeReflectIEnumerable(SceneExplorerState state, ReferenceChain refChain, object myProperty) { - if (!SceneExplorerCommon.SceneTreeCheckDepth(refChain)) return; + if (!SceneExplorerCommon.SceneTreeCheckDepth(refChain)) + { + return; + } - var enumerable = myProperty as IEnumerable; - if (enumerable == null) + if (!(myProperty is IEnumerable enumerable)) { return; } - int count = 0; + var count = 0; var oldRefChain = refChain; foreach (var value in enumerable) { refChain = oldRefChain.Add(count); - var type = value.GetType(); - GUILayout.BeginHorizontal(); - GUILayout.Space(ModTools.Instance.config.sceneExplorerTreeIdentSpacing * refChain.Ident); + SceneExplorerCommon.InsertIndent(refChain.Ident); - GUIExpander.ExpanderControls(state, refChain, type); + var type = value?.GetType(); + if (type != null) + { + GUIExpander.ExpanderControls(state, refChain, type); - GUI.contentColor = ModTools.Instance.config.typeColor; + GUI.contentColor = MainWindow.Instance.Config.TypeColor; - GUILayout.Label(type.ToString() + " "); + GUILayout.Label(type.ToString() + " "); + } - GUI.contentColor = ModTools.Instance.config.nameColor; + GUI.contentColor = MainWindow.Instance.Config.NameColor; GUILayout.Label($"{oldRefChain.LastItemName}.[{count}]"); @@ -43,7 +46,7 @@ public static void OnSceneTreeReflectIEnumerable(SceneExplorerState state, Refer GUILayout.Label(" = "); - GUI.contentColor = ModTools.Instance.config.valueColor; + GUI.contentColor = MainWindow.Instance.Config.ValueColor; GUILayout.Label(value == null ? "null" : value.ToString()); @@ -52,19 +55,18 @@ public static void OnSceneTreeReflectIEnumerable(SceneExplorerState state, Refer GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); - if (!TypeUtil.IsSpecialType(type) && state.expandedObjects.ContainsKey(refChain)) + if (type != null && !TypeUtil.IsSpecialType(type) && state.ExpandedObjects.Contains(refChain.UniqueId)) { - if (value is GameObject) + if (value is GameObject go) { - var go = value as GameObject; foreach (var component in go.GetComponents()) { GUIComponent.OnSceneTreeComponent(state, refChain, component); } } - else if (value is Transform) + else if (value is Transform transform) { - GUITransform.OnSceneTreeReflectUnityEngineTransform(refChain, (Transform)value); + GUITransform.OnSceneTreeReflectUnityEngineTransform(refChain, transform); } else { @@ -73,9 +75,9 @@ public static void OnSceneTreeReflectIEnumerable(SceneExplorerState state, Refer } count++; - if (count >= 1024) + if (count >= 128) { - SceneExplorerCommon.OnSceneTreeMessage(refChain, "Array too large to display"); + SceneExplorerCommon.OnSceneTreeMessage(refChain, "Enumerable too large to display"); break; } } diff --git a/Debugger/GUIExplorer/GUIExpander.cs b/Debugger/Explorer/GUIExpander.cs similarity index 50% rename from Debugger/GUIExplorer/GUIExpander.cs rename to Debugger/Explorer/GUIExpander.cs index 6888ad4..70f77d7 100644 --- a/Debugger/GUIExplorer/GUIExpander.cs +++ b/Debugger/Explorer/GUIExpander.cs @@ -1,31 +1,29 @@ using System; -using System.Collections.Generic; +using ModTools.Utils; using UnityEngine; namespace ModTools.Explorer { - public static class GUIExpander + internal static class GUIExpander { public static void ExpanderControls(SceneExplorerState state, ReferenceChain refChain, Type type, object o = null) { GUI.contentColor = Color.white; - if (TypeUtil.IsSpecialType(type) || (o!=null && TypeUtil.IsEnumerable(o))) + if (TypeUtil.IsSpecialType(type) || o != null && TypeUtil.IsEnumerable(o)) { return; } - if (state.expandedObjects.ContainsKey(refChain)) + + if (state.ExpandedObjects.Contains(refChain.UniqueId)) { if (GUILayout.Button("-", GUILayout.ExpandWidth(false))) { - state.expandedObjects.Remove(refChain); + state.ExpandedObjects.Remove(refChain.UniqueId); } } - else + else if (GUILayout.Button("+", GUILayout.ExpandWidth(false))) { - if (GUILayout.Button("+", GUILayout.ExpandWidth(false))) - { - state.expandedObjects.Add(refChain, true); - } + state.ExpandedObjects.Add(refChain.UniqueId); } } } diff --git a/Debugger/GUIExplorer/GUIField.cs b/Debugger/Explorer/GUIField.cs similarity index 71% rename from Debugger/GUIExplorer/GUIField.cs rename to Debugger/Explorer/GUIField.cs index 22f3c00..802cb23 100644 --- a/Debugger/GUIExplorer/GUIField.cs +++ b/Debugger/Explorer/GUIField.cs @@ -1,17 +1,19 @@ using System; -using System.Collections.Generic; using System.Reflection; +using ModTools.UI; +using ModTools.Utils; using UnityEngine; namespace ModTools.Explorer { - public static class GUIField + internal static class GUIField { public static void OnSceneTreeReflectField(SceneExplorerState state, ReferenceChain refChain, object obj, FieldInfo field) { - var hash = refChain.GetHashCode().ToString(); - - if (!SceneExplorerCommon.SceneTreeCheckDepth(refChain)) return; + if (!SceneExplorerCommon.SceneTreeCheckDepth(refChain)) + { + return; + } if (obj == null || field == null) { @@ -20,7 +22,7 @@ public static void OnSceneTreeReflectField(SceneExplorerState state, ReferenceCh } GUILayout.BeginHorizontal(); - GUILayout.Space(ModTools.Instance.config.sceneExplorerTreeIdentSpacing * refChain.Ident); + SceneExplorerCommon.InsertIndent(refChain.Ident); GUI.contentColor = Color.white; @@ -32,7 +34,7 @@ public static void OnSceneTreeReflectField(SceneExplorerState state, ReferenceCh } catch (Exception e) { - UnityEngine.Debug.LogException(e); + Debug.LogException(e); } if (value != null) @@ -45,9 +47,9 @@ public static void OnSceneTreeReflectField(SceneExplorerState state, ReferenceCh GUI.enabled = false; } - if (ModTools.Instance.config.sceneExplorerShowModifiers) + if (MainWindow.Instance.Config.ShowModifiers) { - GUI.contentColor = ModTools.Instance.config.modifierColor; + GUI.contentColor = MainWindow.Instance.Config.ModifierColor; if (field.IsPublic) { @@ -58,34 +60,33 @@ public static void OnSceneTreeReflectField(SceneExplorerState state, ReferenceCh GUILayout.Label("private "); } - GUI.contentColor = ModTools.Instance.config.memberTypeColor; + GUI.contentColor = MainWindow.Instance.Config.MemberTypeColor; GUILayout.Label("field "); if (field.IsStatic) { - GUI.contentColor = ModTools.Instance.config.keywordColor; + GUI.contentColor = MainWindow.Instance.Config.KeywordColor; GUILayout.Label("static "); } if (field.IsInitOnly) { - GUI.contentColor = ModTools.Instance.config.keywordColor; + GUI.contentColor = MainWindow.Instance.Config.KeywordColor; GUILayout.Label("const "); } } - GUI.contentColor = ModTools.Instance.config.typeColor; + GUI.contentColor = MainWindow.Instance.Config.TypeColor; GUILayout.Label(field.FieldType + " "); - GUI.contentColor = ModTools.Instance.config.nameColor; + GUI.contentColor = MainWindow.Instance.Config.NameColor; GUILayout.Label(field.Name); - GUI.contentColor = Color.white; GUI.contentColor = Color.white; GUILayout.Label(" = "); - GUI.contentColor = ModTools.Instance.config.valueColor; + GUI.contentColor = MainWindow.Instance.Config.ValueColor; if (value == null || !TypeUtil.IsSpecialType(field.FieldType)) { @@ -95,8 +96,8 @@ public static void OnSceneTreeReflectField(SceneExplorerState state, ReferenceCh { try { - var newValue = GUIControls.EditorValueField(refChain, hash, field.FieldType, value); - if (newValue != value) + var newValue = GUIControls.EditorValueField(refChain.UniqueId, field.FieldType, value); + if (!newValue.Equals(value)) { field.SetValue(obj, newValue); } @@ -113,35 +114,37 @@ public static void OnSceneTreeReflectField(SceneExplorerState state, ReferenceCh GUILayout.FlexibleSpace(); if (GUILayout.Button("Watch")) { - ModTools.Instance.watches.AddWatch(refChain); + MainWindow.Instance.Watches.AddWatch(refChain); } - GUIButtons.SetupButtons(field.FieldType, value, refChain); + + GUIButtons.SetupButtons(refChain, field.FieldType, value, valueIndex: -1); object paste = null; var doPaste = !field.IsLiteral && !field.IsInitOnly; if (doPaste) { doPaste = GUIButtons.SetupPasteButon(field.FieldType, out paste); } + GUILayout.EndHorizontal(); - if (value != null && !TypeUtil.IsSpecialType(field.FieldType) && state.expandedObjects.ContainsKey(refChain)) + if (value != null && !TypeUtil.IsSpecialType(field.FieldType) && state.ExpandedObjects.Contains(refChain.UniqueId)) { - if (value is GameObject) + if (value is GameObject go) { - var go = value as GameObject; foreach (var component in go.GetComponents()) { GUIComponent.OnSceneTreeComponent(state, refChain, component); } } - else if (value is Transform) + else if (value is Transform transform) { - GUITransform.OnSceneTreeReflectUnityEngineTransform(refChain, (Transform)value); + GUITransform.OnSceneTreeReflectUnityEngineTransform(refChain, transform); } else { GUIReflect.OnSceneTreeReflect(state, refChain, value); } } + if (doPaste) { try @@ -150,10 +153,9 @@ public static void OnSceneTreeReflectField(SceneExplorerState state, ReferenceCh } catch (Exception e) { - Log.Warning(e.Message); + Logger.Warning(e.Message); } } - } } } \ No newline at end of file diff --git a/Debugger/Explorer/GUIList.cs b/Debugger/Explorer/GUIList.cs new file mode 100644 index 0000000..dc7a9f9 --- /dev/null +++ b/Debugger/Explorer/GUIList.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections; +using ModTools.Utils; +using UnityEngine; + +namespace ModTools.Explorer +{ + internal static class GUIList + { + public static void OnSceneTreeReflectIList(SceneExplorerState state, ReferenceChain refChain, object myProperty) + { + if (!SceneExplorerCommon.SceneTreeCheckDepth(refChain)) + { + return; + } + + if (!(myProperty is IList list)) + { + return; + } + + var oldRefChain = refChain; + var collectionSize = list.Count; + if (collectionSize == 0) + { + GUILayout.BeginHorizontal(); + GUI.contentColor = Color.yellow; + GUILayout.Label("List is empty!"); + GUI.contentColor = Color.white; + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + return; + } + + var listItemType = list.GetType().GetElementType(); + var flagsField = listItemType?.GetField("m_flags"); + var flagIsEnum = flagsField?.FieldType.IsEnum == true && Type.GetTypeCode(flagsField.FieldType) == TypeCode.Int32; + + GUICollectionNavigation.SetUpCollectionNavigation("List", state, refChain, oldRefChain, collectionSize, out var arrayStart, out var arrayEnd); + for (var i = arrayStart; i <= arrayEnd; i++) + { + refChain = oldRefChain.Add(i); + + GUILayout.BeginHorizontal(); + SceneExplorerCommon.InsertIndent(refChain.Ident); + + GUI.contentColor = Color.white; + + var value = list[i]; + var type = value?.GetType() ?? listItemType; + var isNullOrEmpty = value == null || flagIsEnum && Convert.ToInt32(flagsField.GetValue(value)) == 0; + if (type != null) + { + if (!isNullOrEmpty) + { + GUIExpander.ExpanderControls(state, refChain, type); + } + + GUI.contentColor = MainWindow.Instance.Config.TypeColor; + + GUILayout.Label($"{type} "); + } + + GUI.contentColor = MainWindow.Instance.Config.NameColor; + + GUILayout.Label($"{oldRefChain.LastItemName}.[{i}]"); + + GUI.contentColor = Color.white; + + GUILayout.Label(" = "); + + GUI.contentColor = MainWindow.Instance.Config.ValueColor; + + GUILayout.Label(value == null ? "null" : isNullOrEmpty ? "empty" : value.ToString()); + + GUI.contentColor = Color.white; + + GUILayout.FlexibleSpace(); + + if (!isNullOrEmpty) + { + GUIButtons.SetupButtons(refChain, type, value, i); + } + + GUILayout.EndHorizontal(); + + if (!isNullOrEmpty && !TypeUtil.IsSpecialType(type) && state.ExpandedObjects.Contains(refChain.UniqueId)) + { + if (value is GameObject go) + { + foreach (var component in go.GetComponents()) + { + GUIComponent.OnSceneTreeComponent(state, refChain, component); + } + } + else if (value is Transform transform) + { + GUITransform.OnSceneTreeReflectUnityEngineTransform(refChain, transform); + } + else + { + GUIReflect.OnSceneTreeReflect(state, refChain, value); + } + } + } + } + } +} \ No newline at end of file diff --git a/Debugger/GUIExplorer/GUIMaterial.cs b/Debugger/Explorer/GUIMaterial.cs similarity index 52% rename from Debugger/GUIExplorer/GUIMaterial.cs rename to Debugger/Explorer/GUIMaterial.cs index fabc80f..430b4d1 100644 --- a/Debugger/GUIExplorer/GUIMaterial.cs +++ b/Debugger/Explorer/GUIMaterial.cs @@ -1,13 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using ModTools.UI; +using ModTools.Utils; using UnityEngine; namespace ModTools.Explorer { - public static class GUIMaterial + internal static class GUIMaterial { - private static readonly string[] textureProps = { + private static readonly string[] TextureProps = + { "_BackTex", "_BumpMap", "_BumpSpecMap", @@ -43,38 +43,24 @@ public static class GUIMaterial "luminTex", "searchTex", "_SrcTex", - "_Blurred" + "_Blurred", }; - private static readonly string[] colorProps = { + private static readonly string[] ColorProps = + { "_Color", "_ColorV0", "_ColorV1", "_ColorV2", - "_ColorV3" - }; - - private static readonly string[] vectorProps = new string[] - { - "_FloorParams", - "_UvAnimation", - "_WindAnimation", - "_WindAnimationB", - "_TyreLocation0", - "_TyreLocation1", - "_TyreLocation2", - "_TyreLocation3", - "_TyreLocation4", - "_TyreLocation5", - "_TyreLocation6", - "_TyreLocation7", - "_TyreParams" + "_ColorV3", }; - public static void OnSceneReflectUnityEngineMaterial(SceneExplorerState state, ReferenceChain refChain, Material material) { - if (!SceneExplorerCommon.SceneTreeCheckDepth(refChain)) return; + if (!SceneExplorerCommon.SceneTreeCheckDepth(refChain)) + { + return; + } if (material == null) { @@ -82,9 +68,9 @@ public static void OnSceneReflectUnityEngineMaterial(SceneExplorerState state, R return; } - ReferenceChain oldRefChain = refChain; + var oldRefChain = refChain; - foreach (var prop in textureProps) + foreach (var prop in TextureProps) { if (!material.HasProperty(prop)) { @@ -102,15 +88,15 @@ public static void OnSceneReflectUnityEngineMaterial(SceneExplorerState state, R var type = value.GetType(); GUILayout.BeginHorizontal(); - GUILayout.Space(ModTools.Instance.config.sceneExplorerTreeIdentSpacing * (refChain.Ident + 1)); + SceneExplorerCommon.InsertIndent(refChain.Ident + 1); GUIExpander.ExpanderControls(state, refChain, type); - GUI.contentColor = ModTools.Instance.config.typeColor; + GUI.contentColor = MainWindow.Instance.Config.TypeColor; GUILayout.Label(type.ToString() + " "); - GUI.contentColor = ModTools.Instance.config.nameColor; + GUI.contentColor = MainWindow.Instance.Config.NameColor; GUILayout.Label(prop); @@ -118,17 +104,16 @@ public static void OnSceneReflectUnityEngineMaterial(SceneExplorerState state, R GUILayout.Label(" = "); - GUI.contentColor = ModTools.Instance.config.valueColor; + GUI.contentColor = MainWindow.Instance.Config.ValueColor; GUILayout.Label(value.ToString()); GUI.contentColor = Color.white; GUILayout.FlexibleSpace(); - GUIButtons.SetupButtons(type, value, refChain); - object paste; - var doPaste = GUIButtons.SetupPasteButon(type, out paste); + GUIButtons.SetupButtons(refChain, type, value, valueIndex: -1); + var doPaste = GUIButtons.SetupPasteButon(type, out var paste); GUILayout.EndHorizontal(); - if (!TypeUtil.IsSpecialType(type) && state.expandedObjects.ContainsKey(refChain)) + if (!TypeUtil.IsSpecialType(type) && state.ExpandedObjects.Contains(refChain.UniqueId)) { GUIReflect.OnSceneTreeReflect(state, refChain, value); } @@ -139,86 +124,61 @@ public static void OnSceneReflectUnityEngineMaterial(SceneExplorerState state, R } } - foreach (string prop in colorProps) + foreach (var prop in ColorProps) { if (!material.HasProperty(prop)) { continue; } - Color value = material.GetColor(prop); + var value = material.GetColor(prop); refChain = oldRefChain.Add(prop); var type = value.GetType(); GUILayout.BeginHorizontal(); - GUILayout.Space(ModTools.Instance.config.sceneExplorerTreeIdentSpacing * (refChain.Ident + 1)); + SceneExplorerCommon.InsertIndent(refChain.Ident + 1); GUIExpander.ExpanderControls(state, refChain, type); - GUI.contentColor = ModTools.Instance.config.typeColor; + GUI.contentColor = MainWindow.Instance.Config.TypeColor; GUILayout.Label(type.ToString() + " "); - GUI.contentColor = ModTools.Instance.config.nameColor; + GUI.contentColor = MainWindow.Instance.Config.NameColor; GUILayout.Label(prop); GUI.contentColor = Color.white; GUILayout.Label(" = "); - var f = value; - GUI.contentColor = ModTools.Instance.config.valueColor; + GUI.contentColor = MainWindow.Instance.Config.ValueColor; - var propertyCopy = prop; - GUIControls.ColorField(refChain.ToString(), "", ref f, 0.0f, null, true, true, color => { material.SetColor(propertyCopy, color); }); - if (f != value) + var newColor = GUIControls.CustomValueField(refChain.UniqueId, string.Empty, GUIControls.PresentColor, value); + if (newColor != value) { - material.SetColor(prop, f); + material.SetColor(prop, newColor); } GUI.contentColor = Color.white; GUILayout.FlexibleSpace(); - GUIButtons.SetupButtons(type, value, refChain); - object paste; - var doPaste = GUIButtons.SetupPasteButon(type, out paste); + GUIButtons.SetupButtons(refChain, type, value, valueIndex: -1); + var doPaste = GUIButtons.SetupPasteButon(type, out var paste); GUILayout.EndHorizontal(); - if (!TypeUtil.IsSpecialType(type) && state.expandedObjects.ContainsKey(refChain)) + if (!TypeUtil.IsSpecialType(type) && state.ExpandedObjects.Contains(refChain.UniqueId)) { GUIReflect.OnSceneTreeReflect(state, refChain, value); } + if (doPaste) { material.SetColor(prop, (Color)paste); } } -// GUILayout.BeginHorizontal(); -// GUILayout.Space(ModTools.Instance.config.sceneExplorerTreeIdentSpacing * (refChain.Ident + 1)); -// GUI.contentColor = ModTools.Instance.config.typeColor; -// -// GUILayout.Label("Shader:"); -// -// GUI.contentColor = ModTools.Instance.config.nameColor; -// -// var shaders = Resources.FindObjectsOfTypeAll(); -// Array.Sort(shaders, (a, b) => string.Compare(a.name, b.name, StringComparison.Ordinal)); -// var availableShaders = shaders.Select(s => s.name).ToArray(); -// var currentShader = material.shader; -// var selectedShader = Array.IndexOf(shaders, currentShader); -// -// var newSelectedShader = GUIComboBox.Box(selectedShader, availableShaders, "SceneExplorerShadersComboBox"); -// if (newSelectedShader != selectedShader) -// { -// material.shader = shaders[newSelectedShader]; -// } -// GUILayout.FlexibleSpace(); -// GUILayout.EndHorizontal(); GUIReflect.OnSceneTreeReflect(state, refChain, material, true); } - - } } \ No newline at end of file diff --git a/Debugger/GUIExplorer/GUIMethod.cs b/Debugger/Explorer/GUIMethod.cs similarity index 70% rename from Debugger/GUIExplorer/GUIMethod.cs rename to Debugger/Explorer/GUIMethod.cs index a3096ed..4a9dfdf 100644 --- a/Debugger/GUIExplorer/GUIMethod.cs +++ b/Debugger/Explorer/GUIMethod.cs @@ -1,14 +1,16 @@ -using System.Linq; -using System.Reflection; +using System.Reflection; using UnityEngine; namespace ModTools.Explorer { - public class GUIMethod + internal static class GUIMethod { - public static void OnSceneTreeReflectMethod(ReferenceChain refChain, System.Object obj, MethodInfo method) + public static void OnSceneTreeReflectMethod(ReferenceChain refChain, object obj, MethodInfo method) { - if (!SceneExplorerCommon.SceneTreeCheckDepth(refChain)) return; + if (!SceneExplorerCommon.SceneTreeCheckDepth(refChain)) + { + return; + } if (obj == null || method == null) { @@ -17,17 +19,16 @@ public static void OnSceneTreeReflectMethod(ReferenceChain refChain, System.Obje } GUILayout.BeginHorizontal(); - GUILayout.Space(ModTools.Instance.config.sceneExplorerTreeIdentSpacing * refChain.Ident); + SceneExplorerCommon.InsertIndent(refChain.Ident); - GUI.contentColor = ModTools.Instance.config.memberTypeColor; + GUI.contentColor = MainWindow.Instance.Config.MemberTypeColor; GUILayout.Label("method "); GUI.contentColor = Color.white; GUILayout.Label(method.ReturnType.ToString() + " " + method.Name + "("); - GUI.contentColor = ModTools.Instance.config.nameColor; + GUI.contentColor = MainWindow.Instance.Config.NameColor; var first = true; - var parameters = method.GetParameters(); - foreach (var param in parameters) + foreach (var param in method.GetParameters()) { if (!first) { @@ -40,29 +41,30 @@ public static void OnSceneTreeReflectMethod(ReferenceChain refChain, System.Obje GUILayout.Label(param.ParameterType.ToString() + " " + param.Name); } + GUI.contentColor = Color.white; GUILayout.Label(")"); GUILayout.FlexibleSpace(); if (!method.IsGenericMethod) { - if (!method.GetParameters().Any()) + if (method.GetParameters().Length == 0) { if (GUILayout.Button("Invoke", GUILayout.ExpandWidth(false))) { method.Invoke(method.IsStatic ? null : obj, new object[] { }); } } - else if (method.GetParameters().Length == 1 && - method.GetParameters()[0].ParameterType.IsInstanceOfType(obj)) + else if (method.GetParameters().Length == 1 + && method.GetParameters()[0].ParameterType.IsInstanceOfType(obj)) { if (GUILayout.Button("Invoke", GUILayout.ExpandWidth(false))) { method.Invoke(method.IsStatic ? null : obj, new[] { obj }); } } - } + GUILayout.EndHorizontal(); } } diff --git a/Debugger/GUIExplorer/GUIProperty.cs b/Debugger/Explorer/GUIProperty.cs similarity index 76% rename from Debugger/GUIExplorer/GUIProperty.cs rename to Debugger/Explorer/GUIProperty.cs index 6375d30..8a2f0d6 100644 --- a/Debugger/GUIExplorer/GUIProperty.cs +++ b/Debugger/Explorer/GUIProperty.cs @@ -1,16 +1,20 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.Reflection; +using ModTools.UI; +using ModTools.Utils; using UnityEngine; namespace ModTools.Explorer { - public class GUIProperty + internal static class GUIProperty { - public static void OnSceneTreeReflectProperty(SceneExplorerState state, ReferenceChain refChain, System.Object obj, PropertyInfo property) + public static void OnSceneTreeReflectProperty(SceneExplorerState state, ReferenceChain refChain, object obj, PropertyInfo property) { - if (!SceneExplorerCommon.SceneTreeCheckDepth(refChain)) return; + if (!SceneExplorerCommon.SceneTreeCheckDepth(refChain)) + { + return; + } if (obj == null || property == null) { @@ -18,17 +22,15 @@ public static void OnSceneTreeReflectProperty(SceneExplorerState state, Referenc return; } - var hash = refChain.GetHashCode().ToString(); - GUILayout.BeginHorizontal(); - GUILayout.Space(ModTools.Instance.config.sceneExplorerTreeIdentSpacing * refChain.Ident); + SceneExplorerCommon.InsertIndent(refChain.Ident); - bool propertyWasEvaluated = false; + var propertyWasEvaluated = false; object value = null; Exception exceptionOnGetting = null; - if (property.CanRead && ModTools.Instance.config.sceneExplorerEvaluatePropertiesAutomatically || state.evaluatedProperties.ContainsKey(refChain)) + if (property.CanRead && MainWindow.Instance.Config.EvaluateProperties || state.EvaluatedProperties.Contains(refChain.UniqueId)) { try { @@ -53,37 +55,37 @@ public static void OnSceneTreeReflectProperty(SceneExplorerState state, Referenc GUI.enabled = false; } - if (ModTools.Instance.config.sceneExplorerShowModifiers) + if (MainWindow.Instance.Config.ShowModifiers) { - GUI.contentColor = ModTools.Instance.config.memberTypeColor; + GUI.contentColor = MainWindow.Instance.Config.MemberTypeColor; GUILayout.Label("property "); if (!property.CanWrite) { - GUI.contentColor = ModTools.Instance.config.keywordColor; + GUI.contentColor = MainWindow.Instance.Config.KeywordColor; GUILayout.Label("const "); } } - GUI.contentColor = ModTools.Instance.config.typeColor; + GUI.contentColor = MainWindow.Instance.Config.TypeColor; GUILayout.Label(property.PropertyType.ToString() + " "); - GUI.contentColor = ModTools.Instance.config.nameColor; + GUI.contentColor = MainWindow.Instance.Config.NameColor; GUILayout.Label(property.Name); GUI.contentColor = Color.white; GUILayout.Label(" = "); - GUI.contentColor = ModTools.Instance.config.valueColor; + GUI.contentColor = MainWindow.Instance.Config.ValueColor; - if (!ModTools.Instance.config.sceneExplorerEvaluatePropertiesAutomatically && !state.evaluatedProperties.ContainsKey(refChain)) + if (!MainWindow.Instance.Config.EvaluateProperties && !state.EvaluatedProperties.Contains(refChain.UniqueId)) { GUI.enabled = true; if (GUILayout.Button("Evaluate")) { - state.evaluatedProperties.Add(refChain, true); + state.EvaluatedProperties.Add(refChain.UniqueId); } } else @@ -100,6 +102,7 @@ public static void OnSceneTreeReflectProperty(SceneExplorerState state, Referenc exceptionOnGetting = e; } } + if (exceptionOnGetting != null) { GUI.contentColor = Color.red; @@ -113,7 +116,6 @@ public static void OnSceneTreeReflectProperty(SceneExplorerState state, Referenc return; } - if (value == null || !TypeUtil.IsSpecialType(property.PropertyType)) { if (property.CanRead) @@ -131,7 +133,7 @@ public static void OnSceneTreeReflectProperty(SceneExplorerState state, Referenc { try { - var newValue = GUIControls.EditorValueField(refChain, hash, property.PropertyType, value); + var newValue = GUIControls.EditorValueField(refChain.UniqueId, property.PropertyType, value); if (newValue != value) { property.SetValue(obj, newValue, null); @@ -160,36 +162,38 @@ public static void OnSceneTreeReflectProperty(SceneExplorerState state, Referenc if (GUILayout.Button("Watch")) { - ModTools.Instance.watches.AddWatch(refChain); + MainWindow.Instance.Watches.AddWatch(refChain); } - GUIButtons.SetupButtons(property.PropertyType, value, refChain); + + GUIButtons.SetupButtons(refChain, property.PropertyType, value, valueIndex: -1); object paste = null; var doPaste = property.CanWrite; if (doPaste) { doPaste = GUIButtons.SetupPasteButon(property.PropertyType, out paste); } + GUILayout.EndHorizontal(); - if (value != null && state.expandedObjects.ContainsKey(refChain)) + if (value != null && state.ExpandedObjects.Contains(refChain.UniqueId)) { - if (value is GameObject) + if (value is GameObject go) { - var go = value as GameObject; foreach (var component in go.GetComponents()) { GUIComponent.OnSceneTreeComponent(state, refChain, component); } } - else if (value is Transform) + else if (value is Transform transform) { - GUITransform.OnSceneTreeReflectUnityEngineTransform(refChain, (Transform)value); + GUITransform.OnSceneTreeReflectUnityEngineTransform(refChain, transform); } else { GUIReflect.OnSceneTreeReflect(state, refChain, value); } } + if (doPaste) { try @@ -198,10 +202,9 @@ public static void OnSceneTreeReflectProperty(SceneExplorerState state, Referenc } catch (Exception e) { - Log.Warning(e.Message); + Logger.Warning(e.Message); } } } - } } \ No newline at end of file diff --git a/Debugger/GUIExplorer/GUIRecursiveTree.cs b/Debugger/Explorer/GUIRecursiveTree.cs similarity index 63% rename from Debugger/GUIExplorer/GUIRecursiveTree.cs rename to Debugger/Explorer/GUIRecursiveTree.cs index 4f1c963..9ad9cfb 100644 --- a/Debugger/GUIExplorer/GUIRecursiveTree.cs +++ b/Debugger/Explorer/GUIRecursiveTree.cs @@ -3,41 +3,41 @@ namespace ModTools.Explorer { - public class GUIRecursiveTree + internal static class GUIRecursiveTree { public static void OnSceneTreeRecursive(GameObject modToolsGo, SceneExplorerState state, ReferenceChain refChain, GameObject obj) { - if (obj == modToolsGo && !ModTools.DEBUG_MODTOOLS) +#if !DEBUG + if (obj == modToolsGo) { return; } +#endif - if (!SceneExplorerCommon.SceneTreeCheckDepth(refChain)) return; - - if (obj == null) + if (!SceneExplorerCommon.SceneTreeCheckDepth(refChain)) { - SceneExplorerCommon.OnSceneTreeMessage(refChain, "null"); return; } - if (obj.name == "_ModToolsInternal" && !ModTools.DEBUG_MODTOOLS) + if (obj == null) { + SceneExplorerCommon.OnSceneTreeMessage(refChain, "null"); return; } - if (state.expandedGameObjects.ContainsKey(refChain)) + if (state.ExpandedGameObjects.Contains(refChain.UniqueId)) { try { GUILayout.BeginHorizontal(); - GUILayout.Space(ModTools.Instance.config.sceneExplorerTreeIdentSpacing * refChain.Ident); + SceneExplorerCommon.InsertIndent(refChain.Ident); if (GUILayout.Button("-", GUILayout.ExpandWidth(false))) { - state.expandedGameObjects.Remove(refChain); + state.ExpandedGameObjects.Remove(refChain.UniqueId); } - GUI.contentColor = ModTools.Instance.config.gameObjectColor; + GUI.contentColor = MainWindow.Instance.Config.GameObjectColor; GUILayout.Label(obj.name); GUI.contentColor = Color.white; @@ -45,9 +45,9 @@ public static void OnSceneTreeRecursive(GameObject modToolsGo, SceneExplorerStat var components = obj.GetComponents(typeof(Component)); - if (ModTools.Instance.config.sceneExplorerSortAlphabetically) + if (MainWindow.Instance.Config.SortItemsAlphabetically) { - Array.Sort(components, (component, component1) => component.GetType().ToString().CompareTo(component1.GetType().ToString())); + Array.Sort(components, (x, y) => string.CompareOrdinal(x.GetType().ToString(), y.GetType().ToString())); } foreach (var component in components) @@ -55,28 +55,28 @@ public static void OnSceneTreeRecursive(GameObject modToolsGo, SceneExplorerStat GUIComponent.OnSceneTreeComponent(state, refChain.Add(component), component); } - for (int i = 0; i < obj.transform.childCount; i++) + for (var i = 0; i < obj.transform.childCount; i++) { OnSceneTreeRecursive(modToolsGo, state, refChain.Add(obj.transform.GetChild(i)), obj.transform.GetChild(i).gameObject); } } catch (Exception) { - state.expandedGameObjects.Remove(refChain); + state.ExpandedGameObjects.Remove(refChain.UniqueId); throw; } } else { GUILayout.BeginHorizontal(); - GUILayout.Space(ModTools.Instance.config.sceneExplorerTreeIdentSpacing * refChain.Ident); + SceneExplorerCommon.InsertIndent(refChain.Ident); if (GUILayout.Button("+", GUILayout.ExpandWidth(false))) { - state.expandedGameObjects.Add(refChain, true); + state.ExpandedGameObjects.Add(refChain.UniqueId); } - GUI.contentColor = ModTools.Instance.config.gameObjectColor; + GUI.contentColor = MainWindow.Instance.Config.GameObjectColor; GUILayout.Label(obj.name); GUI.contentColor = Color.white; GUILayout.EndHorizontal(); diff --git a/Debugger/GUIExplorer/GUIReflect.cs b/Debugger/Explorer/GUIReflect.cs similarity index 66% rename from Debugger/GUIExplorer/GUIReflect.cs rename to Debugger/Explorer/GUIReflect.cs index 430ecc0..caf6e66 100644 --- a/Debugger/GUIExplorer/GUIReflect.cs +++ b/Debugger/Explorer/GUIReflect.cs @@ -1,14 +1,18 @@ using System; using System.Reflection; +using ModTools.Utils; using UnityEngine; namespace ModTools.Explorer { - public static class GUIReflect + internal static class GUIReflect { - public static void OnSceneTreeReflect(SceneExplorerState state, ReferenceChain refChain, System.Object obj, bool rawReflection = false) + public static void OnSceneTreeReflect(SceneExplorerState state, ReferenceChain refChain, object obj, bool rawReflection = false) { - if (!SceneExplorerCommon.SceneTreeCheckDepth(refChain)) return; + if (!SceneExplorerCommon.SceneTreeCheckDepth(refChain)) + { + return; + } if (obj == null) { @@ -16,32 +20,30 @@ public static void OnSceneTreeReflect(SceneExplorerState state, ReferenceChain r return; } - Type type = obj.GetType(); + var type = obj.GetType(); if (!rawReflection) { - if (!type.IsValueType) + if (!type.IsValueType && state.PreventCircularReferences.Contains(obj)) { - if (state.preventCircularReferences.Contains(obj)) + try { - try - { - GUI.contentColor = Color.yellow; - SceneExplorerCommon.OnSceneTreeMessage(refChain, "Circular reference detected"); - } - finally - { - GUI.contentColor = Color.white; - } - return; + GUI.contentColor = Color.yellow; + SceneExplorerCommon.OnSceneTreeMessage(refChain, "Circular reference detected"); } + finally + { + GUI.contentColor = Color.white; + } + + return; } - state.preventCircularReferences.Add(obj); + state.PreventCircularReferences.Add(obj); - if (type == typeof(UnityEngine.Transform)) + if (type == typeof(Transform)) { - GUITransform.OnSceneTreeReflectUnityEngineTransform(refChain, (UnityEngine.Transform)obj); + GUITransform.OnSceneTreeReflectUnityEngineTransform(refChain, (Transform)obj); return; } @@ -65,33 +67,35 @@ public static void OnSceneTreeReflect(SceneExplorerState state, ReferenceChain r if (type == typeof(Material)) { - GUIMaterial.OnSceneReflectUnityEngineMaterial(state, refChain, (UnityEngine.Material)obj); + GUIMaterial.OnSceneReflectUnityEngineMaterial(state, refChain, (Material)obj); return; } - if (type == typeof(Mesh)) + + if (type == typeof(Mesh) && !((Mesh)obj).isReadable) { - if (!((Mesh)obj).isReadable) - { - SceneExplorerCommon.OnSceneTreeMessage(refChain, "Mesh is not readable"); - return; - } + SceneExplorerCommon.OnSceneTreeMessage(refChain, "Mesh is not readable"); + return; } - } - var members = TypeUtil.GetAllMembers(type, ModTools.Instance.config.sceneExplorerShowInheritedMembers); + var members = TypeUtil.GetAllMembers(type, MainWindow.Instance.Config.ShowInheritedMembers); - if (ModTools.Instance.config.sceneExplorerSortAlphabetically) + if (MainWindow.Instance.Config.SortItemsAlphabetically) { - Array.Sort(members, (info, info1) => string.Compare(info.Name, info1.Name, StringComparison.Ordinal)); + Array.Sort(members, (x, y) => string.CompareOrdinal(x.Name, y.Name)); } - foreach (MemberInfo member in members) + foreach (var member in members) { - if (member.MemberType == MemberTypes.Field && ModTools.Instance.config.sceneExplorerShowFields) + if (member.MemberType == MemberTypes.Field && MainWindow.Instance.Config.ShowFields) { var field = (FieldInfo)member; + if (field.IsLiteral && !field.IsInitOnly && !MainWindow.Instance.Config.ShowConsts) + { + continue; + } + try { GUIField.OnSceneTreeReflectField(state, refChain.Add(field), obj, field); @@ -101,7 +105,7 @@ public static void OnSceneTreeReflect(SceneExplorerState state, ReferenceChain r SceneExplorerCommon.OnSceneTreeMessage(refChain, $"Exception when fetching field \"{field.Name}\" - {ex.Message}\n{ex.StackTrace}"); } } - else if (member.MemberType == MemberTypes.Property && ModTools.Instance.config.sceneExplorerShowProperties) + else if (member.MemberType == MemberTypes.Property && MainWindow.Instance.Config.ShowProperties) { var property = (PropertyInfo)member; @@ -114,7 +118,7 @@ public static void OnSceneTreeReflect(SceneExplorerState state, ReferenceChain r SceneExplorerCommon.OnSceneTreeMessage(refChain, $"Exception when fetching property \"{property.Name}\" - {ex.Message}\n{ex.StackTrace}"); } } - else if (member.MemberType == MemberTypes.Method && ModTools.Instance.config.sceneExplorerShowMethods) + else if (member.MemberType == MemberTypes.Method && MainWindow.Instance.Config.ShowMethods) { var method = (MethodInfo)member; diff --git a/Debugger/GUIExplorer/GUITransform.cs b/Debugger/Explorer/GUITransform.cs similarity index 52% rename from Debugger/GUIExplorer/GUITransform.cs rename to Debugger/Explorer/GUITransform.cs index 5b6ea75..dc7df03 100644 --- a/Debugger/GUIExplorer/GUITransform.cs +++ b/Debugger/Explorer/GUITransform.cs @@ -1,77 +1,76 @@ -using System; +using ModTools.UI; using UnityEngine; namespace ModTools.Explorer { - public class GUITransform + internal static class GUITransform { public static void OnSceneTreeReflectUnityEngineTransform(ReferenceChain refChain, Transform transform) { - if (!SceneExplorerCommon.SceneTreeCheckDepth(refChain)) return; + if (!SceneExplorerCommon.SceneTreeCheckDepth(refChain)) + { + return; + } if (transform == null) { - SceneExplorerCommon.OnSceneTreeMessage(refChain, (string) "null"); + SceneExplorerCommon.OnSceneTreeMessage(refChain, "null"); return; } var position = transform.position; - OnSceneTreeReflectUnityEngineVector3(refChain.Add("position"), transform, "position", ref position); + OnSceneTreeReflectUnityEngineVector3(refChain.Add("position"), "position", ref position); transform.position = position; var localPosition = transform.localPosition; - OnSceneTreeReflectUnityEngineVector3(refChain.Add("localPosition"), transform, "localPosition", ref localPosition); + OnSceneTreeReflectUnityEngineVector3(refChain.Add("localPosition"), "localPosition", ref localPosition); transform.localPosition = localPosition; var localEulerAngles = transform.localEulerAngles; - OnSceneTreeReflectUnityEngineVector3(refChain.Add("localEulerAngles"), transform, "localEulerAngles", ref localEulerAngles); + OnSceneTreeReflectUnityEngineVector3(refChain.Add("localEulerAngles"), "localEulerAngles", ref localEulerAngles); transform.localEulerAngles = localEulerAngles; var rotation = transform.rotation; - OnSceneTreeReflectUnityEngineQuaternion(refChain.Add("rotation"), transform, "rotation", ref rotation); + OnSceneTreeReflectUnityEngineQuaternion(refChain.Add("rotation"), "rotation", ref rotation); transform.rotation = rotation; var localRotation = transform.localRotation; - OnSceneTreeReflectUnityEngineQuaternion(refChain.Add("localRotation"), transform, "localRotation", ref localRotation); + OnSceneTreeReflectUnityEngineQuaternion(refChain.Add("localRotation"), "localRotation", ref localRotation); transform.localRotation = localRotation; var localScale = transform.localScale; - OnSceneTreeReflectUnityEngineVector3(refChain.Add("localScale"), transform, "localScale", ref localScale); + OnSceneTreeReflectUnityEngineVector3(refChain.Add("localScale"), "localScale", ref localScale); transform.localScale = localScale; } - private static void OnSceneTreeReflectUnityEngineQuaternion(ReferenceChain refChain, T obj, string name, ref Quaternion vec) + private static void OnSceneTreeReflectUnityEngineQuaternion(ReferenceChain refChain, string name, ref Quaternion vec) { - if (!SceneExplorerCommon.SceneTreeCheckDepth(refChain)) return; - - GUIControls.QuaternionField(refChain.ToString(), name, ref vec, ModTools.Instance.config.sceneExplorerTreeIdentSpacing * refChain.Ident, () => + if (!SceneExplorerCommon.SceneTreeCheckDepth(refChain)) { - try - { - ModTools.Instance.watches.AddWatch(refChain); - } - catch (Exception ex) - { - Log.Error("Exception in ModTools:OnSceneTreeReflectUnityEngineQuaternion - " + ex.Message); - } - }); + return; + } + + vec = GUIControls.CustomValueField( + refChain.UniqueId, + name, + GUIControls.PresentQuaternion, + vec, + MainWindow.Instance.Config.TreeIdentSpacing * refChain.Ident); } - private static void OnSceneTreeReflectUnityEngineVector3(ReferenceChain refChain, T obj, string name, ref UnityEngine.Vector3 vec) + private static void OnSceneTreeReflectUnityEngineVector3(ReferenceChain refChain, string name, ref Vector3 vec) { - if (!SceneExplorerCommon.SceneTreeCheckDepth(refChain)) return; - - GUIControls.Vector3Field(refChain.ToString(), name, ref vec, ModTools.Instance.config.sceneExplorerTreeIdentSpacing * refChain.Ident, () => + if (!SceneExplorerCommon.SceneTreeCheckDepth(refChain)) { - try - { - ModTools.Instance.watches.AddWatch(refChain); - } - catch (Exception ex) - { - Log.Error("Exception in ModTools:OnSceneTreeReflectUnityEngineVector3 - " + ex.Message); - } - }); + return; + } + + vec = GUIControls.CustomValueField( + refChain.UniqueId, + name, + GUIControls.PresentVector3, + vec, + MainWindow.Instance.Config.TreeIdentSpacing * refChain.Ident); } } } \ No newline at end of file diff --git a/Debugger/Utils/GameObjectUtil.cs b/Debugger/Explorer/GameObjectUtil.cs similarity index 56% rename from Debugger/Utils/GameObjectUtil.cs rename to Debugger/Explorer/GameObjectUtil.cs index 99d8d20..fe63bfb 100644 --- a/Debugger/Utils/GameObjectUtil.cs +++ b/Debugger/Explorer/GameObjectUtil.cs @@ -1,17 +1,15 @@ using System.Collections.Generic; using UnityEngine; -namespace ModTools +namespace ModTools.Explorer { - public static class GameObjectUtil + internal static class GameObjectUtil { - public static Dictionary FindSceneRoots() { - Dictionary roots = new Dictionary(); + var roots = new Dictionary(); - GameObject[] objects = GameObject.FindObjectsOfType(); - foreach (var obj in objects) + foreach (var obj in Object.FindObjectsOfType()) { if (!roots.ContainsKey(obj.transform.root.gameObject)) { @@ -30,6 +28,7 @@ public static List> FindComponentsOfType(str { FindComponentsOfType(typeName, root, list); } + return list; } @@ -41,34 +40,16 @@ public static void FindComponentsOfType(string typeName, GameObject gameObject, list.Add(new KeyValuePair(gameObject, component)); } - for (int i = 0; i < gameObject.transform.childCount; i++) + for (var i = 0; i < gameObject.transform.childCount; i++) { FindComponentsOfType(typeName, gameObject.transform.GetChild(i).gameObject, list); } } - public static string WhereIs(GameObject gameObject, bool logToConsole = true) - { - string outResult = gameObject.name; - WhereIsInternal(gameObject, ref outResult); - - if (logToConsole) - { - Debug.LogWarning(outResult); - } - - return outResult; - } - - private static void WhereIsInternal(GameObject gameObject, ref string outResult) + public static bool ComponentIsEnabled(Component component) { - outResult = gameObject.name + "." + outResult; - if (gameObject.transform.parent != null) - { - WhereIsInternal(gameObject.transform.parent.gameObject, ref outResult); - } + var prop = component.GetType().GetProperty("enabled"); + return prop == null || (bool)prop.GetValue(component, null); } - } - -} +} \ No newline at end of file diff --git a/Debugger/Explorer/MeshViewer.cs b/Debugger/Explorer/MeshViewer.cs new file mode 100644 index 0000000..c79eb13 --- /dev/null +++ b/Debugger/Explorer/MeshViewer.cs @@ -0,0 +1,265 @@ +using System; +using ColossalFramework.UI; +using ModTools.UI; +using ModTools.Utils; +using UnityEngine; + +namespace ModTools.Explorer +{ + internal sealed class MeshViewer : GUIWindow, IGameObject + { + private readonly RenderTexture targetRT; + private readonly Camera meshViewerCamera; + private readonly Light light; + + private Mesh previewMesh; + private Material previewMaterial; + private string assetName; + + private float distance = 4f; + private Vector2 previewDir = new Vector2(120f, -20f); + private Bounds bounds; + private Vector2 lastLeftMousePos = Vector2.zero; + private Vector2 lastRightMousePos = Vector2.zero; + + private Material material; + + private bool useOriginalShader; + + public MeshViewer() + : base("Mesh Viewer", new Rect(512, 128, 512, 512), Skin) + { + try + { + light = GameObject.Find("Directional Light").GetComponent(); + } + catch (Exception) + { + light = null; + } + + meshViewerCamera = gameObject.AddComponent(); + meshViewerCamera.transform.position = new Vector3(-10000.0f, -10000.0f, -10000.0f); + meshViewerCamera.fieldOfView = 30f; + meshViewerCamera.backgroundColor = Color.grey; + meshViewerCamera.nearClipPlane = 1.0f; + meshViewerCamera.farClipPlane = 1000.0f; + meshViewerCamera.enabled = false; + meshViewerCamera.allowHDR = true; + + targetRT = new RenderTexture(512, 512, 24, RenderTextureFormat.ARGBHalf, RenderTextureReadWrite.Linear); + meshViewerCamera.targetTexture = targetRT; + } + + public static MeshViewer CreateMeshViewer(string assetName, Mesh mesh, Material material, bool calculateBounds = true) + { + var go = new GameObject("MeshViewer"); + go.transform.parent = MainWindow.Instance.transform; + var meshViewer = go.AddComponent(); + meshViewer.assetName = assetName; + meshViewer.previewMesh = mesh; + meshViewer.material = material; + + meshViewer.previewMaterial = new Material(Shader.Find("Diffuse")); + if (material != null) + { + meshViewer.previewMaterial.mainTexture = material.mainTexture; + } + + meshViewer.Setup(calculateBounds); + meshViewer.Visible = true; + meshViewer.SetCitizenInfoObjects(false); + return meshViewer; + } + + public void Update() + { + if (previewMesh == null) + { + return; + } + + var intensity = 0.0f; + var color = Color.black; + var enabled = false; + + if (light != null) + { + intensity = light.intensity; + color = light.color; + enabled = light.enabled; + light.intensity = 2.0f; + light.color = Color.white; + light.enabled = true; + } + + var magnitude = bounds.extents.magnitude; + var num1 = magnitude + 16f; + var num2 = magnitude * distance; + meshViewerCamera.transform.position = -Vector3.forward * num2; + meshViewerCamera.transform.rotation = Quaternion.identity; + meshViewerCamera.nearClipPlane = Mathf.Max(num2 - num1 * 1.5f, 0.01f); + meshViewerCamera.farClipPlane = num2 + num1 * 1.5f; + var q = Quaternion.Euler(previewDir.y, 0.0f, 0.0f) + * Quaternion.Euler(0.0f, previewDir.x, 0.0f); + var trs = Matrix4x4.TRS(q * -bounds.center, q, Vector3.one); + + var material1 = (useOriginalShader && material != null) ? material : previewMaterial; + Graphics.DrawMesh(previewMesh, trs, material1, 0, meshViewerCamera, 0, null, false, false); + meshViewerCamera.RenderWithShader(material1.shader, string.Empty); + + if (light != null) + { + light.intensity = intensity; + light.color = color; + light.enabled = enabled; + } + } + + public void Setup(bool calculateBounds) + { + if (previewMesh == null) + { + return; + } + + if (calculateBounds && previewMesh.isReadable) + { + bounds = new Bounds(Vector3.zero, Vector3.zero); + foreach (var vertex in previewMesh.vertices) + { + bounds.Encapsulate(vertex); + } + } + else + { + bounds = new Bounds(new Vector3(0, 0, 0), new Vector3(3, 3, 3)); + } + + distance = 4f; + } + + public void SetCitizenInfoObjects(bool enabled) + { + foreach (var i in Resources.FindObjectsOfTypeAll()) + { + i.gameObject.SetActive(enabled); + } + } + + protected override void OnWindowClosed() + { + SetCitizenInfoObjects(true); + Destroy(this); + } + + protected override void OnWindowDestroyed() + { + Destroy(meshViewerCamera); + Destroy(targetRT); + } + + protected override void DrawWindow() + { + if (previewMesh == null) + { + Title = "Mesh Viewer"; + GUILayout.Label("Use the Scene Explorer to select a Mesh for preview"); + return; + } + + Title = $"Previewing \"{assetName ?? previewMesh.name}\""; + + GUILayout.BeginHorizontal(); + + if (material != null) + { + useOriginalShader = GUILayout.Toggle(useOriginalShader, "Original Shader"); + if (previewMesh.isReadable) + { + if (GUILayout.Button("Dump mesh+textures", GUILayout.Width(160))) + { + DumpUtil.DumpMeshAndTextures(assetName ?? previewMesh.name, previewMesh, material); + } + } + else if (GUILayout.Button("Dump textures", GUILayout.Width(160))) + { + DumpUtil.DumpTextures(assetName, material); + } + } + else + { + useOriginalShader = false; + if (previewMesh.isReadable && GUILayout.Button("Dump mesh", GUILayout.Width(160))) + { + DumpUtil.DumpMeshAndTextures($"{previewMesh.name}", previewMesh); + } + } + + if (previewMesh.isReadable) + { + GUILayout.Label($"Triangles: {previewMesh.triangles.Length / 3}"); + } + else + { + var oldColor = GUI.color; + GUI.color = Color.yellow; + GUILayout.Label("Mesh isn't readable!"); + GUI.color = oldColor; + } + + if (material?.mainTexture != null) + { + GUILayout.Label($"Texture size: {material.mainTexture.width}x{material.mainTexture.height}"); + } + + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + if (Event.current.type == EventType.MouseDown) + { + if (Event.current.button == 0 || Event.current.button == 2) + { + lastLeftMousePos = Event.current.mousePosition; + } + else + { + lastRightMousePos = Event.current.mousePosition; + } + } + else if (Event.current.type == EventType.MouseDrag) + { + var pos = Event.current.mousePosition; + if (Event.current.button == 0 || Event.current.button == 2) + { + if (lastLeftMousePos != Vector2.zero) + { + var moveDelta = (pos - lastLeftMousePos) * 2.0f; + previewDir -= moveDelta / Mathf.Min(targetRT.width, targetRT.height) + * UIView.GetAView().ratio * 140f; + previewDir.y = Mathf.Clamp(previewDir.y, -90f, 90f); + } + + lastLeftMousePos = pos; + } + else + { + if (lastRightMousePos != Vector2.zero) + { + var moveDelta1 = pos - lastRightMousePos; + distance += (float)(moveDelta1.y / (double)targetRT.height + * UIView.GetAView().ratio * 40.0); + const float num1 = 6f; + var magnitude = bounds.extents.magnitude; + var num2 = magnitude + 16f; + distance = Mathf.Min(distance, 4f, num1 * (num2 / magnitude)); + } + + lastRightMousePos = pos; + } + } + + GUI.DrawTexture(new Rect(0.0f, 64.0f, WindowRect.width, WindowRect.height - 64.0f), targetRT, ScaleMode.StretchToFill, false); + } + } +} \ No newline at end of file diff --git a/Debugger/Explorer/Plopper.cs b/Debugger/Explorer/Plopper.cs new file mode 100644 index 0000000..f39efa0 --- /dev/null +++ b/Debugger/Explorer/Plopper.cs @@ -0,0 +1,125 @@ +using System.Reflection; +using ColossalFramework; +using UnityEngine; + +namespace ModTools.Explorer +{ + internal static class Plopper + { + private static PrefabInfo ploppedPrefab; + + public static void Reset() => ploppedPrefab = null; + + public static void Update() + { + if (ploppedPrefab == null) + { + return; + } + + var toolManager = Singleton.instance; + if (toolManager?.m_properties == null) + { + return; + } + + if (Input.GetKeyDown(KeyCode.Escape)) + { + Singleton.instance.m_properties.CurrentTool = ToolsModifierControl.GetTool(); + ploppedPrefab = null; + return; + } + + var currentTool = toolManager.m_properties.CurrentTool; + if (currentTool == null) + { + return; + } + + if (currentTool is BuildingTool || currentTool is NetTool || currentTool is TreeTool + || currentTool is PropTool) + { + var prefabField = currentTool.GetType() + .GetField("m_prefab", BindingFlags.Instance | BindingFlags.Public); + if (prefabField != null) + { + var prefab = prefabField.GetValue(currentTool); + if ((PrefabInfo)prefab != ploppedPrefab) + { + ploppedPrefab = null; + } + } + else + { + ploppedPrefab = null; + } + } + else + { + ploppedPrefab = null; + } + } + + public static void StartPlopping(PrefabInfo prefabInfo) + { + var currentTool = Singleton.instance.m_properties.CurrentTool; + if (currentTool == null) + { + return; + } + + var defaultToolActive = currentTool is DefaultTool; + + switch (prefabInfo) + { + case BuildingInfo buildingInfo when defaultToolActive || currentTool is BuildingTool: + var buildingTool = ToolsModifierControl.GetTool(); + if (buildingTool != null) + { + buildingTool.m_prefab = buildingInfo; + buildingTool.m_relocate = 0; + + SetCurrentTool(buildingTool); + } + + break; + + case NetInfo netInfo when defaultToolActive || currentTool is NetTool: + var netTool = ToolsModifierControl.GetTool(); + if (netTool != null) + { + netTool.m_prefab = netInfo; + SetCurrentTool(netTool); + } + + break; + + case PropInfo propInfo when defaultToolActive || currentTool is PropTool: + var propTool = ToolsModifierControl.GetTool(); + if (propTool != null) + { + propTool.m_prefab = propInfo; + SetCurrentTool(propTool); + } + + break; + + case TreeInfo treeInfo when defaultToolActive || currentTool is TreeTool: + var treeTool = ToolsModifierControl.GetTool(); + if (treeTool != null) + { + ploppedPrefab = treeInfo; + SetCurrentTool(treeTool); + } + + break; + } + + void SetCurrentTool(ToolBase tool) + { + Singleton.instance.m_properties.CurrentTool = tool; + ploppedPrefab = prefabInfo; + } + } + } +} \ No newline at end of file diff --git a/Debugger/Explorer/ReferenceChain.cs b/Debugger/Explorer/ReferenceChain.cs new file mode 100644 index 0000000..1e15a8b --- /dev/null +++ b/Debugger/Explorer/ReferenceChain.cs @@ -0,0 +1,307 @@ +using System; +using System.Collections; +using System.Reflection; +using System.Text; +using UnityEngine; + +namespace ModTools.Explorer +{ + internal sealed class ReferenceChain : ICloneable + { + private readonly object[] chainObjects = new object[MainWindow.Instance.Config.MaxHierarchyDepth]; + private readonly ReferenceType[] chainTypes = new ReferenceType[MainWindow.Instance.Config.MaxHierarchyDepth]; + private string uniqueId; + + public enum ReferenceType + { + None, + GameObject, + Component, + Field, + Property, + Method, + EnumerableItem, + SpecialNamedProperty, + } + + public int IdentOffset { get; set; } + + public int Ident => Length + IdentOffset - 1; + + public int Length { get; private set; } + + public object LastItem => chainObjects[Length - 1]; + + public string LastItemName => ItemToString(Length - 1); + + public ReferenceType FirstItemType => chainTypes[0]; + + public string UniqueId + { + get + { + if (!string.IsNullOrEmpty(uniqueId)) + { + return uniqueId; + } + + var stringBuilder = new StringBuilder(Length * 32); + for (var i = 0; i < Length; ++i) + { + stringBuilder + .Append(chainTypes[i]).Append(':') + .Append(ItemToString(i)).Append('.'); + } + + uniqueId = stringBuilder.ToString(); + return uniqueId; + } + } + + object ICloneable.Clone() => Clone(); + + public ReferenceChain Clone() + { + var clone = new ReferenceChain { Length = Length }; + for (var i = 0; i < Length; i++) + { + clone.chainObjects[i] = chainObjects[i]; + clone.chainTypes[i] = chainTypes[i]; + } + + clone.IdentOffset = IdentOffset; + + return clone; + } + + public bool CheckDepth() => Length >= MainWindow.Instance.Config.MaxHierarchyDepth; + + public ReferenceChain Add(GameObject go) + { + var copy = Clone(); + copy.chainObjects[Length] = go; + copy.chainTypes[Length] = ReferenceType.GameObject; + copy.Length++; + return copy; + } + + public ReferenceChain Add(Component component) + { + var copy = Clone(); + copy.chainObjects[Length] = component; + copy.chainTypes[Length] = ReferenceType.Component; + copy.Length++; + return copy; + } + + public ReferenceChain Add(FieldInfo fieldInfo) + { + var copy = Clone(); + copy.chainObjects[Length] = fieldInfo; + copy.chainTypes[Length] = ReferenceType.Field; + copy.Length++; + return copy; + } + + public ReferenceChain Add(PropertyInfo propertyInfo) + { + var copy = Clone(); + copy.chainObjects[Length] = propertyInfo; + copy.chainTypes[Length] = ReferenceType.Property; + copy.Length++; + return copy; + } + + public ReferenceChain Add(MethodInfo methodInfo) + { + var copy = Clone(); + copy.chainObjects[Length] = methodInfo; + copy.chainTypes[Length] = ReferenceType.Method; + copy.Length++; + return copy; + } + + public ReferenceChain Add(int index) + { + var copy = Clone(); + copy.chainObjects[Length] = index; + copy.chainTypes[Length] = ReferenceType.EnumerableItem; + copy.Length++; + return copy; + } + + public ReferenceChain Add(string namedProperty) + { + var copy = Clone(); + copy.chainObjects[Length] = namedProperty; + copy.chainTypes[Length] = ReferenceType.SpecialNamedProperty; + copy.Length++; + return copy; + } + + public ReferenceChain Trim(int num) + { + var copy = Clone(); + copy.Length = Mathf.Min(num, Length); + return copy; + } + + public object GetChainItem(int index) => index >= 0 && index < Length ? chainObjects[index] : null; + + public ReferenceType GetChainItemType(int index) + => index >= 0 && index < Length ? chainTypes[index] : ReferenceType.None; + + public override string ToString() + { + switch (Length) + { + case 0: + return string.Empty; + + case 1: + return ItemToString(0); + } + + var result = new StringBuilder(); + result.Append(ItemToString(0)); + + for (var i = 1; i < Length; i++) + { + if (chainTypes[i] != ReferenceType.EnumerableItem) + { + result.Append("."); + } + + result.Append(ItemToString(i)); + } + + return result.ToString(); + } + + public ReferenceChain GetReversedCopy() + { + var copy = new ReferenceChain + { + Length = Length, + IdentOffset = IdentOffset, + }; + + for (var i = 0; i < Length; i++) + { + copy.chainObjects[Length - i - 1] = chainObjects[i]; + copy.chainTypes[Length - i - 1] = chainTypes[i]; + } + + return copy; + } + + public object Evaluate() + { + object current = null; + for (var i = 0; i < Length; i++) + { + switch (chainTypes[i]) + { + case ReferenceType.GameObject: + case ReferenceType.Component: + current = chainObjects[i]; + break; + + case ReferenceType.Field: + current = ((FieldInfo)chainObjects[i]).GetValue(current); + break; + + case ReferenceType.Property: + current = ((PropertyInfo)chainObjects[i]).GetValue(current, null); + break; + + case ReferenceType.Method: + break; + + case ReferenceType.EnumerableItem: + var collection = current as IEnumerable; + var itemCount = 0; + foreach (var item in collection) + { + if (itemCount == (int)chainObjects[i]) + { + current = item; + break; + } + + itemCount++; + } + + break; + + case ReferenceType.SpecialNamedProperty: + break; + } + } + + return current; + } + + public bool IsSameChain(ReferenceChain other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + if (Length != other.Length) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + for (var i = 0; i < Length; ++i) + { + if (chainTypes[i] != other.chainTypes[i]) + { + return false; + } + + if (chainObjects[i] != other.chainObjects[i]) + { + return false; + } + } + + return true; + } + + private string ItemToString(int i) + { + switch (chainTypes[i]) + { + case ReferenceType.GameObject: + return ((GameObject)chainObjects[i]).name; + + case ReferenceType.Component: + return ((Component)chainObjects[i]).name; + + case ReferenceType.Field: + return ((FieldInfo)chainObjects[i]).Name; + + case ReferenceType.Property: + return ((PropertyInfo)chainObjects[i]).Name; + + case ReferenceType.Method: + return ((MethodInfo)chainObjects[i]).Name; + + case ReferenceType.EnumerableItem: + return "[" + chainObjects[i] + "]"; + + case ReferenceType.SpecialNamedProperty: + return (string)chainObjects[i]; + } + + return string.Empty; + } + } +} \ No newline at end of file diff --git a/Debugger/Explorer/SceneExplorer.cs b/Debugger/Explorer/SceneExplorer.cs new file mode 100644 index 0000000..7715eae --- /dev/null +++ b/Debugger/Explorer/SceneExplorer.cs @@ -0,0 +1,488 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using ModTools.UI; +using ModTools.Utils; +using UnityEngine; + +namespace ModTools.Explorer +{ + internal sealed class SceneExplorer : GUIWindow, IGameObject, IAwakingObject + { + private const float WindowTopMargin = 16.0f; + private const float WindowBottomMargin = 8.0f; + + private const float HeaderHeightCompact = 1.65f; + private const float HeaderHeightExpanded = 13.75f; + + private const float SceneTreeWidth = 320.0f; + + private const string ExpandDownButtonText = " ▼ ▼ ▼ "; + private const string CollapseUpButtonText = " ▲ ▲ ▲ "; + private const string ExpandRightButtonText = " ▶▶▶ "; + private const string CollapseLeftButtonText = " ◀◀◀ "; + + private readonly GUIArea headerArea; + private readonly GUIArea sceneTreeArea; + private readonly GUIArea componentArea; + + private Dictionary sceneRoots = new Dictionary(); + + private string findGameObjectFilter = string.Empty; + private string findObjectTypeFilter = string.Empty; + private string searchDisplayString = string.Empty; + + private Vector2 sceneTreeScrollPosition = Vector2.zero; + private Vector2 componentScrollPosition = Vector2.zero; + private SceneExplorerState state; + + private bool headerExpanded; + private bool treeExpanded = true; + + public SceneExplorer() + : base("Scene Explorer", new Rect(128, 440, 800, 500), Skin) + { + headerArea = new GUIArea(this) + .ChangeSizeRelative(height: 0) + .OffsetBy(vertical: WindowTopMargin); + + sceneTreeArea = new GUIArea(this) + .ChangeSizeRelative(width: 0) + .ChangeSizeBy(width: SceneTreeWidth); + + componentArea = new GUIArea(this) + .OffsetBy(horizontal: SceneTreeWidth) + .ChangeSizeBy(width: -SceneTreeWidth); + + state = new SceneExplorerState(); + + RecalculateAreas(); + } + + public void Awake() => Plopper.Reset(); + + public void Update() => Plopper.Update(); + + public void RecalculateAreas() + { + if (!treeExpanded || WindowRect.width < Screen.width / 4) + { + sceneTreeArea.ChangeSizeBy(width: 0); + + componentArea + .OffsetBy(horizontal: 0) + .ChangeSizeBy(width: 0f); + } + else + { + sceneTreeArea.ChangeSizeBy(width: SceneTreeWidth); + + componentArea + .OffsetBy(horizontal: SceneTreeWidth) + .ChangeSizeBy(width: -SceneTreeWidth); + } + + var headerHeight = headerExpanded ? HeaderHeightExpanded : HeaderHeightCompact; + headerHeight *= MainWindow.Instance.Config.FontSize; + headerHeight += 32.0f; + + var verticalOffset = headerHeight - WindowTopMargin; + var verticalSizeOffset = -(verticalOffset + WindowBottomMargin); + + headerArea.ChangeSizeBy(height: verticalOffset); + + sceneTreeArea + .OffsetBy(vertical: verticalOffset) + .ChangeSizeBy(height: verticalSizeOffset); + + componentArea + .OffsetBy(vertical: verticalOffset) + .ChangeSizeBy(height: verticalSizeOffset); + } + + public void Refresh() + { + sceneRoots = GameObjectUtil.FindSceneRoots(); + TypeUtil.ClearTypeCache(); + } + + public void Show(ReferenceChain refChain) + { + if (refChain == null) + { + Logger.Error("SceneExplorer: ExpandFromRefChain(): Null refChain"); + return; + } + + if (refChain.Length == 0) + { + Logger.Error("SceneExplorer: ExpandFromRefChain(): Invalid refChain, expected Length >= 0"); + return; + } + + if (refChain.FirstItemType != ReferenceChain.ReferenceType.GameObject) + { + Logger.Error($"SceneExplorer: ExpandFromRefChain(): invalid chain type for element [0] - expected {ReferenceChain.ReferenceType.GameObject}, got {refChain.FirstItemType}"); + return; + } + + sceneRoots.Clear(); + ClearExpanded(); + searchDisplayString = $"Showing results for \"{refChain}\""; + + var rootGameObject = (GameObject)refChain.GetChainItem(0); + sceneRoots.Add(rootGameObject, true); + + var expandedRefChain = new ReferenceChain().Add(rootGameObject); + state.ExpandedGameObjects.Add(expandedRefChain.UniqueId); + + for (var i = 1; i < refChain.Length; i++) + { + switch (refChain.GetChainItemType(i)) + { + case ReferenceChain.ReferenceType.GameObject: + var go = (GameObject)refChain.GetChainItem(i); + expandedRefChain = expandedRefChain.Add(go); + state.ExpandedGameObjects.Add(expandedRefChain.UniqueId); + break; + + case ReferenceChain.ReferenceType.Component: + var component = (Component)refChain.GetChainItem(i); + expandedRefChain = expandedRefChain.Add(component); + state.ExpandedComponents.Add(expandedRefChain.UniqueId); + break; + + case ReferenceChain.ReferenceType.Field: + var field = (FieldInfo)refChain.GetChainItem(i); + expandedRefChain = expandedRefChain.Add(field); + state.ExpandedObjects.Add(expandedRefChain.UniqueId); + break; + + case ReferenceChain.ReferenceType.Property: + var property = (PropertyInfo)refChain.GetChainItem(i); + expandedRefChain = expandedRefChain.Add(property); + state.ExpandedObjects.Add(expandedRefChain.UniqueId); + break; + + case ReferenceChain.ReferenceType.EnumerableItem: + var index = (int)refChain.GetChainItem(i); + state.SelectedArrayStartIndices[expandedRefChain.UniqueId] = index; + state.SelectedArrayEndIndices[expandedRefChain.UniqueId] = index; + expandedRefChain = expandedRefChain.Add(index); + state.ExpandedObjects.Add(expandedRefChain.UniqueId); + break; + } + } + + state.CurrentRefChain = refChain.Clone(); + state.CurrentRefChain.IdentOffset = -state.CurrentRefChain.Length; + Visible = true; + } + + public void DrawHeader() + { + headerArea.Begin(); + + if (headerExpanded) + { + DrawExpandedHeader(); + } + + GUILayout.BeginHorizontal(); + + if (GUILayout.Button(treeExpanded ? CollapseLeftButtonText : ExpandRightButtonText, GUILayout.ExpandWidth(false))) + { + treeExpanded = !treeExpanded; + RecalculateAreas(); + } + + GUILayout.Space(MainWindow.Instance.Config.TreeIdentSpacing); + + if (GUILayout.Button(headerExpanded ? CollapseUpButtonText : ExpandDownButtonText)) + { + headerExpanded = !headerExpanded; + RecalculateAreas(); + } + + GUILayout.EndHorizontal(); + + headerArea.End(); + } + + public void DrawExpandedHeader() + { + GUILayout.BeginHorizontal(); + + GUI.contentColor = Color.green; + GUILayout.Label("Show:", GUILayout.ExpandWidth(false)); + + var configChanged = false; + + var showFields = GUILayout.Toggle(MainWindow.Instance.Config.ShowFields, " Fields"); + if (MainWindow.Instance.Config.ShowFields != showFields) + { + MainWindow.Instance.Config.ShowFields = showFields; + configChanged = true; + } + + GUILayout.Space(MainWindow.Instance.Config.TreeIdentSpacing); + var showConsts = GUILayout.Toggle(MainWindow.Instance.Config.ShowConsts, " Constants"); + if (MainWindow.Instance.Config.ShowConsts != showConsts) + { + MainWindow.Instance.Config.ShowConsts = showConsts; + configChanged = true; + } + + GUILayout.Space(MainWindow.Instance.Config.TreeIdentSpacing); + var showProperties = GUILayout.Toggle(MainWindow.Instance.Config.ShowProperties, " Properties"); + if (MainWindow.Instance.Config.ShowProperties != showProperties) + { + MainWindow.Instance.Config.ShowProperties = showProperties; + configChanged = true; + } + + GUILayout.Space(MainWindow.Instance.Config.TreeIdentSpacing); + var showMethods = GUILayout.Toggle(MainWindow.Instance.Config.ShowMethods, " Methods"); + if (MainWindow.Instance.Config.ShowMethods != showMethods) + { + MainWindow.Instance.Config.ShowMethods = showMethods; + configChanged = true; + } + + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + var showModifiers = GUILayout.Toggle(MainWindow.Instance.Config.ShowModifiers, " Show field / property modifiers"); + if (showModifiers != MainWindow.Instance.Config.ShowModifiers) + { + MainWindow.Instance.Config.ShowModifiers = showModifiers; + configChanged = true; + } + + var showInheritedMembers = GUILayout.Toggle(MainWindow.Instance.Config.ShowInheritedMembers, " Show inherited members"); + if (showInheritedMembers != MainWindow.Instance.Config.ShowInheritedMembers) + { + MainWindow.Instance.Config.ShowInheritedMembers = showInheritedMembers; + configChanged = true; + TypeUtil.ClearTypeCache(); + } + + var evaluatePropertiesAutomatically = GUILayout.Toggle(MainWindow.Instance.Config.EvaluateProperties, " Evaluate properties automatically"); + if (evaluatePropertiesAutomatically != MainWindow.Instance.Config.EvaluateProperties) + { + MainWindow.Instance.Config.EvaluateProperties = evaluatePropertiesAutomatically; + configChanged = true; + } + + var sortAlphabetically = GUILayout.Toggle(MainWindow.Instance.Config.SortItemsAlphabetically, " Sort alphabetically"); + if (sortAlphabetically != MainWindow.Instance.Config.SortItemsAlphabetically) + { + MainWindow.Instance.Config.SortItemsAlphabetically = sortAlphabetically; + configChanged = true; + } + + if (configChanged) + { + MainWindow.Instance.SaveConfig(); + } + + GUI.contentColor = Color.white; + DrawFindGameObjectPanel(); + } + + public void DrawSceneTree() + { + var gameObjects = sceneRoots.Keys.ToArray(); + + if (MainWindow.Instance.Config.SortItemsAlphabetically) + { + Array.Sort(gameObjects, (x, y) => string.CompareOrdinal(x?.name, y?.name)); + } + + if (!sceneTreeArea.Begin()) + { + return; + } + + GUILayout.BeginHorizontal(); + + if (GUILayout.Button("Refresh", GUILayout.ExpandWidth(false))) + { + Refresh(); + } + + if (GUILayout.Button("Fold all / Clear", GUILayout.ExpandWidth(false))) + { + ClearExpanded(); + Refresh(); + } + + GUILayout.EndHorizontal(); + + if (!string.IsNullOrEmpty(searchDisplayString)) + { + GUI.contentColor = Color.green; + GUILayout.Label(searchDisplayString); + GUI.contentColor = Color.white; + } + + sceneTreeScrollPosition = GUILayout.BeginScrollView(sceneTreeScrollPosition); + + foreach (var obj in gameObjects) + { + GUIRecursiveTree.OnSceneTreeRecursive(gameObject, state, new ReferenceChain().Add(obj), obj); + } + + GUILayout.EndScrollView(); + + sceneTreeArea.End(); + } + + public void DrawComponent() + { + componentArea.Begin(); + + componentScrollPosition = GUILayout.BeginScrollView(componentScrollPosition); + + if (state.CurrentRefChain != null) + { + try + { + GUIReflect.OnSceneTreeReflect(state, state.CurrentRefChain, state.CurrentRefChain.Evaluate()); + } + catch (Exception e) + { + Debug.LogException(e); + state.CurrentRefChain = null; + throw; + } + } + + GUILayout.EndScrollView(); + + componentArea.End(); + } + + protected override void HandleException(Exception ex) + { + Debug.LogException(ex); + state = new SceneExplorerState(); + sceneRoots = GameObjectUtil.FindSceneRoots(); + TypeUtil.ClearTypeCache(); + } + + protected override void DrawWindow() + { + RecalculateAreas(); + + var enterPressed = Event.current.type == EventType.KeyDown && (Event.current.keyCode == KeyCode.Return || Event.current.keyCode == KeyCode.KeypadEnter); + + if (enterPressed) + { + GUI.FocusControl(null); + } + + state.PreventCircularReferences.Clear(); + + DrawHeader(); + DrawSceneTree(); + DrawComponent(); + } + + private void ClearExpanded() + { + state.ExpandedGameObjects.Clear(); + state.ExpandedComponents.Clear(); + state.ExpandedObjects.Clear(); + state.EvaluatedProperties.Clear(); + state.SelectedArrayStartIndices.Clear(); + state.SelectedArrayEndIndices.Clear(); + searchDisplayString = string.Empty; + sceneTreeScrollPosition = Vector2.zero; + state.CurrentRefChain = null; + TypeUtil.ClearTypeCache(); + } + + private void DrawFindGameObjectPanel() + { + GUILayout.BeginHorizontal(); + GUILayout.Label("GameObject.Find"); + findGameObjectFilter = GUILayout.TextField(findGameObjectFilter, GUILayout.Width(256)); + + if (findGameObjectFilter.Trim().Length == 0) + { + GUI.enabled = false; + } + + if (GUILayout.Button("Find")) + { + ClearExpanded(); + var go = GameObject.Find(findGameObjectFilter.Trim()); + if (go != null) + { + sceneRoots.Clear(); + state.ExpandedGameObjects.Add(new ReferenceChain().Add(go).UniqueId); + sceneRoots.Add(go, true); + sceneTreeScrollPosition = Vector2.zero; + searchDisplayString = $"Showing results for GameObject.Find(\"{findGameObjectFilter}\")"; + } + } + + if (GUILayout.Button("Reset")) + { + ClearExpanded(); + sceneRoots = GameObjectUtil.FindSceneRoots(); + sceneTreeScrollPosition = Vector2.zero; + searchDisplayString = string.Empty; + } + + GUI.enabled = true; + + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(); + GUILayout.Label("GameObject.FindObjectsOfType"); + findObjectTypeFilter = GUILayout.TextField(findObjectTypeFilter, GUILayout.Width(256)); + + if (findObjectTypeFilter.Trim().Length == 0) + { + GUI.enabled = false; + } + + if (GUILayout.Button("Find")) + { + var gameObjects = GameObjectUtil.FindComponentsOfType(findObjectTypeFilter.Trim()); + + sceneRoots.Clear(); + foreach (var item in gameObjects) + { + ClearExpanded(); + state.ExpandedGameObjects.Add(new ReferenceChain().Add(item.Key).UniqueId); + if (gameObjects.Count == 1) + { + state.ExpandedComponents.Add(new ReferenceChain().Add(item.Key).Add(item.Value).UniqueId); + } + + sceneRoots.Add(item.Key, true); + sceneTreeScrollPosition = Vector2.zero; + searchDisplayString = $"Showing results for GameObject.FindObjectsOfType({findObjectTypeFilter})"; + } + } + + if (GUILayout.Button("Reset")) + { + ClearExpanded(); + sceneRoots = GameObjectUtil.FindSceneRoots(); + sceneTreeScrollPosition = Vector2.zero; + searchDisplayString = string.Empty; + } + + GUI.enabled = true; + + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + } + } +} \ No newline at end of file diff --git a/Debugger/Explorer/SceneExplorerCommon.cs b/Debugger/Explorer/SceneExplorerCommon.cs new file mode 100644 index 0000000..b3575c5 --- /dev/null +++ b/Debugger/Explorer/SceneExplorerCommon.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using System.Text; +using UnityEngine; + +namespace ModTools.Explorer +{ + internal static class SceneExplorerCommon + { + private static readonly Dictionary IndentStrings = new Dictionary(); + + internal static void OnSceneTreeMessage(ReferenceChain refChain, string message) + { + GUILayout.BeginHorizontal(); + InsertIndent(refChain.Ident); + GUILayout.Label(message); + GUILayout.EndHorizontal(); + } + + internal static bool SceneTreeCheckDepth(ReferenceChain refChain) + { + if (refChain.CheckDepth()) + { + OnSceneTreeMessage(refChain, "Hierarchy too deep, sorry :("); + return false; + } + + return true; + } + + internal static void InsertIndent(int indent) + { + if (indent <= 0) + { + return; + } + + if (!IndentStrings.TryGetValue(indent, out var indentString)) + { + indentString = new StringBuilder().Insert(0, "· ", indent).ToString(); + IndentStrings.Add(indent, indentString); + } + + GUILayout.Label(indentString, GUILayout.Width(MainWindow.Instance.Config.TreeIdentSpacing * indent)); + } + } +} \ No newline at end of file diff --git a/Debugger/Explorer/SceneExplorerState.cs b/Debugger/Explorer/SceneExplorerState.cs new file mode 100644 index 0000000..da06850 --- /dev/null +++ b/Debugger/Explorer/SceneExplorerState.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; + +namespace ModTools.Explorer +{ + internal sealed class SceneExplorerState + { + public HashSet ExpandedGameObjects { get; } = new HashSet(); + + public HashSet ExpandedComponents { get; } = new HashSet(); + + public HashSet ExpandedObjects { get; } = new HashSet(); + + public HashSet EvaluatedProperties { get; } = new HashSet(); + + public Dictionary SelectedArrayStartIndices { get; } = new Dictionary(); + + public Dictionary SelectedArrayEndIndices { get; } = new Dictionary(); + + public HashSet PreventCircularReferences { get; } = new HashSet(); + + public ReferenceChain CurrentRefChain { get; set; } + } +} \ No newline at end of file diff --git a/Debugger/Explorer/TextureViewer.cs b/Debugger/Explorer/TextureViewer.cs new file mode 100644 index 0000000..2c738cb --- /dev/null +++ b/Debugger/Explorer/TextureViewer.cs @@ -0,0 +1,54 @@ +using ModTools.UI; +using ModTools.Utils; +using UnityEngine; + +namespace ModTools.Explorer +{ + internal sealed class TextureViewer : GUIWindow + { + private Texture previewTexture; + + private TextureViewer() + : base("Texture Viewer", new Rect(512, 128, 512, 512), Skin) + { + } + + public static TextureViewer CreateTextureViewer(Texture texture) + { + var go = new GameObject("TextureViewer"); + go.transform.parent = MainWindow.Instance.transform; + var textureViewer = go.AddComponent(); + var texture3D = texture as Texture3D; + textureViewer.previewTexture = texture3D == null ? texture : texture3D.ToTexture2D(); + textureViewer.Visible = true; + return textureViewer; + } + + protected override void OnWindowClosed() => Destroy(this); + + protected override void DrawWindow() + { + if (previewTexture == null) + { + Title = "Texture Viewer"; + GUILayout.Label("Use the Scene Explorer to select a Texture for preview"); + return; + } + + Title = $"Previewing \"{previewTexture.name}\""; + + if (GUILayout.Button("Dump .png", GUILayout.Width(128))) + { + TextureUtil.DumpTextureToPNG(previewTexture); + } + + var aspect = previewTexture.width / (previewTexture.height + 60.0f); + var newWindowRect = WindowRect; + + newWindowRect.width = newWindowRect.height * aspect; + MoveResize(newWindowRect); + + GUI.DrawTexture(new Rect(0.0f, 60.0f, WindowRect.width, WindowRect.height - 60.0f), previewTexture, ScaleMode.ScaleToFit, false); + } + } +} \ No newline at end of file diff --git a/Debugger/Watches.cs b/Debugger/Explorer/Watches.cs similarity index 78% rename from Debugger/Watches.cs rename to Debugger/Explorer/Watches.cs index 8e156c0..a548233 100644 --- a/Debugger/Watches.cs +++ b/Debugger/Explorer/Watches.cs @@ -1,29 +1,29 @@ using System; using System.Collections.Generic; using System.Reflection; +using ModTools.UI; +using ModTools.Utils; using UnityEngine; -namespace ModTools +namespace ModTools.Explorer { - public class Watches : GUIWindow + internal sealed class Watches : GUIWindow { - private readonly List watches = new List(); - private Configuration config => ModTools.Instance.config; - private Vector2 watchesScroll = Vector2.zero; public Watches() - : base("Watches", new Rect(504, 128, 800, 300), skin) + : base("Watches", new Rect(504, 128, 800, 300), Skin) { - onDraw = DoWatchesWindow; } + private static ModConfiguration Config => MainWindow.Instance.Config; + public void AddWatch(ReferenceChain refChain) { watches.Add(refChain); - visible = true; + Visible = true; } public void RemoveWatch(ReferenceChain refChain) @@ -43,10 +43,11 @@ public Type GetWatchType(ReferenceChain refChain) var info = item as FieldInfo; ret = info?.FieldType ?? (item as PropertyInfo)?.PropertyType; } + return ret; } - void DoWatchesWindow() + protected override void DrawWindow() { watchesScroll = GUILayout.BeginScrollView(watchesScroll); @@ -56,9 +57,9 @@ void DoWatchesWindow() var type = GetWatchType(watch); - GUI.contentColor = config.typeColor; + GUI.contentColor = Config.TypeColor; GUILayout.Label(type.ToString()); - GUI.contentColor = config.nameColor; + GUI.contentColor = Config.NameColor; GUILayout.Label(watch.ToString()); GUI.contentColor = Color.white; GUILayout.Label(" = "); @@ -66,7 +67,7 @@ void DoWatchesWindow() GUI.enabled = false; var value = watch.Evaluate(); - GUI.contentColor = config.valueColor; + GUI.contentColor = Config.ValueColor; if (value == null || !TypeUtil.IsSpecialType(type)) { @@ -76,7 +77,7 @@ void DoWatchesWindow() { try { - GUIControls.EditorValueField(watch, "watch." + watch, type, value); + GUIControls.EditorValueField("watch." + watch, type, value); } catch (Exception) { @@ -93,8 +94,7 @@ void DoWatchesWindow() if (GUILayout.Button("Find in Scene Explorer")) { var sceneExplorer = FindObjectOfType(); - sceneExplorer.ExpandFromRefChain(watch.Trim(watch.Length - 1)); - sceneExplorer.visible = true; + sceneExplorer.Show(watch.Trim(watch.Length - 1)); } if (GUILayout.Button("x", GUILayout.Width(24))) @@ -107,7 +107,5 @@ void DoWatchesWindow() GUILayout.EndScrollView(); } - } - -} +} \ No newline at end of file diff --git a/Debugger/GUICommon/ColorPicker.cs b/Debugger/GUICommon/ColorPicker.cs deleted file mode 100644 index 7332c74..0000000 --- a/Debugger/GUICommon/ColorPicker.cs +++ /dev/null @@ -1,209 +0,0 @@ -using System; -using System.Collections.Generic; -using UnityEngine; - -namespace ModTools -{ - public class ColorPicker : GUIWindow - { - - public delegate void OnColorChanged(Color color); - - public delegate void OnColor32Changed(Color32 color); - - private static Dictionary textureCache = new Dictionary(); - - public static Texture2D GetColorTexture(string hash, Color color) - { - if (!textureCache.ContainsKey(hash)) - { - textureCache.Add(hash, new Texture2D(1, 1)); - } - - var texture = textureCache[hash]; - texture.SetPixel(0, 0, color); - texture.Apply(); - return texture; - } - - private readonly int colorPickerSize = 142; - private readonly int huesBarWidth = 26; - - private Texture2D colorPickerTexture; - private Texture2D huesBarTexture; - private ColorUtil.HSV currentHSV; - private float originalAlpha = 0.0f; - - private OnColorChanged onColorChanged; - private OnColor32Changed onColor32Changed; - - private Rect colorPickerRect; - private Rect huesBarRect; - - private Texture2D lineTexTexture; - private static Color lineColor = Color.white; - - public ColorPicker() : base("ColorPicker", new Rect(16.0f, 16.0f, 188.0f, 156.0f), skin) - { - resizable = false; - hasTitlebar = false; - hasCloseButton = false; - - onDraw = DrawWindow; - onException = HandleException; - - colorPickerRect = new Rect(8.0f, 8.0f, colorPickerSize, colorPickerSize); - huesBarRect = new Rect(colorPickerRect.x + colorPickerSize + 4.0f, colorPickerRect.y, huesBarWidth, colorPickerRect.height); - visible = false; - } - - private Texture2D colorPicker => colorPickerTexture ?? (colorPickerTexture = new Texture2D(colorPickerSize, colorPickerSize)); - - private Texture2D huesBar => huesBarTexture ?? (huesBarTexture = DrawHuesBar(huesBarWidth, colorPickerSize)); - - private Texture2D lineTex => lineTexTexture ?? (lineTexTexture = DrawLineTex()); - - public void SetColor(Color color, OnColorChanged _onColorChanged) - { - originalAlpha = color.a; - currentHSV = ColorUtil.HSV.RGB2HSV(color); - currentHSV.h = 360.0f - currentHSV.h; - onColorChanged = _onColorChanged; - RedrawPicker(); - } - - public void SetColor(Color32 color, OnColor32Changed _onColor32Changed) - { - originalAlpha = color.a / 255.0f; - currentHSV = ColorUtil.HSV.RGB2HSV(color); - currentHSV.h = 360.0f - currentHSV.h; - onColor32Changed = _onColor32Changed; - RedrawPicker(); - } - - void RedrawPicker() - { - DrawColorPicker(colorPicker, currentHSV.h); - } - - void HandleException(Exception ex) - { - Log.Error("Exception in ColorPicker - " + ex.Message); - visible = false; - } - - void DrawWindow() - { - GUI.DrawTexture(colorPickerRect, colorPicker); - GUI.DrawTexture(huesBarRect, huesBar); - - float huesBarLineY = huesBarRect.y + (1.0f - ((float)currentHSV.h / 360.0f)) * huesBarRect.height; - GUI.DrawTexture(new Rect(huesBarRect.x - 2.0f, huesBarLineY, huesBarRect.width + 4.0f, 2.0f), lineTex); - - float colorPickerLineY = colorPickerRect.x + (float)currentHSV.v * colorPickerRect.width; - GUI.DrawTexture(new Rect(colorPickerRect.x - 1.0f, colorPickerLineY, colorPickerRect.width + 2.0f, 1.0f), lineTex); - - float colorPickerLineX = colorPickerRect.y + (float)currentHSV.s * colorPickerRect.height; - GUI.DrawTexture(new Rect(colorPickerLineX, colorPickerRect.y - 1.0f, 1.0f, colorPickerRect.height + 2.0f), lineTex); - } - - void InternalOnColorChanged(Color color) - { - color.a = originalAlpha; - - if (onColorChanged != null) - { - onColorChanged(color); - } - - if (onColor32Changed != null) - { - onColor32Changed(color); - } - } - - void Update() - { - Vector2 mouse = Input.mousePosition; - mouse.y = Screen.height - mouse.y; - - if (Input.GetMouseButton(0) && !rect.Contains(mouse)) - { - visible = false; - return; - } - - mouse -= rect.position; - - if (Input.GetMouseButton(0)) - { - if (huesBarRect.Contains(mouse)) - { - currentHSV.h = (1.0f - Mathf.Clamp01((mouse.y - huesBarRect.y) / huesBarRect.height)) * 360.0f; - RedrawPicker(); - - InternalOnColorChanged(ColorUtil.HSV.HSV2RGB(currentHSV)); - } - - if (colorPickerRect.Contains(mouse)) - { - currentHSV.s = Mathf.Clamp01((mouse.x - colorPickerRect.x) / colorPickerRect.width); - currentHSV.v = Mathf.Clamp01((mouse.y - colorPickerRect.y) / colorPickerRect.height); - - InternalOnColorChanged(ColorUtil.HSV.HSV2RGB(currentHSV)); - } - } - } - - public static void DrawColorPicker(Texture2D texture, double hue) - { - for (int x = 0; x < texture.width; x++) - { - for (int y = 0; y < texture.height; y++) - { - texture.SetPixel(x, y, GetColorAtXY(hue, (float)x / (float)texture.width, 1.0f - (float)y / (float)texture.height)); - } - } - - texture.Apply(); - } - - public static Texture2D DrawHuesBar(int width, int height) - { - Texture2D texture = new Texture2D(width, height); - - for (int y = 0; y < height; y++) - { - var color = GetColorAtT(((float)y / (float)height) * 360.0f); - - for (int x = 0; x < width; x++) - { - texture.SetPixel(x, y, color); - } - } - - texture.Apply(); - return texture; - } - - public static Texture2D DrawLineTex() - { - Texture2D tex = new Texture2D(1, 1); - tex.SetPixel(0, 0, lineColor); - tex.Apply(); - return tex; - } - - public static Color GetColorAtT(double hue) - { - return ColorUtil.HSV.HSV2RGB(new ColorUtil.HSV { h = hue, s = 1.0f, v = 1.0f }); - } - - public static Color GetColorAtXY(double hue, float xT, float yT) - { - return ColorUtil.HSV.HSV2RGB(new ColorUtil.HSV { h = hue, s = xT, v = yT }); - } - - } - -} diff --git a/Debugger/GUICommon/GUIArea.cs b/Debugger/GUICommon/GUIArea.cs deleted file mode 100644 index ae5e863..0000000 --- a/Debugger/GUICommon/GUIArea.cs +++ /dev/null @@ -1,50 +0,0 @@ -using UnityEngine; - -namespace ModTools -{ - public class GUIArea - { - public Vector2 absolutePosition = Vector2.zero; - public Vector2 absoluteSize = Vector2.zero; - public Vector2 margin = new Vector2(8.0f, 8.0f); - public Vector2 relativePosition = Vector2.zero; - public Vector2 relativeSize = Vector2.zero; - public GUIWindow window; - - public GUIArea(GUIWindow _window) - { - window = _window; - } - - public Vector2 Position - { - get - { - return absolutePosition + - new Vector2(relativePosition.x*window.rect.width, relativePosition.y*window.rect.height) + - margin; - } - } - - public Vector2 Size - { - get - { - return absoluteSize + new Vector2(relativeSize.x*window.rect.width, relativeSize.y*window.rect.height) - - margin * 2.0f; - } - } - - public void Begin() - { - var position = Position; - var size = Size; - GUILayout.BeginArea(new Rect(position.x, position.y, size.x, size.y)); - } - - public void End() - { - GUILayout.EndArea(); - } - } -} \ No newline at end of file diff --git a/Debugger/GUICommon/GUIComboBox.cs b/Debugger/GUICommon/GUIComboBox.cs deleted file mode 100644 index 90bb0b5..0000000 --- a/Debugger/GUICommon/GUIComboBox.cs +++ /dev/null @@ -1,166 +0,0 @@ -using UnityEngine; - -// Taken from the MechJeb2 (https://github.com/MuMech/MechJeb2) source, see MECHJEB-LICENSE for license info - -namespace ModTools -{ - public class GUIComboBox - { - // Easy to use combobox class - // ***** For users ***** - // Call the Box method with the latest selected item, list of text entries - // and an object identifying who is making the request. - // The result is the newly selected item. - // There is currently no way of knowing when a choice has been made - - // Position of the popuprect - private static Rect rect; - private static bool hasScrollbars = false; - // Identifier of the caller of the popup, null if nobody is waiting for a value - private static string popupOwner; - private static string[] entries; - private static bool popupActive; - // Result to be returned to the owner - private static int selectedItem; - // Unity identifier of the window, just needs to be unique - private static readonly int id = GUIUtility.GetControlID(FocusType.Passive); - // ComboBox GUI Style - private static readonly GUIStyle style; - private static GUIStyle _yellowOnHover; - - static GUIComboBox() - { - var background = new Texture2D(16, 16, TextureFormat.RGBA32, false); - background.wrapMode = TextureWrapMode.Clamp; - - for (var x = 0; x < background.width; x++) - for (var y = 0; y < background.height; y++) - { - if (x == 0 || x == background.width - 1 || y == 0 || y == background.height - 1) - background.SetPixel(x, y, new Color(0, 0, 0, 1)); - else - background.SetPixel(x, y, new Color(0.05f, 0.05f, 0.05f, 0.95f)); - } - - background.Apply(); - - style = new GUIStyle(GUI.skin.window); - style.normal.background = background; - style.onNormal.background = background; - style.border.top = style.border.bottom; - style.padding.top = style.padding.bottom; - } - - public static GUIStyle yellowOnHover - { - get - { - if (_yellowOnHover == null) - { - _yellowOnHover = new GUIStyle(GUI.skin.label); - _yellowOnHover.hover.textColor = Color.yellow; - var t = new Texture2D(1, 1); - t.SetPixel(0, 0, new Color(0, 0, 0, 0)); - t.Apply(); - _yellowOnHover.hover.background = t; - } - return _yellowOnHover; - } - } - - public static void DrawGUI() - { - if (popupOwner == null || rect.height == 0 || !popupActive) - return; - - // Make sure the rectangle is fully on screen - // rect.x = Math.Max(0, Math.Min(rect.x, rect.width)); - // rect.y = Math.Max(0, Math.Min(rect.y, rect.height)); - - rect = GUILayout.Window(id, rect, identifier => - { - if (hasScrollbars) - { - comboBoxScroll = GUILayout.BeginScrollView(comboBoxScroll, false, false); - } - - selectedItem = GUILayout.SelectionGrid(-1, entries, 1, yellowOnHover); - - if (hasScrollbars) - { - GUILayout.EndScrollView(); - } - - if (GUI.changed) - popupActive = false; - }, "", style); - - //Cancel the popup if we click outside - if (Event.current.type == EventType.MouseDown && !rect.Contains(Event.current.mousePosition)) - popupOwner = null; - } - - private static Vector2 comboBoxScroll = Vector2.zero; - - public static int Box(int selectedItem, string[] entries, string caller) - { - // Trivial cases (0-1 items) - if (entries.Length == 0) - return 0; - if (entries.Length == 1) - { - GUILayout.Label(entries[0]); - return 0; - } - - // A choice has been made, update the return value - if (popupOwner == caller && !popupActive) - { - popupOwner = null; - selectedItem = GUIComboBox.selectedItem; - GUI.changed = true; - } - - var guiChanged = GUI.changed; - - float width = 0; - foreach (var entry in entries) - { - var len = GUI.skin.button.CalcSize(new GUIContent(entry)).x; - if (len > width) - { - width = len; - } - } - - if (GUILayout.Button("↓ " + entries[selectedItem] + " ↓", GUILayout.Width(width + 24))) - { - // We will set the changed status when we return from the menu instead - GUI.changed = guiChanged; - // Update the global state with the new items - popupOwner = caller; - popupActive = true; - GUIComboBox.entries = entries; - // Magic value to force position update during repaint event - rect = new Rect(0, 0, 0, 0); - } - // The GetLastRect method only works during repaint event, but the Button will return false during repaint - if (Event.current.type == EventType.Repaint && popupOwner == caller && rect.height == 0) - { - rect = GUILayoutUtility.GetLastRect(); - // But even worse, I can't find a clean way to convert from relative to absolute coordinates - Vector2 mousePos = Input.mousePosition; - mousePos.y = Screen.height - mousePos.y; - var clippedMousePos = Event.current.mousePosition; - rect.x = (rect.x + mousePos.x) - clippedMousePos.x; - rect.y = (rect.y + mousePos.y) - clippedMousePos.y; - var targetHeight = rect.height*entries.Length; - hasScrollbars = targetHeight >= 400.0f; - rect.height = Mathf.Min(targetHeight, 400.0f); - comboBoxScroll = Vector2.zero; - } - - return selectedItem; - } - } -} \ No newline at end of file diff --git a/Debugger/GUICommon/GUIControls.cs b/Debugger/GUICommon/GUIControls.cs deleted file mode 100644 index d8bc32b..0000000 --- a/Debugger/GUICommon/GUIControls.cs +++ /dev/null @@ -1,1159 +0,0 @@ -using System; -using System.Linq; -using UnityEngine; - -namespace ModTools -{ - class GUIControls - { - - private static Configuration config - { - get { return ModTools.Instance.config; } - } - - public static float numberFieldSize = 100; - public static float stringFieldSize = 200; - public static float byteFieldSize = 40; - public static float charFieldSize = 25; - - public delegate void WatchButtonCallback(); - - private static bool IsHot(string hash) - { - return hash == GUI.GetNameOfFocusedControl(); - } - - private static string HotControl() - { - return GUI.GetNameOfFocusedControl(); - } - - private static string currentHotControl = null; - private static string hotControlBuffer = ""; - - public static string BufferedTextField(string hash, string value, float fieldSize) - { - GUI.SetNextControlName(hash); - bool isHot = IsHot(hash); - - string newBuffer = GUILayout.TextField(isHot ? hotControlBuffer : value.ToString(), GUILayout.Width(fieldSize)); - string res = null; - - if (isHot) - { - if (currentHotControl == null) - { - currentHotControl = hash; - hotControlBuffer = value; - } - else if (currentHotControl == hash) - { - hotControlBuffer = newBuffer; - } - } - else if (currentHotControl == hash) - { - res = hotControlBuffer; - currentHotControl = null; - hotControlBuffer = ""; - } - - return res; - } - - public static object EditorValueField(ReferenceChain refChain, string hash, Type type, object value) - { - if (type == typeof(System.Single)) - { - var f = (float)value; - FloatField(hash, "", ref f, 0.0f, true, true); - if (f != (float)value) - { - return f; - } - - return value; - } - - if (type == typeof(System.Double)) - { - var f = (double)value; - DoubleField(hash, "", ref f, 0.0f, true, true); - if (f != (double)value) - { - return f; - } - - return value; - } - - if (type == typeof(System.Byte)) - { - var f = (byte)value; - ByteField(hash, "", ref f, 0.0f, true, true); - if (f != (byte)value) - { - return f; - } - - return value; - } - - if (type == typeof(System.Int32)) - { - var f = (int)value; - IntField(hash, "", ref f, 0.0f, true, true); - if (f != (int)value) - { - return f; - } - - return value; - } - - if (type == typeof(System.UInt32)) - { - var f = (uint)value; - UIntField(hash, "", ref f, 0.0f, true, true); - if (f != (uint)value) - { - return f; - } - - return value; - } - - if (type == typeof(System.Int64)) - { - var f = (Int64)value; - Int64Field(hash, "", ref f, 0.0f, true, true); - if (f != (Int64)value) - { - return f; - } - - return value; - } - - if (type == typeof(System.UInt64)) - { - var f = (UInt64)value; - UInt64Field(hash, "", ref f, 0.0f, true, true); - if (f != (UInt64)value) - { - return f; - } - - return value; - } - - if (type == typeof(System.Int16)) - { - var f = (Int16)value; - Int16Field(hash, "", ref f, 0.0f, true, true); - if (f != (Int16)value) - { - return f; - } - - return value; - } - - if (type == typeof(System.UInt16)) - { - var f = (UInt16)value; - UInt16Field(hash, "", ref f, 0.0f, true, true); - if (f != (UInt16)value) - { - return f; - } - - return value; - } - - if (type == typeof(System.Boolean)) - { - var f = (bool)value; - BoolField("", ref f, 0.0f, true, true); - if (f != (bool)value) - { - return f; - } - - return value; - } - - if (type == typeof(System.String)) - { - var f = (string)value; - StringField(hash, "", ref f, 0.0f, true, true); - if (f != (string)value) - { - return f; - } - - return value; - } - - if (type == typeof(System.Char)) - { - var f = (char)value; - CharField(hash, "", ref f, 0.0f, true, true); - if (f != (char)value) - { - return f; - } - - return value; - } - - if (type == typeof(UnityEngine.Vector2)) - { - var f = (Vector2)value; - Vector2Field(hash, "", ref f, 0.0f, null, true, true); - if (f != (Vector2)value) - { - return f; - } - - return value; - } - - if (type == typeof(UnityEngine.Vector3)) - { - var f = (Vector3)value; - Vector3Field(hash, "", ref f, 0.0f, null, true, true); - if (f != (Vector3)value) - { - return f; - } - - return value; - } - - if (type == typeof(UnityEngine.Vector4)) - { - var f = (Vector4)value; - Vector4Field(hash, "", ref f, 0.0f, null, true, true); - if (f != (Vector4)value) - { - return f; - } - - return value; - } - - if (type == typeof(UnityEngine.Quaternion)) - { - var f = (Quaternion)value; - QuaternionField(hash, "", ref f, 0.0f, null, true, true); - if (f != (Quaternion)value) - { - return f; - } - - return value; - } - - if (type == typeof(UnityEngine.Color)) - { - var f = (Color)value; - ColorField(hash, "", ref f, 0.0f, null, true, true, color => { refChain.SetValue(color); }); - if (f != (Color)value) - { - return f; - } - - return value; - } - - if (type == typeof(UnityEngine.Color32)) - { - var f = (Color32)value; - Color32Field(hash, "", ref f, 0.0f, null, true, true, color => { refChain.SetValue(color); }); - var v = (Color32)value; - if (f.r != v.r || f.g != v.g || f.b != v.b || f.a != v.a) - { - return f; - } - - return value; - } - - if (type.IsEnum) - { - var f = value; - EnumField(hash, "", ref f, 0.0f, true, true); - if (f != value) - { - return f; - } - - return value; - } - - return value; - } - - public static void FloatField(string hash, string name, ref float value, float ident = 0.0f, bool noSpace = false, bool noTypeLabel = false) - { - GUILayout.BeginHorizontal(); - - if (ident != 0.0f) - { - GUILayout.Space(ident); - } - - if (!noTypeLabel) - { - GUI.contentColor = config.typeColor; - GUILayout.Label("float"); - } - - GUI.contentColor = config.nameColor; - GUILayout.Label(name); - - if (!noSpace) - { - GUILayout.FlexibleSpace(); - } - - GUI.contentColor = config.valueColor; - - string result = BufferedTextField(hash, value.ToString(), numberFieldSize); - if (result != null) - { - float newValue; - if (Single.TryParse(result, out newValue)) - { - value = newValue; - } - } - - GUI.contentColor = Color.white; - - GUILayout.EndHorizontal(); - } - - static public void DoubleField(string hash, string name, ref double value, float ident = 0.0f, bool noSpace = false, bool noTypeLabel = false) - { - GUILayout.BeginHorizontal(); - - if (ident != 0.0f) - { - GUILayout.Space(ident); - } - - if (!noTypeLabel) - { - GUI.contentColor = config.typeColor; - GUILayout.Label("double"); - } - - GUI.contentColor = config.nameColor; - GUILayout.Label(name); - - if (!noSpace) - { - GUILayout.FlexibleSpace(); - } - - GUI.contentColor = config.valueColor; - - string result = BufferedTextField(hash, value.ToString(), numberFieldSize); - if (result != null) - { - double newValue; - if (Double.TryParse(result, out newValue)) - { - value = newValue; - } - } - - GUI.contentColor = Color.white; - - GUILayout.EndHorizontal(); - } - - static public void ByteField(string hash, string name, ref byte value, float ident = 0.0f, bool noSpace = false, bool noTypeLabel = false) - { - GUILayout.BeginHorizontal(); - - if (ident != 0.0f) - { - GUILayout.Space(ident); - } - - if (!noTypeLabel) - { - GUI.contentColor = config.typeColor; - GUILayout.Label("int"); - } - - GUI.contentColor = config.nameColor; - - GUILayout.Label(name); - - if (!noSpace) - { - GUILayout.FlexibleSpace(); - } - - byte oldValue = value; - - GUI.contentColor = config.valueColor; - string result = BufferedTextField(hash, value.ToString(), byteFieldSize); - if (result != null) - { - byte newValue; - if (Byte.TryParse(result, out newValue)) - { - value = newValue; - } - } - - GUI.contentColor = Color.white; - - GUILayout.EndHorizontal(); - } - - static public void IntField(string hash, string name, ref int value, float ident = 0.0f, bool noSpace = false, bool noTypeLabel = false) - { - GUILayout.BeginHorizontal(); - - if (ident != 0.0f) - { - GUILayout.Space(ident); - } - - if (!noTypeLabel) - { - GUI.contentColor = config.typeColor; - GUILayout.Label("int"); - } - - GUI.contentColor = config.nameColor; - - GUILayout.Label(name); - - if (!noSpace) - { - GUILayout.FlexibleSpace(); - } - - GUI.contentColor = config.valueColor; - - string result = BufferedTextField(hash, value.ToString(), numberFieldSize); - if (result != null) - { - int newValue; - if (Int32.TryParse(result, out newValue)) - { - value = newValue; - } - } - - GUI.contentColor = Color.white; - GUILayout.EndHorizontal(); - } - - static public void UIntField(string hash, string name, ref uint value, float ident = 0.0f, bool noSpace = false, bool noTypeLabel = false) - { - GUILayout.BeginHorizontal(); - - if (ident != 0.0f) - { - GUILayout.Space(ident); - } - - if (!noTypeLabel) - { - GUI.contentColor = config.typeColor; - GUILayout.Label("uint"); - } - - GUI.contentColor = config.nameColor; - - GUILayout.Label(name); - - if (!noSpace) - { - GUILayout.FlexibleSpace(); - } - - GUI.contentColor = config.valueColor; - - string result = BufferedTextField(hash, value.ToString(), numberFieldSize); - if (result != null) - { - uint newValue; - if (UInt32.TryParse(result, out newValue)) - { - value = newValue; - } - } - - GUI.contentColor = Color.white; - - GUILayout.EndHorizontal(); - } - - static public void Int64Field(string hash, string name, ref Int64 value, float ident = 0.0f, bool noSpace = false, bool noTypeLabel = false) - { - GUILayout.BeginHorizontal(); - - if (ident != 0.0f) - { - GUILayout.Space(ident); - } - - if (!noTypeLabel) - { - GUI.contentColor = config.typeColor; - GUILayout.Label("Int64"); - } - - GUI.contentColor = config.nameColor; - - GUILayout.Label(name); - - if (!noSpace) - { - GUILayout.FlexibleSpace(); - } - - GUI.contentColor = config.valueColor; - - string result = BufferedTextField(hash, value.ToString(), numberFieldSize); - if (result != null) - { - Int64 newValue; - if (Int64.TryParse(result, out newValue)) - { - value = newValue; - } - } - - GUI.contentColor = Color.white; - - GUILayout.EndHorizontal(); - } - - static public void UInt64Field(string hash, string name, ref UInt64 value, float ident = 0.0f, bool noSpace = false, bool noTypeLabel = false) - { - GUILayout.BeginHorizontal(); - - if (ident != 0.0f) - { - GUILayout.Space(ident); - } - - if (!noTypeLabel) - { - GUI.contentColor = config.typeColor; - GUILayout.Label("UInt64"); - } - - GUI.contentColor = config.nameColor; - - GUILayout.Label(name); - - if (!noSpace) - { - GUILayout.FlexibleSpace(); - } - - GUI.contentColor = config.valueColor; - - string result = BufferedTextField(hash, value.ToString(), numberFieldSize); - if (result != null) - { - UInt64 newValue; - if (UInt64.TryParse(result, out newValue)) - { - value = newValue; - } - } - - GUI.contentColor = Color.white; - - GUILayout.EndHorizontal(); - } - - static public void Int16Field(string hash, string name, ref Int16 value, float ident = 0.0f, bool noSpace = false, bool noTypeLabel = false) - { - GUILayout.BeginHorizontal(); - - if (ident != 0.0f) - { - GUILayout.Space(ident); - } - - if (!noTypeLabel) - { - GUI.contentColor = config.typeColor; - GUILayout.Label("Int16"); - } - - GUI.contentColor = config.nameColor; - - GUILayout.Label(name); - - if (!noSpace) - { - GUILayout.FlexibleSpace(); - } - - Int16 oldValue = value; - - GUI.contentColor = config.valueColor; - - string result = BufferedTextField(hash, value.ToString(), numberFieldSize); - if (result != null) - { - Int16 newValue; - if (Int16.TryParse(result, out newValue)) - { - value = newValue; - } - } - - GUI.contentColor = Color.white; - - GUILayout.EndHorizontal(); - } - - static public void UInt16Field(string hash, string name, ref UInt16 value, float ident = 0.0f, bool noSpace = false, bool noTypeLabel = false) - { - GUILayout.BeginHorizontal(); - - if (ident != 0.0f) - { - GUILayout.Space(ident); - } - - if (!noTypeLabel) - { - GUI.contentColor = config.typeColor; - GUILayout.Label("UInt16"); - } - - GUI.contentColor = config.nameColor; - - GUILayout.Label(name); - - if (!noSpace) - { - GUILayout.FlexibleSpace(); - } - - GUI.contentColor = config.valueColor; - - string result = BufferedTextField(hash, value.ToString(), numberFieldSize); - if (result != null) - { - UInt16 newValue; - if (UInt16.TryParse(result, out newValue)) - { - value = newValue; - } - } - - GUI.contentColor = Color.white; - - GUILayout.EndHorizontal(); - } - - static public void StringField(string hash, string name, ref string value, float ident = 0.0f, bool noSpace = false, bool noTypeLabel = false) - { - GUILayout.BeginHorizontal(); - - if (ident != 0.0f) - { - GUILayout.Space(ident); - } - - if (!noTypeLabel) - { - GUI.contentColor = config.typeColor; - GUILayout.Label("string"); - } - - GUI.contentColor = config.nameColor; - GUILayout.Label(name); - if (!noSpace) - { - GUILayout.FlexibleSpace(); - } - - GUI.contentColor = config.valueColor; - - string result = BufferedTextField(hash, value.ToString(), stringFieldSize); - if (result != null) - { - value = result; - } - - GUI.contentColor = Color.white; - - GUILayout.EndHorizontal(); - } - - static public void CharField(string hash, string name, ref char value, float ident = 0.0f, bool noSpace = false, bool noTypeLabel = false) - { - GUILayout.BeginHorizontal(); - - if (ident != 0.0f) - { - GUILayout.Space(ident); - } - - if (!noTypeLabel) - { - GUI.contentColor = config.typeColor; - GUILayout.Label("string"); - } - - GUI.contentColor = config.nameColor; - GUILayout.Label(name); - if (!noSpace) - { - GUILayout.FlexibleSpace(); - } - - GUI.contentColor = config.valueColor; - - string result = BufferedTextField(hash, value.ToString(), charFieldSize); - if (result != null) - { - value = result[0]; - } - - GUI.contentColor = Color.white; - - GUILayout.EndHorizontal(); - } - - static public void BoolField(string name, ref bool value, float ident = 0.0f, bool noSpace = false, bool noTypeLabel = false) - { - GUILayout.BeginHorizontal(); - - if (ident != 0.0f) - { - GUILayout.Space(ident); - } - - if (!noTypeLabel) - { - GUI.contentColor = config.typeColor; - GUILayout.Label("bool"); - } - - GUI.contentColor = config.nameColor; - GUILayout.Label(name); - - GUI.contentColor = config.valueColor; - - if (!noSpace) - { - GUILayout.FlexibleSpace(); - } - - value = GUILayout.Toggle(value, ""); - - GUI.contentColor = Color.white; - - GUILayout.EndHorizontal(); - } - - static public void EnumField(string hash, string name, ref object value, float ident = 0.0f, bool noSpace = false, bool noTypeLabel = false) - { - GUILayout.BeginHorizontal(); - - try - { - if (ident != 0.0f) - { - GUILayout.Space(ident); - } - - var enumType = value.GetType(); - - if (!noTypeLabel) - { - GUI.contentColor = config.typeColor; - GUILayout.Label(enumType.FullName); - } - - GUI.contentColor = config.nameColor; - GUILayout.Label(name); - GUI.contentColor = config.valueColor; - - if (!noSpace) - { - GUILayout.FlexibleSpace(); - } - - var enumNames = Enum.GetNames(enumType).ToArray(); - - if (TypeUtil.IsBitmaskEnum(enumType)) - { - GUILayout.Label(value.ToString()); - } - else - { - int i = 0; - for (; i < enumNames.Length; i++) - { - if (value.ToString() == enumNames[i]) - { - break; - } - } - - int newIndex = GUIComboBox.Box(i, enumNames, hash); - value = Enum.Parse(enumType, enumNames[newIndex]); - } - - GUI.contentColor = Color.white; - } - catch (Exception) - { - GUILayout.EndHorizontal(); - throw; - } - - GUILayout.EndHorizontal(); - } - - static public void Vector2Field(string hash, string name, ref Vector2 value, float ident = 0.0f, WatchButtonCallback watch = null, bool noSpace = false, bool noTypeLabel = false) - { - GUILayout.BeginHorizontal(); - - if (ident != 0.0f) - { - GUILayout.Space(ident); - } - - if (!noTypeLabel) - { - GUI.contentColor = config.typeColor; - GUILayout.Label("Vector2"); - } - - GUI.contentColor = config.nameColor; - GUILayout.Label(name); - - GUI.contentColor = config.valueColor; - - if (!noSpace) - { - GUILayout.FlexibleSpace(); - } - - FloatField(hash + ".x", "x", ref value.x, 0.0f, true, true); - FloatField(hash + ".y", "y", ref value.y, 0.0f, true, true); - - if (watch != null) - { - if (GUILayout.Button("Watch")) - { - watch(); - } - } - - GUI.contentColor = Color.white; - - GUILayout.EndHorizontal(); - } - - static public void Vector3Field(string hash, string name, ref Vector3 value, float ident = 0.0f, WatchButtonCallback watch = null, bool noSpace = false, bool noTypeLabel = false) - { - GUILayout.BeginHorizontal(); - - if (ident != 0.0f) - { - GUILayout.Space(ident); - } - - if (!noTypeLabel) - { - GUI.contentColor = config.typeColor; - GUILayout.Label("Vector3"); - } - - GUI.contentColor = config.nameColor; - GUILayout.Label(name); - - GUI.contentColor = config.valueColor; - - if (!noSpace) - { - GUILayout.FlexibleSpace(); - } - - FloatField(hash + ".x", "x", ref value.x, 0.0f, true, true); - FloatField(hash + ".y", "y", ref value.y, 0.0f, true, true); - FloatField(hash + ".z", "z", ref value.z, 0.0f, true, true); - - if (watch != null) - { - if (GUILayout.Button("Watch")) - { - watch(); - } - } - - GUI.contentColor = Color.white; - - GUILayout.EndHorizontal(); - } - - static public void Vector4Field(string hash, string name, ref Vector4 value, float ident = 0.0f, WatchButtonCallback watch = null, bool noSpace = false, bool noTypeLabel = false) - { - GUILayout.BeginHorizontal(); - - if (ident != 0.0f) - { - GUILayout.Space(ident); - } - - if (!noTypeLabel) - { - GUI.contentColor = config.typeColor; - GUILayout.Label("Vector4"); - } - - GUI.contentColor = config.nameColor; - GUILayout.Label(name); - - GUI.contentColor = config.valueColor; - - if (!noSpace) - { - GUILayout.FlexibleSpace(); - } - - FloatField(hash + ".x", "x", ref value.x, 0.0f, true, true); - FloatField(hash + ".y", "y", ref value.y, 0.0f, true, true); - FloatField(hash + ".z", "z", ref value.z, 0.0f, true, true); - FloatField(hash + ".w", "w", ref value.w, 0.0f, true, true); - - if (watch != null) - { - if (GUILayout.Button("Watch")) - { - watch(); - } - } - - GUI.contentColor = Color.white; - - GUILayout.EndHorizontal(); - } - - static public void QuaternionField(string hash, string name, ref Quaternion value, float ident = 0.0f, WatchButtonCallback watch = null, bool noSpace = false, bool noTypeLabel = false) - { - GUILayout.BeginHorizontal(); - - if (ident != 0.0f) - { - GUILayout.Space(ident); - } - - if (!noTypeLabel) - { - GUI.contentColor = config.typeColor; - GUILayout.Label("Quaternion (euler)"); - } - - GUI.contentColor = config.nameColor; - GUILayout.Label(name); - - GUI.contentColor = config.valueColor; - - if (!noSpace) - { - GUILayout.FlexibleSpace(); - } - - var euler = value.eulerAngles; - - FloatField(hash + ".x", "x", ref euler.x, 0.0f, true, true); - FloatField(hash + ".y", "y", ref euler.y, 0.0f, true, true); - FloatField(hash + ".z", "z", ref euler.z, 0.0f, true, true); - - if (euler != value.eulerAngles) - { - value = Quaternion.Euler(euler); - } - - if (watch != null) - { - if (GUILayout.Button("Watch")) - { - watch(); - } - } - - GUI.contentColor = Color.white; - - GUILayout.EndHorizontal(); - } - - static public void ColorField(string hash, string name, ref Color value, float ident = 0.0f, WatchButtonCallback watch = null, bool noSpace = false, bool noTypeLabel = false, ColorPicker.OnColorChanged onColorChanged = null) - { - GUILayout.BeginHorizontal(); - - if (ident != 0.0f) - { - GUILayout.Space(ident); - } - - if (!noTypeLabel) - { - GUI.contentColor = config.typeColor; - GUILayout.Label("Color"); - } - - GUI.contentColor = config.nameColor; - GUILayout.Label(name); - - GUI.contentColor = config.valueColor; - - if (!noSpace) - { - GUILayout.FlexibleSpace(); - } - - var r = (byte)(Mathf.Clamp(value.r * 255.0f, Byte.MinValue, Byte.MaxValue)); - var g = (byte)(Mathf.Clamp(value.g * 255.0f, Byte.MinValue, Byte.MaxValue)); - var b = (byte)(Mathf.Clamp(value.b * 255.0f, Byte.MinValue, Byte.MaxValue)); - var a = (byte)(Mathf.Clamp(value.a * 255.0f, Byte.MinValue, Byte.MaxValue)); - - ByteField(hash + ".r", "r", ref r, 0.0f, true, true); - ByteField(hash + ".g", "g", ref g, 0.0f, true, true); - ByteField(hash + ".b", "b", ref b, 0.0f, true, true); - ByteField(hash + ".a", "a", ref a, 0.0f, true, true); - - value.r = Mathf.Clamp01((float)r / 255.0f); - value.g = Mathf.Clamp01((float)g / 255.0f); - value.b = Mathf.Clamp01((float)b / 255.0f); - value.a = Mathf.Clamp01((float)a / 255.0f); - - if (onColorChanged != null) - { - if (value.r != r || value.g != g || value.b != b || value.a != a) - { - onColorChanged(value); - } - - if (GUILayout.Button("", GUILayout.Width(72))) - { - var picker = ModTools.Instance.colorPicker; - if (picker != null) - { - picker.SetColor(value, onColorChanged); - - Vector2 mouse = Input.mousePosition; - mouse.y = Screen.height - mouse.y; - - picker.rect.position = mouse; - picker.visible = true; - } - - } - - var lastRect = GUILayoutUtility.GetLastRect(); - lastRect.x += 4.0f; - lastRect.y += 4.0f; - lastRect.width -= 8.0f; - lastRect.height -= 8.0f; - GUI.DrawTexture(lastRect, ColorPicker.GetColorTexture(hash, value), ScaleMode.StretchToFill); - } - - if (watch != null) - { - if (GUILayout.Button("Watch")) - { - watch(); - } - } - - GUI.contentColor = Color.white; - - GUILayout.EndHorizontal(); - } - - public static void Color32Field(string hash, string name, ref Color32 value, float ident = 0.0f, WatchButtonCallback watch = null, bool noSpace = false, bool noTypeLabel = false, ColorPicker.OnColor32Changed onColor32Changed = null) - { - GUILayout.BeginHorizontal(); - - if (ident != 0.0f) - { - GUILayout.Space(ident); - } - - if (!noTypeLabel) - { - GUI.contentColor = config.typeColor; - GUILayout.Label("Color"); - } - - GUI.contentColor = config.nameColor; - GUILayout.Label(name); - - GUI.contentColor = config.valueColor; - - if (!noSpace) - { - GUILayout.FlexibleSpace(); - } - - ByteField(hash + ".r", "r", ref value.r, 0.0f, true, true); - ByteField(hash + ".g", "g", ref value.g, 0.0f, true, true); - ByteField(hash + ".b", "b", ref value.b, 0.0f, true, true); - ByteField(hash + ".a", "a", ref value.a, 0.0f, true, true); - - if (onColor32Changed != null) - { - if (GUILayout.Button("", GUILayout.Width(72))) - { - var picker = ModTools.Instance.colorPicker; - if (picker != null) - { - picker.SetColor(value, onColor32Changed); - - Vector2 mouse = Input.mousePosition; - mouse.y = Screen.height - mouse.y; - - picker.rect.position = mouse; - picker.visible = true; - } - - } - - var lastRect = GUILayoutUtility.GetLastRect(); - lastRect.x += 4.0f; - lastRect.y += 4.0f; - lastRect.width -= 8.0f; - lastRect.height -= 8.0f; - GUI.DrawTexture(lastRect, ColorPicker.GetColorTexture(hash, value), ScaleMode.StretchToFill); - } - - if (watch != null) - { - if (GUILayout.Button("Watch")) - { - watch(); - } - } - - GUI.contentColor = Color.white; - - GUILayout.EndHorizontal(); - } - } - -} \ No newline at end of file diff --git a/Debugger/GUICommon/GUIWindow.cs b/Debugger/GUICommon/GUIWindow.cs deleted file mode 100644 index 3dee6a0..0000000 --- a/Debugger/GUICommon/GUIWindow.cs +++ /dev/null @@ -1,463 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using ColossalFramework.UI; -using UnityEngine; - -namespace ModTools -{ - public class GUIWindow : MonoBehaviour - { - - private static Configuration config - { - get { return ModTools.Instance.config; } - } - - public delegate void OnDraw(); - - public delegate void OnException(Exception ex); - - public delegate void OnUnityGUI(); - - public delegate void OnOpen(); - - public delegate void OnClose(); - - public delegate void OnResize(Vector2 size); - - public delegate void OnMove(Vector2 position); - - public delegate void OnUnityDestroy(); - - public OnDraw onDraw = null; - public OnException onException = null; - public OnUnityGUI onUnityGUI = null; - public OnOpen onOpen = null; - public OnClose onClose = null; - public OnResize onResize = null; - public OnMove onMove = null; - public OnUnityDestroy onUnityDestroy = null; - - public Rect rect = new Rect(0, 0, 64, 64); - - public static GUISkin skin = null; - public static Texture2D bgTexture = null; - public static Texture2D resizeNormalTexture = null; - public static Texture2D resizeHoverTexture = null; - - public static Texture2D closeNormalTexture = null; - public static Texture2D closeHoverTexture = null; - - public static Texture2D moveNormalTexture = null; - public static Texture2D moveHoverTexture = null; - - public static GUIWindow resizingWindow = null; - public static Vector2 resizeDragHandle = Vector2.zero; - - public static GUIWindow movingWindow = null; - public static Vector2 moveDragHandle = Vector2.zero; - - public static float uiScale = 1.0f; - private bool _visible = false; - - public bool visible - { - get { return _visible; } - - set - { - _visible = value; - GUI.BringWindowToFront(id); - UpdateClickCatcher(); - - if (_visible && onOpen != null) - { - onOpen(); - } - } - } - - public bool resizable = true; - public bool hasCloseButton = true; - public bool hasTitlebar = true; - - public string title = "Window"; - - private int id = 0; - - private Vector2 minSize = Vector2.zero; - - private static List windows = new List(); - - private UIPanel clickCatcher; - - public GUIWindow(string _title, Rect _rect, GUISkin _skin) - { - id = UnityEngine.Random.Range(1024, int.MaxValue); - title = _title; - rect = _rect; - skin = _skin; - minSize = new Vector2(64.0f, 64.0f); - windows.Add(this); - - var uiView = FindObjectOfType(); - if (uiView != null) - { - clickCatcher = uiView.AddUIComponent(typeof(UIPanel)) as UIPanel; - if (clickCatcher != null) - { - clickCatcher.name = "_ModToolsInternal"; - } - } - UpdateClickCatcher(); - } - - void UpdateClickCatcher() - { - if (clickCatcher == null) - { - return; - } - - //adjust rect from unity pixels to C:S pixels via GetUIView().ratio - var ratio = UIView.GetAView().ratio; - - clickCatcher.absolutePosition = new Vector3(rect.position.x * ratio, rect.position.y * ratio); - clickCatcher.size = new Vector2(rect.width * ratio, rect.height * ratio); - clickCatcher.isVisible = visible; - clickCatcher.zOrder = int.MaxValue; - } - - void OnDestroy() - { - if (onUnityDestroy != null) - { - onUnityDestroy(); - } - - if (clickCatcher != null) - { - Destroy(clickCatcher.gameObject); - } - - windows.Remove(this); - } - - public static void UpdateFont() - { - skin.font = Font.CreateDynamicFontFromOSFont(config.fontName, config.fontSize); - ModTools.Instance.sceneExplorer.RecalculateAreas(); - } - - public static void UpdateMouseScrolling() - { - var mouse = Input.mousePosition; - mouse.y = Screen.height - mouse.y; - var mouseInsideGuiWindow = windows.Any(window => window.visible && window.rect.Contains(mouse)); - Util.SetMouseScrolling(!mouseInsideGuiWindow); - } - - void OnGUI() - { - if (skin == null) - { - bgTexture = new Texture2D(1, 1); - bgTexture.SetPixel(0, 0, config.backgroundColor); - bgTexture.Apply(); - - resizeNormalTexture = new Texture2D(1, 1); - resizeNormalTexture.SetPixel(0, 0, Color.white); - resizeNormalTexture.Apply(); - - resizeHoverTexture = new Texture2D(1, 1); - resizeHoverTexture.SetPixel(0, 0, Color.blue); - resizeHoverTexture.Apply(); - - closeNormalTexture = new Texture2D(1, 1); - closeNormalTexture.SetPixel(0, 0, Color.red); - closeNormalTexture.Apply(); - - closeHoverTexture = new Texture2D(1, 1); - closeHoverTexture.SetPixel(0, 0, Color.white); - closeHoverTexture.Apply(); - - moveNormalTexture = new Texture2D(1, 1); - moveNormalTexture.SetPixel(0, 0, config.titlebarColor); - moveNormalTexture.Apply(); - - moveHoverTexture = new Texture2D(1, 1); - moveHoverTexture.SetPixel(0, 0, config.titlebarColor * 1.2f); - moveHoverTexture.Apply(); - - skin = ScriptableObject.CreateInstance(); - skin.box = new GUIStyle(GUI.skin.box); - skin.button = new GUIStyle(GUI.skin.button); - skin.horizontalScrollbar = new GUIStyle(GUI.skin.horizontalScrollbar); - skin.horizontalScrollbarLeftButton = new GUIStyle(GUI.skin.horizontalScrollbarLeftButton); - skin.horizontalScrollbarRightButton = new GUIStyle(GUI.skin.horizontalScrollbarRightButton); - skin.horizontalScrollbarThumb = new GUIStyle(GUI.skin.horizontalScrollbarThumb); - skin.horizontalSlider = new GUIStyle(GUI.skin.horizontalSlider); - skin.horizontalSliderThumb = new GUIStyle(GUI.skin.horizontalSliderThumb); - skin.label = new GUIStyle(GUI.skin.label); - skin.scrollView = new GUIStyle(GUI.skin.scrollView); - skin.textArea = new GUIStyle(GUI.skin.textArea); - skin.textField = new GUIStyle(GUI.skin.textField); - skin.toggle = new GUIStyle(GUI.skin.toggle); - skin.verticalScrollbar = new GUIStyle(GUI.skin.verticalScrollbar); - skin.verticalScrollbarDownButton = new GUIStyle(GUI.skin.verticalScrollbarDownButton); - skin.verticalScrollbarThumb = new GUIStyle(GUI.skin.verticalScrollbarThumb); - skin.verticalScrollbarUpButton = new GUIStyle(GUI.skin.verticalScrollbarUpButton); - skin.verticalSlider = new GUIStyle(GUI.skin.verticalSlider); - skin.verticalSliderThumb = new GUIStyle(GUI.skin.verticalSliderThumb); - skin.window = new GUIStyle(GUI.skin.window); - skin.window.normal.background = bgTexture; - skin.window.onNormal.background = bgTexture; - - skin.settings.cursorColor = GUI.skin.settings.cursorColor; - skin.settings.cursorFlashSpeed = GUI.skin.settings.cursorFlashSpeed; - skin.settings.doubleClickSelectsWord = GUI.skin.settings.doubleClickSelectsWord; - skin.settings.selectionColor = GUI.skin.settings.selectionColor; - skin.settings.tripleClickSelectsLine = GUI.skin.settings.tripleClickSelectsLine; - - UpdateFont(); - } - - if (visible) - { - var oldSkin = GUI.skin; - if (skin != null) - { - GUI.skin = skin; - } - - var matrix = GUI.matrix; - GUI.matrix = Matrix4x4.Scale(new Vector3(uiScale, uiScale, uiScale)); - - rect = GUI.Window(id, rect, i => - { - if (onDraw != null) - { - GUILayout.Space(8.0f); - - try - { - onDraw(); - } - catch (Exception ex) - { - if (onException != null) - { - onException(ex); - } - else - { - throw; - } - } - - GUILayout.Space(16.0f); - - var mouse = Input.mousePosition; - mouse.y = Screen.height - mouse.y; - - DrawBorder(); - - if (hasTitlebar) - { - DrawTitlebar(mouse); - } - - if (hasCloseButton) - { - DrawCloseButton(mouse); - } - - if (resizable) - { - DrawResizeHandle(mouse); - } - } - }, ""); - - if (onUnityGUI != null) - { - onUnityGUI(); - } - - GUI.matrix = matrix; - - GUI.skin = oldSkin; - } - } - - private void DrawBorder() - { - var leftRect = new Rect(0.0f, 0.0f, 1.0f, rect.height); - var rightRect = new Rect(rect.width - 1.0f, 0.0f, 1.0f, rect.height); - var bottomRect = new Rect(0.0f, rect.height - 1.0f, rect.width, 1.0f); - GUI.DrawTexture(leftRect, moveNormalTexture); - GUI.DrawTexture(rightRect, moveNormalTexture); - GUI.DrawTexture(bottomRect, moveNormalTexture); - } - - private void DrawTitlebar(Vector3 mouse) - { - var moveRect = new Rect(rect.x * uiScale, rect.y * uiScale, rect.width * uiScale, 20.0f); - var moveTex = moveNormalTexture; - - if (movingWindow != null) - { - if (movingWindow == this) - { - moveTex = moveHoverTexture; - - if (Input.GetMouseButton(0)) - { - var pos = new Vector2(mouse.x, mouse.y) + moveDragHandle; - rect.x = pos.x; - rect.y = pos.y; - if (rect.x < 0.0f) - { - rect.x = 0.0f; - } - if (rect.x + rect.width > Screen.width) - { - rect.x = Screen.width - rect.width; - } - - if (rect.y < 0.0f) - { - rect.y = 0.0f; - } - if (rect.y + rect.height > Screen.height) - { - rect.y = Screen.height - rect.height; - } - } - else - { - movingWindow = null; - ModTools.Instance.SaveConfig(); - - UpdateClickCatcher(); - - if (onMove != null) - { - onMove(rect.position); - } - } - } - } - else if (moveRect.Contains(mouse)) - { - moveTex = moveHoverTexture; - if (Input.GetMouseButton(0) && resizingWindow == null) - { - movingWindow = this; - moveDragHandle = new Vector2(rect.x, rect.y) - new Vector2(mouse.x, mouse.y); - } - } - - GUI.DrawTexture(new Rect(0.0f, 0.0f, rect.width * uiScale, 20.0f), moveTex, ScaleMode.StretchToFill); - GUI.contentColor = config.titlebarTextColor; - GUI.Label(new Rect(0.0f, 0.0f, rect.width * uiScale, 20.0f), title); - GUI.contentColor = Color.white; - } - - private void DrawCloseButton(Vector3 mouse) - { - var closeRect = new Rect(rect.x * uiScale + rect.width * uiScale - 20.0f, rect.y * uiScale, 16.0f, 8.0f); - var closeTex = closeNormalTexture; - - if (closeRect.Contains(mouse)) - { - closeTex = closeHoverTexture; - - if (Input.GetMouseButton(0)) - { - resizingWindow = null; - movingWindow = null; - visible = false; - ModTools.Instance.SaveConfig(); - - UpdateClickCatcher(); - - if (onClose != null) - { - onClose(); - } - } - } - - GUI.DrawTexture(new Rect(rect.width - 20.0f, 0.0f, 16.0f, 8.0f), closeTex, ScaleMode.StretchToFill); - } - - private void DrawResizeHandle(Vector3 mouse) - { - var resizeRect = new Rect(rect.x * uiScale + rect.width * uiScale - 16.0f, rect.y * uiScale + rect.height * uiScale - 8.0f, 16.0f, 8.0f); - var resizeTex = resizeNormalTexture; - - if (resizingWindow != null) - { - if (resizingWindow == this) - { - resizeTex = resizeHoverTexture; - - if (Input.GetMouseButton(0)) - { - var size = new Vector2(mouse.x, mouse.y) + resizeDragHandle - new Vector2(rect.x, rect.y); - - if (size.x < minSize.x) - { - size.x = minSize.x; - } - - if (size.y < minSize.y) - { - size.y = minSize.y; - } - - rect.width = size.x; - rect.height = size.y; - - if (rect.x + rect.width >= Screen.width) - { - rect.width = Screen.width - rect.x; - } - - if (rect.y + rect.height >= Screen.height) - { - rect.height = Screen.height - rect.y; - } - } - else - { - resizingWindow = null; - ModTools.Instance.SaveConfig(); - - UpdateClickCatcher(); - - if (onResize != null) - { - onResize(rect.size); - } - } - } - } - else if (resizeRect.Contains(mouse)) - { - resizeTex = resizeHoverTexture; - if (Input.GetMouseButton(0) && movingWindow == null) - { - resizingWindow = this; - resizeDragHandle = new Vector2(rect.x + rect.width, rect.y + rect.height) - new Vector2(mouse.x, mouse.y); - } - } - - GUI.DrawTexture(new Rect(rect.width - 16.0f, rect.height - 8.0f, 16.0f, 8.0f), resizeTex, ScaleMode.StretchToFill); - } - - } - -} diff --git a/Debugger/GUIExplorer/GUIButtons.cs b/Debugger/GUIExplorer/GUIButtons.cs deleted file mode 100644 index e4b7c51..0000000 --- a/Debugger/GUIExplorer/GUIButtons.cs +++ /dev/null @@ -1,198 +0,0 @@ -using System; -using ModTools.Utils; -using UnityEngine; - -namespace ModTools.Explorer -{ - public class GUIButtons - { - private static System.Object _buffer = null; - - public static void SetupButtons(Type type, object value, ReferenceChain refChain) - { - if (value is VehicleInfo) - { - var info = (VehicleInfo)value; - if (info.m_mesh != null) - { - if (GUILayout.Button("Preview")) - { - MeshViewer.CreateMeshViewer(info.name, info.m_mesh, info.m_material); - } - } - if (info.m_lodMesh != null) - { - if (GUILayout.Button("Preview LOD")) - { - MeshViewer.CreateMeshViewer(info.name + "_LOD", info.m_lodMesh, info.m_lodMaterial); - } - } - } - else if (value is NetInfo) - { - SetupPlopButton(value); - } - else if (value is BuildingInfo) - { - var info = (BuildingInfo)value; - SetupPlopButton(value); - if (info.m_mesh != null) - { - if (GUILayout.Button("Preview")) - { - MeshViewer.CreateMeshViewer(info.name, info.m_mesh, info.m_material); - } - } - if (info.m_lodMesh != null) - { - if (GUILayout.Button("Preview LOD")) - { - MeshViewer.CreateMeshViewer(info.name + "_LOD", info.m_lodMesh, info.m_lodMaterial); - } - } - } - else if (value is PropInfo) - { - var info = (PropInfo)value; - SetupPlopButton(value); - if (info.m_mesh != null) - { - if (GUILayout.Button("Preview")) - { - MeshViewer.CreateMeshViewer(info.name, info.m_mesh, info.m_material); - } - } - if (info.m_lodMesh != null) - { - if (GUILayout.Button("Preview LOD")) - { - MeshViewer.CreateMeshViewer(info.name + "_LOD", info.m_lodMesh, info.m_lodMaterial); - } - } - } - else if (value is TreeInfo) - { - var info = (TreeInfo)value; - SetupPlopButton(value); - if (info.m_mesh != null) - { - if (GUILayout.Button("Preview")) - { - MeshViewer.CreateMeshViewer(info.name, info.m_mesh, info.m_material); - } - } - } - else if (value is CitizenInfo) - { - var info = (CitizenInfo)value; - if (info.m_skinRenderer?.sharedMesh != null) - { - if (GUILayout.Button("Preview")) - { - MeshViewer.CreateMeshViewer(info.name, info.m_skinRenderer?.sharedMesh, info.m_skinRenderer?.material); - } - } - if (info.m_lodMesh != null) - { - if (GUILayout.Button("Preview LOD")) - { - MeshViewer.CreateMeshViewer(info.name, info.m_lodMesh, info.m_lodMaterial); - } - } - } - else if (value is MilestoneInfo) - { - var info = (MilestoneInfo)value; - if (GUILayout.Button("Unlock")) - { - var wrapper = new MilestonesWrapper(UnlockManager.instance); - wrapper.UnlockMilestone(info.name); - } - } - else if (value is NetInfo.Segment) - { - var info = (NetInfo.Segment)value; - if (info.m_mesh != null) - { - if (GUILayout.Button("Preview")) - { - MeshViewer.CreateMeshViewer(null, info.m_mesh, info.m_material); - } - if (GUILayout.Button("Preview LOD")) - { - MeshViewer.CreateMeshViewer(null, info.m_lodMesh, info.m_lodMaterial); - } - } - } - else if (value is NetInfo.Node) - { - var info = (NetInfo.Node)value; - if (info.m_mesh != null) - { - if (GUILayout.Button("Preview")) - { - MeshViewer.CreateMeshViewer(null, info.m_mesh, info.m_material); - } - if (GUILayout.Button("Preview LOD")) - { - MeshViewer.CreateMeshViewer(null, info.m_lodMesh, info.m_lodMaterial); - } - } - } - else if (TypeUtil.IsTextureType(type) && value != null) - { - var texture = (Texture)value; - if (GUILayout.Button("Preview")) - { - TextureViewer.CreateTextureViewer(refChain, texture); - } - if (GUILayout.Button("Dump .png")) - { - TextureUtil.DumpTextureToPNG(texture); - } - } - else if (TypeUtil.IsMeshType(type) && value != null) - { - if (GUILayout.Button("Preview")) - { - MeshViewer.CreateMeshViewer(null, (Mesh)value, null); - } - if (((Mesh)value).isReadable) - { - if (GUILayout.Button("Dump .obj")) - { - var outPath = refChain.ToString().Replace(' ', '_'); - DumpUtil.DumpMeshAndTextures(outPath, value as Mesh); - } - } - } - if (GUILayout.Button("Copy")) - { - _buffer = value; - } - } - - public static bool SetupPasteButon(Type type, out object paste) - { - paste = null; - if (_buffer != null && type.IsInstanceOfType(_buffer)) - { - if (GUILayout.Button("Paste")) - { - paste = _buffer; - return true; - } - } - return GUILayout.Button("Unset"); - } - - private static void SetupPlopButton(object @object) - { - var info = @object as PrefabInfo; - if (info != null && GUILayout.Button("Plop")) - { - Plopper.StartPlopping(info); - } - } - } -} \ No newline at end of file diff --git a/Debugger/GUIExplorer/GUIList.cs b/Debugger/GUIExplorer/GUIList.cs deleted file mode 100644 index e80927e..0000000 --- a/Debugger/GUIExplorer/GUIList.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using UnityEngine; - -namespace ModTools.Explorer -{ - public static class GUIList - { - public static void OnSceneTreeReflectIList(SceneExplorerState state, - ReferenceChain refChain, System.Object myProperty) - { - if (!SceneExplorerCommon.SceneTreeCheckDepth(refChain)) return; - - var list = myProperty as IList; - if (list == null) - { - return; - } - - var oldRefChain = refChain; - var collectionSize = list.Count; - if (collectionSize == 0) - { - GUILayout.BeginHorizontal(); - GUI.contentColor = Color.yellow; - GUILayout.Label("List is empty!"); - GUI.contentColor = Color.white; - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - return; - } - - int arrayStart; - int arrayEnd; - GUICollectionNavigation.SetUpCollectionNavigation("List", state, refChain, oldRefChain, collectionSize, out arrayStart, out arrayEnd); - for (int i = arrayStart; i <= arrayEnd; i++) - { - refChain = oldRefChain.Add(i); - if (list[i] == null) - { - continue; - } - - GUILayout.BeginHorizontal(); - GUILayout.Space(ModTools.Instance.config.sceneExplorerTreeIdentSpacing * refChain.Ident); - - - GUI.contentColor = Color.white; - var type = list[i] == null ? null : list[i].GetType(); - GUIExpander.ExpanderControls(state, refChain, type); - - GUI.contentColor = ModTools.Instance.config.typeColor; - - GUILayout.Label($"{type} "); - - GUI.contentColor = ModTools.Instance.config.nameColor; - - GUILayout.Label($"{oldRefChain.LastItemName}.[{i}]"); - - GUI.contentColor = Color.white; - - GUILayout.Label(" = "); - - GUI.contentColor = ModTools.Instance.config.valueColor; - - if (list[i] == null || !TypeUtil.IsSpecialType(list[i].GetType())) - { - GUILayout.Label(list[i] == null ? "null" : list[i].ToString()); - } - else - { - try - { - var newValue = GUIControls.EditorValueField(refChain, refChain.ToString(), list[i].GetType(), list[i]); - if (newValue != list[i]) - { - list[i] = newValue; - } - } - catch (Exception) - { - GUILayout.Label(list[i] == null ? "null" : list[i].ToString()); - } - } - - GUI.contentColor = Color.white; - - GUILayout.FlexibleSpace(); - GUIButtons.SetupButtons(type, list[i], refChain); - GUILayout.EndHorizontal(); - - if (!TypeUtil.IsSpecialType(type) && state.expandedObjects.ContainsKey(refChain)) - { - if (list[i] is GameObject) - { - var go = list[i] as GameObject; - foreach (var component in go.GetComponents()) - { - GUIComponent.OnSceneTreeComponent(state, refChain, component); - } - } - else if (list[i] is Transform) - { - GUITransform.OnSceneTreeReflectUnityEngineTransform(refChain, (Transform)list[i]); - } - else - { - GUIReflect.OnSceneTreeReflect(state, refChain, list[i]); - } - } - } - } - } -} \ No newline at end of file diff --git a/Debugger/GUIExplorer/SceneExplorerCommon.cs b/Debugger/GUIExplorer/SceneExplorerCommon.cs deleted file mode 100644 index 2b7f63e..0000000 --- a/Debugger/GUIExplorer/SceneExplorerCommon.cs +++ /dev/null @@ -1,26 +0,0 @@ -using UnityEngine; - -namespace ModTools.Explorer -{ - public class SceneExplorerCommon - { - internal static void OnSceneTreeMessage(ReferenceChain refChain, string message) - { - GUILayout.BeginHorizontal(); - GUILayout.Space(ModTools.Instance.config.sceneExplorerTreeIdentSpacing * refChain.Ident); - GUILayout.Label(message); - GUILayout.EndHorizontal(); - } - - internal static bool SceneTreeCheckDepth(ReferenceChain refChain) - { - if (refChain.CheckDepth()) - { - OnSceneTreeMessage(refChain, "Hierarchy too deep, sorry :("); - return false; - } - - return true; - } - } -} \ No newline at end of file diff --git a/Debugger/GamePanelExtender.cs b/Debugger/GamePanelExtender.cs deleted file mode 100644 index 6ecda0c..0000000 --- a/Debugger/GamePanelExtender.cs +++ /dev/null @@ -1,696 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using ColossalFramework.UI; -using ModTools.Utils; -using UnityEngine; - -namespace ModTools -{ - - class GamePanelExtender : MonoBehaviour - { - - - private bool initializedZonedBuildingsPanel = false; - private ZonedBuildingWorldInfoPanel zonedBuildingInfoPanel; - private UILabel zonedBuildingAssetNameLabel; - private UIButton zonedBuildingShowExplorerButton; - private UIButton zonedBuildingDumpMeshTextureButton; - - private bool initializedServiceBuildingsPanel = false; - private CityServiceWorldInfoPanel serviceBuildingInfoPanel; - private UILabel serviceBuildingAssetNameLabel; - private UIButton serviceBuildingShowExplorerButton; - private UIButton serviceBuildingDumpMeshTextureButton; - - private SceneExplorer sceneExplorer; - - private ReferenceChain buildingsBufferRefChain; - private ReferenceChain vehiclesBufferRefChain; - private ReferenceChain vehiclesParkedBufferRefChain; - private ReferenceChain citizenInstancesBufferRefChain; - private ReferenceChain citizensBufferRefChain; - private ReferenceChain citizensUnitsBufferRefChain; - - private bool initializedCitizenVehiclePanel = false; - private CitizenVehicleWorldInfoPanel citizenVehicleInfoPanel; - private UILabel citizenVehicleAssetNameLabel; - private UIButton citizenVehicleShowExplorerButton; - private UIButton citizenVehicleDumpTextureMeshButton; - - private bool initializedCityServiceVehiclePanel = false; - private CityServiceVehicleWorldInfoPanel cityServiceVehicleInfoPanel; - private UILabel cityServiceVehicleAssetNameLabel; - private UIButton cityServiceVehicleShowExplorerButton; - private UIButton cityServiceVehicleDumpTextureMeshButton; - - - private bool initializedPublicTransportVehiclePanel = false; - private PublicTransportVehicleWorldInfoPanel publicTransportVehicleInfoPanel; - private UILabel publicTransportVehicleAssetNameLabel; - private UIButton publicTransportVehicleShowExplorerButton; - private UIButton publicTransportVehicleDumpTextureMeshButton; - - private bool initializedAnimalPanel = false; - private AnimalWorldInfoPanel animalInfoPanel; - private UILabel animalAssetNameLabel; - private UIButton animalShowExplorerButton; - private UIButton animalShowInstanceButton; - private UIButton animalShowUnitButton; - - private bool initializedCitizenPanel = false; - private HumanWorldInfoPanel citizenInfoPanel; - private UILabel citizenAssetNameLabel; - private UIButton citizenShowExplorerButton; - private UIButton citizenShowInstanceButton; - private UIButton citizenShowUnitButton; - - void OnDestroy() - { - try - { - Destroy(zonedBuildingAssetNameLabel.gameObject); - Destroy(zonedBuildingShowExplorerButton.gameObject); - Destroy(zonedBuildingDumpMeshTextureButton.gameObject); - - Destroy(serviceBuildingAssetNameLabel.gameObject); - Destroy(serviceBuildingShowExplorerButton.gameObject); - Destroy(serviceBuildingDumpMeshTextureButton.gameObject); - - - zonedBuildingInfoPanel.component.Find("AllGood").isVisible = true; - zonedBuildingInfoPanel.component.Find("ProblemsPanel").isVisible = true; - - serviceBuildingInfoPanel.component.Find("AllGood").isVisible = true; - serviceBuildingInfoPanel.component.Find("ProblemsPanel").isVisible = true; - - Destroy(citizenVehicleAssetNameLabel.gameObject); - Destroy(citizenVehicleShowExplorerButton.gameObject); - Destroy(citizenVehicleDumpTextureMeshButton.gameObject); - - citizenVehicleInfoPanel.component.Find("Type").isVisible = true; - - Destroy(cityServiceVehicleAssetNameLabel.gameObject); - Destroy(cityServiceVehicleShowExplorerButton.gameObject); - Destroy(cityServiceVehicleDumpTextureMeshButton.gameObject); - - cityServiceVehicleInfoPanel.component.Find("Type").isVisible = true; - - Destroy(publicTransportVehicleAssetNameLabel.gameObject); - Destroy(publicTransportVehicleShowExplorerButton.gameObject); - Destroy(publicTransportVehicleDumpTextureMeshButton.gameObject); - - publicTransportVehicleInfoPanel.component.Find("Type").isVisible = true; - - Destroy(animalAssetNameLabel.gameObject); - Destroy(animalShowExplorerButton.gameObject); - Destroy(animalShowInstanceButton.gameObject); - - publicTransportVehicleInfoPanel.component.Find("Type").isVisible = true; - } - catch (Exception) - { - } - } - - UIButton CreateButton(string text, int width, int height, UIComponent parentComponent, Vector3 offset, UIAlignAnchor anchor, MouseEventHandler handler) - { - var button = UIView.GetAView().AddUIComponent(typeof(UIButton)) as UIButton; - button.name = "ModTools Button"; - button.text = text; - button.textScale = 0.8f; - button.width = width; - button.height = height; - button.normalBgSprite = "ButtonMenu"; - button.disabledBgSprite = "ButtonMenuDisabled"; - button.hoveredBgSprite = "ButtonMenuHovered"; - button.focusedBgSprite = "ButtonMenu"; - button.pressedBgSprite = "ButtonMenuPressed"; - button.textColor = new Color32(255, 255, 255, 255); - button.disabledTextColor = new Color32(7, 7, 7, 255); - button.hoveredTextColor = new Color32(255, 255, 255, 255); - button.focusedTextColor = new Color32(255, 255, 255, 255); - button.pressedTextColor = new Color32(30, 30, 44, 255); - button.eventClick += handler; - button.AlignTo(parentComponent, anchor); - button.relativePosition += offset; - return button; - } - - UILabel CreateLabel(string text, int width, int height, UIComponent parentComponent, Vector3 offset, - UIAlignAnchor anchor) - { - var label = UIView.GetAView().AddUIComponent(typeof(UILabel)) as UILabel; - label.text = text; - label.name = "ModTools Label"; - label.width = width; - label.height = height; - label.textColor = new Color32(255, 255, 255, 255); - label.AlignTo(parentComponent, anchor); - label.relativePosition += offset; - return label; - } - - void AddBuildingPanelControls(WorldInfoPanel infoPanel, out UILabel assetNameLabel, - out UIButton showExplorerButton, Vector3 showExplorerButtonOffset, - out UIButton dumpMeshTextureButton, Vector3 dumpMeshTextureButtonOffset) - { - infoPanel.component.Find("AllGood").isVisible = false; - infoPanel.component.Find("ProblemsPanel").isVisible = false; - - assetNameLabel = CreateLabel - ( - "AssetName: <>", 160, 24, - infoPanel.component, - new Vector3(8.0f, 48.0f, 0.0f), - UIAlignAnchor.TopLeft - ); - - showExplorerButton = CreateButton - ( - "Find in SceneExplorer", 160, 24, - infoPanel.component, - showExplorerButtonOffset, - UIAlignAnchor.TopRight, - (component, param) => - { - InstanceID instance = ReflectionUtil.GetPrivate(infoPanel, "m_InstanceID"); - sceneExplorer.ExpandFromRefChain(buildingsBufferRefChain.Add(instance.Building)); - sceneExplorer.visible = true; - } - ); - - dumpMeshTextureButton = CreateButton - ( - "Dump asset", 160, 24, - infoPanel.component, - dumpMeshTextureButtonOffset, - UIAlignAnchor.TopRight, - (component, param) => - { - var instance = ReflectionUtil.GetPrivate(infoPanel, "m_InstanceID"); - var building = BuildingManager.instance.m_buildings.m_buffer[instance.Building]; - var assetName = building.Info.name; - DumpUtil.DumpAsset(assetName, building.Info.m_mesh, building.Info.m_material, building.Info.m_lodMesh, building.Info.m_lodMaterial); - } - ); - } - - void AddVehiclePanelControls(WorldInfoPanel infoPanel, out UILabel assetNameLabel, out UIButton showExplorerButton, out UIButton dumpMeshTextureButton) - { - infoPanel.component.Find("Type").isVisible = false; - - assetNameLabel = CreateLabel - ( - "AssetName: <>", 160, 24, - infoPanel.component, - new Vector3(8.0f, 48.0f, 0.0f), - UIAlignAnchor.TopLeft - ); - - showExplorerButton = CreateButton - ( - "Find in SceneExplorer", 160, 24, - infoPanel.component, - new Vector3(-8.0f, -57.0f, 0.0f), - UIAlignAnchor.BottomRight, - (component, param) => - { - InstanceID instance = ReflectionUtil.GetPrivate(infoPanel, "m_InstanceID"); - - if (instance.Vehicle == 0) - { - sceneExplorer.ExpandFromRefChain(vehiclesParkedBufferRefChain.Add(instance.ParkedVehicle)); - } - else - { - sceneExplorer.ExpandFromRefChain(vehiclesBufferRefChain.Add(instance.Vehicle)); - } - - sceneExplorer.visible = true; - } - ); - - dumpMeshTextureButton = CreateButton - ( - "Dump asset", 160, 24, - infoPanel.component, - new Vector3(-8.0f, -25.0f, 0.0f), - UIAlignAnchor.BottomRight, - (component, param) => - { - var instance = ReflectionUtil.GetPrivate(infoPanel, "m_InstanceID"); - var vehicleInfo = instance.Vehicle == 0 ? VehicleManager.instance.m_parkedVehicles.m_buffer[instance.ParkedVehicle].Info : VehicleManager.instance.m_vehicles.m_buffer[instance.Vehicle].Info; - var assetName = vehicleInfo.name; - DumpUtil.DumpAsset(assetName, vehicleInfo.m_mesh, vehicleInfo.m_material, vehicleInfo.m_lodMesh, vehicleInfo.m_lodMaterial); - } - ); - } - - void AddCitizenPanelControls(WorldInfoPanel infoPanel, out UILabel assetNameLabel, - out UIButton showExplorerButton, Vector3 showExplorerButtonOffset, - out UIButton showInstanceButton, Vector3 showInstanceButtonOffset, - out UIButton showUnitButton, Vector3 showUniteButtonOffset - ) - { - - assetNameLabel = CreateLabel - ( - "AssetName: <>", 160, 24, - infoPanel.component, - new Vector3(8.0f, 48.0f, 0.0f), - UIAlignAnchor.TopLeft - ); - - showExplorerButton = CreateButton - ( - "Find citizen in SE", 160, 24, - infoPanel.component, - showExplorerButtonOffset, - UIAlignAnchor.TopRight, - (component, param) => - { - InstanceID instance = ReflectionUtil.GetPrivate(infoPanel, "m_InstanceID"); - if (instance.Type == InstanceType.CitizenInstance) - { - var ci = CitizenManager.instance.m_instances.m_buffer[instance.CitizenInstance]; - if (ci.m_citizen == 0) - { - return; - } - sceneExplorer.ExpandFromRefChain(citizenInstancesBufferRefChain.Add((int)ci.m_citizen)); - } - else if (instance.Type == InstanceType.Citizen) - { - sceneExplorer.ExpandFromRefChain(citizensBufferRefChain.Add((int)instance.Citizen)); - } - sceneExplorer.visible = true; - } - ); - - showInstanceButton = CreateButton - ( - "Find instance in SE ", 160, 24, - infoPanel.component, - showInstanceButtonOffset, - UIAlignAnchor.TopRight, - (component, param) => - { - var instance = ReflectionUtil.GetPrivate(infoPanel, "m_InstanceID"); - if (instance.Type == InstanceType.CitizenInstance) - { - sceneExplorer.ExpandFromRefChain(citizenInstancesBufferRefChain.Add(instance.CitizenInstance)); - sceneExplorer.visible = true; - } - else if (instance.Type == InstanceType.Citizen) - { - for (var index = 0; index < CitizenManager.instance.m_instances.m_buffer.Length; index++) - { - var ci = CitizenManager.instance.m_instances.m_buffer[index]; - if (ci.m_flags == CitizenInstance.Flags.None || ci.Info == null || ci.m_citizen != instance.Citizen) - { - continue; - } - sceneExplorer.ExpandFromRefChain(citizenInstancesBufferRefChain.Add(index)); - sceneExplorer.visible = true; - break; - } - } - } - ); - - showUnitButton = CreateButton - ( - "Find unit in SE ", 160, 24, - infoPanel.component, - showUniteButtonOffset, - UIAlignAnchor.TopRight, - (component, param) => - { - var instance = ReflectionUtil.GetPrivate(infoPanel, "m_InstanceID"); - if (instance.Type == InstanceType.CitizenInstance) - { - var ci = CitizenManager.instance.m_instances.m_buffer[instance.CitizenInstance]; - var citizen = ci.m_citizen; - if (citizen == 0) - { - return; - } - for (var index = 0; index < CitizenManager.instance.m_units.m_buffer.Length; index++) - { - var cu = CitizenManager.instance.m_units.m_buffer[index]; - if (cu.m_flags == CitizenUnit.Flags.None) - { - continue; - } - uint unit = 0; - if (cu.m_citizen0 == citizen) - { - unit = cu.m_citizen0; - } - else if (cu.m_citizen1 == citizen) - { - unit = cu.m_citizen1; - } - else if (cu.m_citizen2 == citizen) - { - unit = cu.m_citizen2; - } - else if (cu.m_citizen3 == citizen) - { - unit = cu.m_citizen3; - } - else if (cu.m_citizen4 == citizen) - { - unit = cu.m_citizen4; - } - if (unit == 0) - { - continue; - } - sceneExplorer.ExpandFromRefChain(citizensUnitsBufferRefChain.Add(index)); - sceneExplorer.visible = true; - break; - } - sceneExplorer.visible = true; - } - else if (instance.Type == InstanceType.Citizen) - { - if (instance.Citizen == 0) - { - return; - } - for (var index = 0; index < CitizenManager.instance.m_units.m_buffer.Length; index++) - { - var cu = CitizenManager.instance.m_units.m_buffer[index]; - if (cu.m_flags == CitizenUnit.Flags.None) - { - continue; - } - uint unit = 0; - if (cu.m_citizen0 == instance.Citizen) - { - unit = cu.m_citizen0; - } - else if (cu.m_citizen1 == instance.Citizen) - { - unit = cu.m_citizen1; - } - else if (cu.m_citizen2 == instance.Citizen) - { - unit = cu.m_citizen2; - } - else if (cu.m_citizen3 == instance.Citizen) - { - unit = cu.m_citizen3; - } - else if (cu.m_citizen4 == instance.Citizen) - { - unit = cu.m_citizen4; - } - if (unit == 0) - { - continue; - } - sceneExplorer.ExpandFromRefChain(citizensUnitsBufferRefChain.Add(index)); - sceneExplorer.visible = true; - break; - } - } - } - ); - } - - void Update() - { - Initialize(); - SetInstance(); - } - - private void Initialize() - { - if (!initializedZonedBuildingsPanel) - { - sceneExplorer = FindObjectOfType(); - - buildingsBufferRefChain = new ReferenceChain() - .Add(BuildingManager.instance.gameObject) - .Add(BuildingManager.instance) - .Add(typeof(BuildingManager).GetField("m_buildings")) - .Add(typeof(Array16).GetField("m_buffer")); - - zonedBuildingInfoPanel = - GameObject.Find("(Library) ZonedBuildingWorldInfoPanel").GetComponent(); - if (zonedBuildingInfoPanel != null) - { - AddBuildingPanelControls(zonedBuildingInfoPanel, out zonedBuildingAssetNameLabel, - out zonedBuildingShowExplorerButton, new Vector3(-8.0f, 100.0f, 0.0f), - out zonedBuildingDumpMeshTextureButton, new Vector3(-8.0f, 132.0f, 0.0f) - ); - initializedZonedBuildingsPanel = true; - } - } - - if (!initializedServiceBuildingsPanel) - { - sceneExplorer = FindObjectOfType(); - serviceBuildingInfoPanel = - GameObject.Find("(Library) CityServiceWorldInfoPanel").GetComponent(); - if (serviceBuildingInfoPanel != null) - { - AddBuildingPanelControls(serviceBuildingInfoPanel, out serviceBuildingAssetNameLabel, - out serviceBuildingShowExplorerButton, new Vector3(-8.0f, 175.0f, 0.0f), - out serviceBuildingDumpMeshTextureButton, new Vector3(-8.0f, 200.0f, 0.0f) - ); - initializedServiceBuildingsPanel = true; - } - } - - if (!initializedCitizenVehiclePanel) - { - sceneExplorer = FindObjectOfType(); - - vehiclesBufferRefChain = new ReferenceChain() - .Add(VehicleManager.instance.gameObject) - .Add(VehicleManager.instance) - .Add(typeof(VehicleManager).GetField("m_vehicles")) - .Add(typeof(Array16).GetField("m_buffer")); - - vehiclesParkedBufferRefChain = new ReferenceChain() - .Add(VehicleManager.instance.gameObject) - .Add(VehicleManager.instance) - .Add(typeof(VehicleManager).GetField("m_parkedVehicles")) - .Add(typeof(Array16).GetField("m_buffer")); - - citizenVehicleInfoPanel = - GameObject.Find("(Library) CitizenVehicleWorldInfoPanel").GetComponent(); - if (citizenVehicleInfoPanel != null) - { - AddVehiclePanelControls( - citizenVehicleInfoPanel, - out citizenVehicleAssetNameLabel, - out citizenVehicleShowExplorerButton, - out citizenVehicleDumpTextureMeshButton - ); - initializedCitizenVehiclePanel = true; - } - } - - if (!initializedCityServiceVehiclePanel) - { - sceneExplorer = FindObjectOfType(); - - cityServiceVehicleInfoPanel = - GameObject.Find("(Library) CityServiceVehicleWorldInfoPanel") - .GetComponent(); - if (cityServiceVehicleInfoPanel != null) - { - AddVehiclePanelControls( - cityServiceVehicleInfoPanel, - out cityServiceVehicleAssetNameLabel, - out cityServiceVehicleShowExplorerButton, - out cityServiceVehicleDumpTextureMeshButton); - initializedCityServiceVehiclePanel = true; - } - } - - if (!initializedPublicTransportVehiclePanel) - { - sceneExplorer = FindObjectOfType(); - - publicTransportVehicleInfoPanel = - GameObject.Find("(Library) PublicTransportVehicleWorldInfoPanel") - .GetComponent(); - if (publicTransportVehicleInfoPanel != null) - { - AddVehiclePanelControls( - publicTransportVehicleInfoPanel, - out publicTransportVehicleAssetNameLabel, - out publicTransportVehicleShowExplorerButton, - out publicTransportVehicleDumpTextureMeshButton); - initializedPublicTransportVehiclePanel = true; - } - } - - if (!initializedAnimalPanel) - { - citizenInstancesBufferRefChain = new ReferenceChain() - .Add(CitizenManager.instance.gameObject) - .Add(CitizenManager.instance) - .Add(typeof(CitizenManager).GetField("m_instances")) - .Add(typeof(Array16).GetField("m_buffer")); - citizensBufferRefChain = new ReferenceChain() - .Add(CitizenManager.instance.gameObject) - .Add(CitizenManager.instance) - .Add(typeof(CitizenManager).GetField("m_citizens")) - .Add(typeof(Array32).GetField("m_buffer")); - citizensUnitsBufferRefChain = new ReferenceChain() - .Add(CitizenManager.instance.gameObject) - .Add(CitizenManager.instance) - .Add(typeof(CitizenManager).GetField("m_units")) - .Add(typeof(Array32).GetField("m_buffer")); - - sceneExplorer = FindObjectOfType(); - animalInfoPanel = GameObject.Find("(Library) AnimalWorldInfoPanel").GetComponent(); - if (animalInfoPanel != null) - { - AddCitizenPanelControls(animalInfoPanel, out animalAssetNameLabel, - out animalShowExplorerButton, new Vector3(-8.0f, 65.0f, 0.0f), - out animalShowInstanceButton, new Vector3(-8.0f, 90.0f, 0.0f), - out animalShowUnitButton, new Vector3(-8.0f, 115.0f, 0.0f) - ); - initializedAnimalPanel = true; - } - } - - if (!initializedCitizenPanel) - { - citizenInstancesBufferRefChain = new ReferenceChain() - .Add(CitizenManager.instance.gameObject) - .Add(CitizenManager.instance) - .Add(typeof(CitizenManager).GetField("m_instances")) - .Add(typeof(Array16).GetField("m_buffer")); - citizensBufferRefChain = new ReferenceChain() - .Add(CitizenManager.instance.gameObject) - .Add(CitizenManager.instance) - .Add(typeof(CitizenManager).GetField("m_citizens")) - .Add(typeof(Array32).GetField("m_buffer")); - citizensUnitsBufferRefChain = new ReferenceChain() - .Add(CitizenManager.instance.gameObject) - .Add(CitizenManager.instance) - .Add(typeof(CitizenManager).GetField("m_units")) - .Add(typeof(Array32).GetField("m_buffer")); - - - sceneExplorer = FindObjectOfType(); - citizenInfoPanel = GameObject.Find("(Library) CitizenWorldInfoPanel").GetComponent(); - if (citizenInfoPanel != null) - { - AddCitizenPanelControls(citizenInfoPanel, out citizenAssetNameLabel, - out citizenShowExplorerButton, new Vector3(-8.0f, 110.0f, 0.0f), - out citizenShowInstanceButton, new Vector3(-8.0f, 135.0f, 0.0f), - out citizenShowUnitButton, new Vector3(-8.0f, 160.0f, 0.0f) - ); - initializedCitizenPanel = true; - } - } - } - - private void SetInstance() - { - if (zonedBuildingInfoPanel.component.isVisible) - { - InstanceID instance = ReflectionUtil.GetPrivate(zonedBuildingInfoPanel, "m_InstanceID"); - var building = BuildingManager.instance.m_buildings.m_buffer[instance.Building]; - zonedBuildingAssetNameLabel.text = $"AssetName: {building.Info.name}"; - } - - if (serviceBuildingInfoPanel.component.isVisible) - { - InstanceID instance = ReflectionUtil.GetPrivate(serviceBuildingInfoPanel, "m_InstanceID"); - var building = BuildingManager.instance.m_buildings.m_buffer[instance.Building]; - serviceBuildingAssetNameLabel.text = $"AssetName: {building.Info.name}"; - } - - if (citizenVehicleInfoPanel.component.isVisible) - { - InstanceID instance = ReflectionUtil.GetPrivate(citizenVehicleInfoPanel, "m_InstanceID"); - - if (instance.Vehicle == 0) - { - var vehicle = VehicleManager.instance.m_parkedVehicles.m_buffer[instance.ParkedVehicle]; - citizenVehicleAssetNameLabel.text = $"AssetName: {vehicle.Info.name}"; - } - else - { - var vehicle = VehicleManager.instance.m_vehicles.m_buffer[instance.Vehicle]; - citizenVehicleAssetNameLabel.text = $"AssetName: {vehicle.Info.name}"; - } - } - - if (cityServiceVehicleInfoPanel.component.isVisible) - { - InstanceID instance = ReflectionUtil.GetPrivate(cityServiceVehicleInfoPanel, "m_InstanceID"); - - if (instance.Vehicle == 0) - { - var vehicle = VehicleManager.instance.m_parkedVehicles.m_buffer[instance.ParkedVehicle]; - cityServiceVehicleAssetNameLabel.text = $"AssetName: {vehicle.Info.name}"; - } - else - { - var vehicle = VehicleManager.instance.m_vehicles.m_buffer[instance.Vehicle]; - cityServiceVehicleAssetNameLabel.text = $"AssetName: {vehicle.Info.name}"; - } - } - - if (publicTransportVehicleInfoPanel.component.isVisible) - { - InstanceID instance = ReflectionUtil.GetPrivate(publicTransportVehicleInfoPanel, "m_InstanceID"); - - if (instance.Vehicle == 0) - { - var vehicle = VehicleManager.instance.m_parkedVehicles.m_buffer[instance.ParkedVehicle]; - publicTransportVehicleAssetNameLabel.text = $"AssetName: {vehicle.Info.name}"; - } - else - { - var vehicle = VehicleManager.instance.m_vehicles.m_buffer[instance.Vehicle]; - publicTransportVehicleAssetNameLabel.text = $"AssetName: {vehicle.Info.name}"; - } - } - - if (animalInfoPanel.component.isVisible) - { - InstanceID instance = ReflectionUtil.GetPrivate(animalInfoPanel, "m_InstanceID"); - var animal = CitizenManager.instance.m_instances.m_buffer[instance.CitizenInstance]; - animalAssetNameLabel.text = $"AssetName: {animal.Info.name}"; - } - - if (citizenInfoPanel.component.isVisible) - { - InstanceID instance = ReflectionUtil.GetPrivate(citizenInfoPanel, "m_InstanceID"); - if (instance.Type == InstanceType.CitizenInstance) - { - var citizen = CitizenManager.instance.m_instances.m_buffer[instance.CitizenInstance]; - citizenAssetNameLabel.text = $"AssetName: {citizen.Info.name}"; - } - else if (instance.Type == InstanceType.Citizen) - { - citizenAssetNameLabel.text = "AssetName: N/A"; - foreach (var ci in CitizenManager.instance.m_instances.m_buffer) - { - if (ci.m_flags == CitizenInstance.Flags.None || ci.Info == null || - ci.m_citizen != instance.Citizen) - { - continue; - } - citizenAssetNameLabel.text = $"AssetName: {ci.Info.name}"; - break; - } - } - } - } - } -} diff --git a/Debugger/GamePanels/ButtonsInfoPanelExtension.cs b/Debugger/GamePanels/ButtonsInfoPanelExtension.cs new file mode 100644 index 0000000..32d7930 --- /dev/null +++ b/Debugger/GamePanels/ButtonsInfoPanelExtension.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using ColossalFramework.UI; + +namespace ModTools.GamePanels +{ + internal sealed class ButtonsInfoPanelExtension : InfoPanelExtensionBase + where T : WorldInfoPanel + { + private readonly List createdButtons; + private readonly IDictionary> additionalButtons; + + private ButtonsInfoPanelExtension( + string infoPanelName, + Func assetNameGetter, + Action displayAction, + IDictionary> buttons) + : base(infoPanelName, assetNameGetter, displayAction) + { + additionalButtons = new Dictionary>(buttons); + createdButtons = new List(additionalButtons.Count); + } + + public static ButtonsInfoPanelExtension Create( + string panelName, + Func assetNameGetter, + Action displayAction, + IDictionary> buttons) + { + if (string.IsNullOrEmpty(panelName)) + { + throw new ArgumentException("The panel name cannot be null or empty string.", nameof(panelName)); + } + + if (assetNameGetter == null) + { + throw new ArgumentNullException(nameof(assetNameGetter)); + } + + if (displayAction == null) + { + throw new ArgumentNullException(nameof(displayAction)); + } + + if (buttons == null) + { + throw new ArgumentNullException(nameof(buttons)); + } + + var result = new ButtonsInfoPanelExtension(panelName, assetNameGetter, displayAction, buttons); + return result.Initialize() ? result : null; + } + + protected override void DisableCore() + { + foreach (var button in createdButtons) + { + button.eventClick -= AdditionalButtonClick; + ItemsPanel.RemoveUIComponent(button); + UnityEngine.Object.Destroy(button); + } + + createdButtons.Clear(); + additionalButtons.Clear(); + } + + protected override bool InitializeCore() + { + foreach (var buttonDefinition in additionalButtons) + { + var newButton = AddButton(buttonDefinition.Key); + if (newButton == null) + { + return false; + } + + newButton.eventClick += AdditionalButtonClick; + createdButtons.Add(newButton); + } + + return true; + } + + private void AdditionalButtonClick(UIComponent component, UIMouseEventParameter eventParam) + { + var buttonCaption = ((UIButton)component).text; + var action = additionalButtons[buttonCaption]; + action(CurrentInstanceId); + } + } +} diff --git a/Debugger/GamePanels/GamePanelExtension.cs b/Debugger/GamePanels/GamePanelExtension.cs new file mode 100644 index 0000000..f533a36 --- /dev/null +++ b/Debugger/GamePanels/GamePanelExtension.cs @@ -0,0 +1,351 @@ +using System; +using System.Collections.Generic; +using ModTools.Explorer; +using ModTools.Utils; +using UnityEngine; + +namespace ModTools.GamePanels +{ + internal sealed class GamePanelExtension : MonoBehaviour, IDestroyableObject, IAwakingObject + { + private readonly ReferenceChain buildingsBufferRefChain; + private readonly ReferenceChain vehiclesBufferRefChain; + private readonly ReferenceChain vehiclesParkedBufferRefChain; + private readonly ReferenceChain citizenInstancesBufferRefChain; + private readonly ReferenceChain citizensBufferRefChain; + private readonly ReferenceChain citizensUnitsBufferRefChain; + + private readonly List customPanels = new List(); + + private SceneExplorer sceneExplorer; + + public GamePanelExtension() + { + buildingsBufferRefChain = + new ReferenceChain() + .Add(BuildingManager.instance.gameObject) + .Add(BuildingManager.instance) + .Add(typeof(BuildingManager).GetField("m_buildings")) + .Add(typeof(Array16).GetField("m_buffer")); + + vehiclesBufferRefChain = + new ReferenceChain() + .Add(VehicleManager.instance.gameObject) + .Add(VehicleManager.instance) + .Add(typeof(VehicleManager).GetField("m_vehicles")) + .Add(typeof(Array16).GetField("m_buffer")); + + vehiclesParkedBufferRefChain = + new ReferenceChain() + .Add(VehicleManager.instance.gameObject) + .Add(VehicleManager.instance) + .Add(typeof(VehicleManager).GetField("m_parkedVehicles")) + .Add(typeof(Array16).GetField("m_buffer")); + + citizenInstancesBufferRefChain = + new ReferenceChain() + .Add(CitizenManager.instance.gameObject) + .Add(CitizenManager.instance) + .Add(typeof(CitizenManager).GetField("m_instances")) + .Add(typeof(Array16).GetField("m_buffer")); + + citizensBufferRefChain = + new ReferenceChain() + .Add(CitizenManager.instance.gameObject) + .Add(CitizenManager.instance) + .Add(typeof(CitizenManager).GetField("m_citizens")) + .Add(typeof(Array32).GetField("m_buffer")); + + citizensUnitsBufferRefChain = + new ReferenceChain() + .Add(CitizenManager.instance.gameObject) + .Add(CitizenManager.instance) + .Add(typeof(CitizenManager).GetField("m_units")) + .Add(typeof(Array32).GetField("m_buffer")); + } + + public void OnDestroy() + { + foreach (var panel in customPanels) + { + panel.Disable(); + } + + customPanels.Clear(); + + sceneExplorer = null; + } + + public void Awake() + { + sceneExplorer = FindObjectOfType(); + + if (sceneExplorer != null) + { + CreateBuildingsPanels(); + CreateVehiclePanels(); + CreateCitizenPanels(); + } + } + + private static ushort GetCitizenInstanceId(InstanceID instanceId) + { + var result = instanceId.CitizenInstance; + if (result == 0) + { + var citizenId = instanceId.Citizen; + if (citizenId != 0) + { + result = CitizenManager.instance.m_citizens.m_buffer[citizenId].m_instance; + } + } + + return result; + } + + private static uint GetCitizenId(InstanceID instanceId) + { + var result = instanceId.Citizen; + if (result == 0) + { + var citizenInstanceId = instanceId.CitizenInstance; + if (citizenInstanceId != 0) + { + result = CitizenManager.instance.m_instances.m_buffer[citizenInstanceId].m_citizen; + } + } + + return result; + } + + private static string GetBuildingAssetName(InstanceID instanceId) + { + var buildingId = instanceId.Building; + return buildingId != 0 + ? BuildingManager.instance.m_buildings.m_buffer[buildingId].Info?.name ?? "N/A" + : "N/A"; + } + + private static string GetVehicleAssetName(InstanceID instanceId) + { + var vehicleId = instanceId.Vehicle; + if (vehicleId != 0) + { + return VehicleManager.instance.m_vehicles.m_buffer[vehicleId].Info?.name ?? "N/A"; + } + + vehicleId = instanceId.ParkedVehicle; + if (vehicleId != 0) + { + return VehicleManager.instance.m_parkedVehicles.m_buffer[vehicleId].Info?.name ?? "N/A"; + } + + return "N/A"; + } + + private static string GetCitizenAssetName(InstanceID instanceId) + { + var citizenInstanceId = GetCitizenInstanceId(instanceId); + if (citizenInstanceId != 0) + { + return CitizenManager.instance.m_instances.m_buffer[citizenInstanceId].Info?.name ?? "N/A"; + } + + return "N/A"; + } + + private static void DumpBuilding(InstanceID instanceId) + { + var buildingId = instanceId.Building; + if (buildingId == 0) + { + return; + } + + var buildingInfo = BuildingManager.instance.m_buildings.m_buffer[buildingId].Info; + if (buildingInfo != null) + { + DumpUtil.DumpAsset( + buildingInfo.name, + buildingInfo.m_mesh, + buildingInfo.m_material, + buildingInfo.m_lodMesh, + buildingInfo.m_lodMaterial); + } + } + + private static void DumpVehicle(InstanceID instanceId) + { + var vehicleId = instanceId.Vehicle; + + VehicleInfo vehicleInfo = null; + if (vehicleId != 0) + { + vehicleInfo = VehicleManager.instance.m_vehicles.m_buffer[vehicleId].Info; + } + else + { + vehicleId = instanceId.ParkedVehicle; + if (vehicleId != 0) + { + vehicleInfo = VehicleManager.instance.m_parkedVehicles.m_buffer[vehicleId].Info; + } + } + + if (vehicleInfo != null) + { + DumpUtil.DumpAsset( + vehicleInfo.name, + vehicleInfo.m_mesh, + vehicleInfo.m_material, + vehicleInfo.m_lodMesh, + vehicleInfo.m_lodMaterial); + } + } + + private void CreateBuildingsPanels() + { + CreateBuildingPanel(); + CreateBuildingPanel(); + CreateBuildingPanel(); + CreateBuildingPanel(); + CreateBuildingPanel(); + CreateBuildingPanel(); + CreateBuildingPanel(); + CreateBuildingPanel(); + CreateBuildingPanel(); + } + + private void CreateVehiclePanels() + { + CreateVehiclePanel(); + CreateVehiclePanel(); + CreateVehiclePanel(); + CreateVehiclePanel(); + } + + private void CreateCitizenPanels() + { + CreateCitizenPanel(); + CreateCitizenPanel(); + CreateCitizenPanel(); + CreateCitizenPanel(); + } + + private void CreateBuildingPanel() + where T : BuildingWorldInfoPanel + { + var name = "(Library) " + typeof(T).Name; + var buttons = new Dictionary> + { + ["Dump asset"] = DumpBuilding, + }; + + var buildingPanel = ButtonsInfoPanelExtension.Create(name, GetBuildingAssetName, ShowBuilding, buttons); + customPanels.Add(buildingPanel); + } + + private void CreateVehiclePanel() + where T : VehicleWorldInfoPanel + { + var name = "(Library) " + typeof(T).Name; + var buttons = new Dictionary> + { + ["Dump asset"] = DumpVehicle, + }; + + var vehiclePanel = ButtonsInfoPanelExtension.Create(name, GetVehicleAssetName, ShowVehicle, buttons); + customPanels.Add(vehiclePanel); + } + + private void CreateCitizenPanel() + where T : LivingCreatureWorldInfoPanel + { + var name = "(Library) " + typeof(T).Name; + var buttons = new Dictionary> + { + ["Show instance in Scene Explorer"] = ShowCitizenInstance, + ["Show unit in Scene Explorer"] = ShowCitizenUnit, + }; + + var vehiclePanel = ButtonsInfoPanelExtension.Create(name, GetCitizenAssetName, ShowCitizen, buttons); + customPanels.Add(vehiclePanel); + } + + private void ShowBuilding(InstanceID instanceId) + { + var buildingId = instanceId.Building; + if (buildingId != 0) + { + sceneExplorer.Show(buildingsBufferRefChain.Add(buildingId)); + } + } + + private void ShowVehicle(InstanceID instanceId) + { + var vehicleId = instanceId.Vehicle; + if (vehicleId != 0) + { + sceneExplorer.Show(vehiclesBufferRefChain.Add(vehicleId)); + return; + } + + vehicleId = instanceId.ParkedVehicle; + if (vehicleId != 0) + { + sceneExplorer.Show(vehiclesParkedBufferRefChain.Add(vehicleId)); + } + } + + private void ShowCitizen(InstanceID instanceId) + { + var citizenId = GetCitizenId(instanceId); + + if (citizenId != 0) + { + sceneExplorer.Show(citizensBufferRefChain.Add((int)citizenId)); + } + } + + private void ShowCitizenUnit(InstanceID instanceId) + { + var citizenId = GetCitizenId(instanceId); + if (citizenId == 0) + { + return; + } + + ref var citizen = ref CitizenManager.instance.m_citizens.m_buffer[citizenId]; + var buildingId = citizen.m_homeBuilding; + if (buildingId == 0) + { + buildingId = citizen.m_workBuilding; + } + + if (buildingId == 0) + { + buildingId = citizen.m_visitBuilding; + } + + if (buildingId == 0) + { + return; + } + + var unitId = BuildingManager.instance.m_buildings.m_buffer[buildingId].FindCitizenUnit(CitizenUnit.Flags.All, citizenId); + if (unitId != 0) + { + sceneExplorer.Show(citizensUnitsBufferRefChain.Add((int)unitId)); + } + } + + private void ShowCitizenInstance(InstanceID instanceId) + { + var citizenInstanceId = GetCitizenInstanceId(instanceId); + if (citizenInstanceId != 0) + { + sceneExplorer.Show(citizenInstancesBufferRefChain.Add(citizenInstanceId)); + } + } + } +} \ No newline at end of file diff --git a/Debugger/GamePanels/IInfoPanelExtension.cs b/Debugger/GamePanels/IInfoPanelExtension.cs new file mode 100644 index 0000000..fa13d2f --- /dev/null +++ b/Debugger/GamePanels/IInfoPanelExtension.cs @@ -0,0 +1,7 @@ +namespace ModTools.GamePanels +{ + internal interface IInfoPanelExtension + { + void Disable(); + } +} diff --git a/Debugger/GamePanels/InfoPanelExtensionBase.cs b/Debugger/GamePanels/InfoPanelExtensionBase.cs new file mode 100644 index 0000000..5039195 --- /dev/null +++ b/Debugger/GamePanels/InfoPanelExtensionBase.cs @@ -0,0 +1,253 @@ +using System; +using System.Linq; +using System.Reflection; +using ColossalFramework.UI; +using ModTools.Utils; +using UnityEngine; + +namespace ModTools.GamePanels +{ + /// A base class for the world info panel extension. + /// The type of the game world info panel to extend. + internal abstract class InfoPanelExtensionBase : IInfoPanelExtension + where T : WorldInfoPanel + { + private readonly Func getAssetName; + private readonly Action showInExplorer; + private readonly string infoPanelName; + private readonly FieldInfo instanceField; + + private T infoPanel; + private UIPanel infoPanelContainer; + private UILabel assetLabel; + private UIButton showInExplorerButton; + + /// Initializes a new instance of the class. + /// Name of the game's panel object. + /// A delegate representing a method that can return the asset name of the panel's displayed object. + /// A delegate representing a method that can display the panel's object in the scene explorer. + /// + /// Thrown when is null or an empty string. + /// + protected InfoPanelExtensionBase( + string infoPanelName, + Func assetNameGetter, + Action displayAction) + { + if (string.IsNullOrEmpty(infoPanelName)) + { + throw new ArgumentException("The panel name cannot be null or an empty string.", nameof(infoPanelName)); + } + + getAssetName = assetNameGetter ?? throw new ArgumentNullException(nameof(assetNameGetter)); + showInExplorer = displayAction ?? throw new ArgumentNullException(nameof(displayAction)); + + this.infoPanelName = infoPanelName; + instanceField = TypeUtil.FindField(typeof(T), "m_InstanceID"); + } + + /// + /// Gets the current value of the game panel's that is being displayed. + /// + protected InstanceID CurrentInstanceId { get; private set; } + + /// Gets the UI container of the info panel where to custom controls can be placed. + protected UIPanel ItemsPanel { get; private set; } + + /// Disables the info panel extension, if it is enabled. + public void Disable() + { + DisableCore(); + + if (infoPanelContainer != null) + { + infoPanelContainer.eventPositionChanged -= UpdatePanelContent; + infoPanelContainer.eventSizeChanged -= UpdatePanelSize; + infoPanelContainer = null; + } + + if (ItemsPanel == null) + { + return; + } + + showInExplorerButton.eventClick -= ShowInExplorerButtonClick; + + ItemsPanel.RemoveUIComponent(showInExplorerButton); + UnityEngine.Object.Destroy(showInExplorerButton); + + ItemsPanel.RemoveUIComponent(assetLabel); + UnityEngine.Object.Destroy(assetLabel); + + infoPanel.component.RemoveUIComponent(ItemsPanel); + UnityEngine.Object.Destroy(ItemsPanel); + ItemsPanel = null; + infoPanel = null; + } + + /// + /// Adds a simple label into this info panel extension. + /// + /// The newly created label. + protected UILabel AddLabel() + { + if (ItemsPanel == null) + { + return null; + } + + var label = ItemsPanel.AddUIComponent(); + label.name = "ModTools Label"; + label.autoSize = true; + return label; + } + + /// + /// Adds a button with specified as caption into this info panel extension. + /// + /// The button's caption. + /// The newly created button. + protected UIButton AddButton(string text) + { + if (ItemsPanel == null) + { + return null; + } + + var button = ItemsPanel.AddUIComponent(); + button.name = "ModTools Button"; + button.text = text; + button.textScale = 0.8f; + button.autoSize = true; + button.textPadding = new RectOffset(10, 10, 5, 5); + button.normalBgSprite = "ButtonMenu"; + button.disabledBgSprite = "ButtonMenuDisabled"; + button.hoveredBgSprite = "ButtonMenuHovered"; + button.focusedBgSprite = "ButtonMenu"; + button.pressedBgSprite = "ButtonMenuPressed"; + button.textColor = new Color32(255, 255, 255, 255); + button.disabledTextColor = new Color32(7, 7, 7, 255); + button.hoveredTextColor = new Color32(255, 255, 255, 255); + button.focusedTextColor = new Color32(255, 255, 255, 255); + button.pressedTextColor = new Color32(30, 30, 44, 255); + return button; + } + + /// Initializes this instance and builds up the custom UI objects. + /// true on success; otherwise, false. + protected bool Initialize() + { + ConnectToInfoPanel(); + if (infoPanelContainer != null) + { + infoPanelContainer.eventPositionChanged += UpdatePanelContent; + infoPanelContainer.eventSizeChanged += UpdatePanelSize; + } + + SetupItemsPanel(); + if (ItemsPanel == null) + { + return false; + } + + assetLabel = AddLabel(); + assetLabel.padding = new RectOffset(4, 4, 4, 8); + showInExplorerButton = AddButton("Show in Scene Explorer"); + showInExplorerButton.eventClick += ShowInExplorerButtonClick; + + return InitializeCore(); + } + + /// When overridden in derive classes, builds up the custom UI objects for the info panel. + /// true on success; otherwise, false. + protected virtual bool InitializeCore() => true; + + /// When overridden in derived classes, destroys the custom UI objects for the info panel. + protected virtual void DisableCore() + { + } + + private void ShowInExplorerButtonClick(UIComponent component, UIMouseEventParameter eventParam) => showInExplorer(CurrentInstanceId); + + private void UpdatePanelContent(UIComponent component, Vector2 value) + { + CurrentInstanceId = (InstanceID)instanceField.GetValue(infoPanel); + assetLabel.text = getAssetName(CurrentInstanceId); + } + + private void UpdatePanelSize(UIComponent component, Vector2 value) + { + var parentWidth = infoPanelContainer.width; + if (ItemsPanel.width != parentWidth) + { + ItemsPanel.width = parentWidth; + } + } + + private void ConnectToInfoPanel() + { + var panelGameObject = GameObject.Find(infoPanelName); + if (panelGameObject == null) + { + Debug.LogWarning($"Failed to extend the info panel '{infoPanelName}'. No game object '{infoPanelName}' found."); + return; + } + + infoPanel = panelGameObject.GetComponent(); + if (infoPanel == null) + { + Debug.LogWarning($"Failed to extend the info panel '{infoPanelName}'. No game object's component of type '{typeof(T).Name}' found."); + return; + } + + infoPanelContainer = infoPanel.component as UIPanel; + if (infoPanelContainer == null) + { + Debug.LogWarning($"Failed to extend the info panel '{infoPanelName}'. No main UI panel found."); + } + } + + private void SetupItemsPanel() + { + if (infoPanelContainer == null) + { + return; + } + + var parentPanel = infoPanelContainer; + + if (typeof(T) == typeof(CityServiceWorldInfoPanel)) + { + // This magic is required because the 'city service info panel' has a different layout, + // so the standard AlignTo won't do - we must align not to the panel itself, but to its child panel. + var wrapperPanel = infoPanelContainer.components.OfType().FirstOrDefault(c => c.name == "Wrapper"); + if (wrapperPanel != null) + { + parentPanel = wrapperPanel; + } + } + else if (typeof(T) == typeof(FestivalPanel)) + { + // This magic is required because the 'festival panel' is buggy, + // it grows unlimitedly on performing auto layout - so restrict its size. + infoPanelContainer.maximumSize = infoPanelContainer.size; + infoPanelContainer.clipChildren = false; + } + + var itemsPanel = parentPanel.AddUIComponent(); + itemsPanel.name = "ModTools extension panel"; + itemsPanel.width = parentPanel.width; + itemsPanel.backgroundSprite = "MenuPanel"; + + itemsPanel.padding = new RectOffset(5, 5, 10, 10); + itemsPanel.autoLayoutPadding = new RectOffset(0, 0, 0, 4); + itemsPanel.autoLayout = true; + itemsPanel.autoFitChildrenVertically = true; + itemsPanel.autoLayoutDirection = LayoutDirection.Vertical; + + itemsPanel.AlignTo(parentPanel, UIAlignAnchor.BottomLeft); + + ItemsPanel = itemsPanel; + } + } +} \ No newline at end of file diff --git a/Debugger/IGameObject.cs b/Debugger/IGameObject.cs new file mode 100644 index 0000000..cdd1a68 --- /dev/null +++ b/Debugger/IGameObject.cs @@ -0,0 +1,23 @@ +namespace ModTools +{ + public interface IGameObject + { + void Update(); + } + + public interface IDestroyableObject + { + void OnDestroy(); + } + + public interface IAwakingObject + { + void Awake(); + } + + public interface IUIObject + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709", MessageId = "GUI", Justification = "Unity method")] + void OnGUI(); + } +} diff --git a/Debugger/ILogger.cs b/Debugger/ILogger.cs new file mode 100644 index 0000000..0b937fa --- /dev/null +++ b/Debugger/ILogger.cs @@ -0,0 +1,9 @@ +using UnityEngine; + +namespace ModTools +{ + internal interface ILogger + { + void Log(string message, LogType type); + } +} \ No newline at end of file diff --git a/Debugger/LoadingExtension.cs b/Debugger/LoadingExtension.cs index 0eabccf..e1207b2 100644 --- a/Debugger/LoadingExtension.cs +++ b/Debugger/LoadingExtension.cs @@ -1,31 +1,26 @@ -using System; -using ColossalFramework; +using ColossalFramework; using ICities; +using ModTools.GamePanels; namespace ModTools { - public class LoadingExtension : LoadingExtensionBase + public sealed class LoadingExtension : LoadingExtensionBase { - public override void OnCreated(ILoading loading) - { - base.OnCreated(loading); - ModToolsBootstrap.Bootstrap(); - } - public override void OnLevelLoaded(LoadMode mode) { base.OnLevelLoaded(mode); CustomPrefabs.Bootstrap(); var appMode = Singleton.instance.m_properties.m_mode; - var modTools = ModTools.Instance; + var modTools = MainWindow.Instance; if (modTools == null) { UnityEngine.Debug.LogError("ModTools instance wasn't present"); return; } - if (modTools.config.extendGamePanels && appMode == ItemClass.Availability.Game) + + if (modTools.Config.ExtendGamePanels && appMode == ItemClass.Availability.Game) { - modTools.gameObject.AddComponent(); + modTools.gameObject.AddComponent(); } } diff --git a/Debugger/Log.cs b/Debugger/Log.cs deleted file mode 100644 index 90cf6fa..0000000 --- a/Debugger/Log.cs +++ /dev/null @@ -1,43 +0,0 @@ -using UnityEngine; - -namespace ModTools -{ - public static class Log - { - public static void Message(string s) - { - if (ModTools.Instance.console != null) - { - ModTools.Instance.console.AddMessage(s, LogType.Log, false); - } - else - { - DebugOutputPanel.AddMessage(ColossalFramework.Plugins.PluginManager.MessageType.Message, s); - } - } - - public static void Error(string s) - { - if (ModTools.Instance.console != null) - { - ModTools.Instance.console.AddMessage(s, LogType.Error, false); - } - else - { - DebugOutputPanel.AddMessage(ColossalFramework.Plugins.PluginManager.MessageType.Error, s); - } - } - - public static void Warning(string s) - { - if (ModTools.Instance.console != null) - { - ModTools.Instance.console.AddMessage(s, LogType.Warning, false); - } - else - { - DebugOutputPanel.AddMessage(ColossalFramework.Plugins.PluginManager.MessageType.Warning, s); - } - } - } -} diff --git a/Debugger/Logger.cs b/Debugger/Logger.cs new file mode 100644 index 0000000..291909e --- /dev/null +++ b/Debugger/Logger.cs @@ -0,0 +1,72 @@ +using UnityEngine; + +namespace ModTools +{ + internal static class Logger + { + private static readonly object SyncObject = new object(); + private static ILogger customLogger; + + public static void SetCustomLogger(ILogger logger) + { + lock (SyncObject) + { + customLogger = logger; + } + } + + public static void Message(string s) + { + ILogger logger; + lock (SyncObject) + { + logger = customLogger; + } + + if (logger != null) + { + logger.Log(s, LogType.Log); + } + else + { + DebugOutputPanel.AddMessage(ColossalFramework.Plugins.PluginManager.MessageType.Message, s); + } + } + + public static void Error(string s) + { + ILogger logger; + lock (SyncObject) + { + logger = customLogger; + } + + if (logger != null) + { + logger.Log(s, LogType.Error); + } + else + { + DebugOutputPanel.AddMessage(ColossalFramework.Plugins.PluginManager.MessageType.Error, s); + } + } + + public static void Warning(string s) + { + ILogger logger; + lock (SyncObject) + { + logger = customLogger; + } + + if (logger != null) + { + logger.Log(s, LogType.Warning); + } + else + { + DebugOutputPanel.AddMessage(ColossalFramework.Plugins.PluginManager.MessageType.Warning, s); + } + } + } +} \ No newline at end of file diff --git a/Debugger/MainWindow.cs b/Debugger/MainWindow.cs new file mode 100644 index 0000000..9c38ab3 --- /dev/null +++ b/Debugger/MainWindow.cs @@ -0,0 +1,352 @@ +using ColossalFramework.UI; +using ModTools.Console; +using ModTools.Explorer; +using ModTools.GamePanels; +using ModTools.Scripting; +using ModTools.UI; +using UnityEngine; + +namespace ModTools +{ + internal sealed class MainWindow : GUIWindow, IGameObject + { + private const string ConfigPath = "ModToolsConfig.xml"; + private static readonly object LoggingLock = new object(); + private static readonly object InstanceLock = new object(); + + private static MainWindow instance; + private static bool loggingInitialized; + + private readonly ModalUI modalUI = new ModalUI(); + + private CustomConsole console; + private ScriptEditor scriptEditor; + private AppearanceConfig appearanceConfig; + private DebugRenderer debugRenderer; + + public MainWindow() + : base("Mod Tools " + ModToolsMod.Version, new Rect(128, 128, 356, 320), Skin, resizable: false) + { + } + + // TODO: remove the singleton + public static MainWindow Instance + { + get + { + lock (InstanceLock) + { + instance = instance ?? FindObjectOfType(); + return instance; + } + } + } + + // TODO: refactor the configuration access + public ModConfiguration Config { get; set; } = new ModConfiguration(); + + // TODO: refactor and remove this property + internal SceneExplorer SceneExplorer { get; private set; } + + // TODO: refactor and remove this property + internal ColorPicker ColorPicker { get; private set; } + + // TODO: refactor and remove this property + internal Watches Watches { get; private set; } + + // TODO: refactor and move this functionality to a dedicated service + public void SaveConfig() + { + if (Config == null) + { + return; + } + + if (console != null) + { + Config.ConsoleRect = console.WindowRect; + } + + Config.WatchesRect = Watches.WindowRect; + Config.SceneExplorerRect = SceneExplorer.WindowRect; + Config.Serialize(ConfigPath); + } + + public void Initialize() + { + if (!loggingInitialized) + { + Application.logMessageReceivedThreaded += OnApplicationLogMessageReceivedThreaded; + + loggingInitialized = true; + } + + SceneExplorer = gameObject.AddComponent(); + Watches = gameObject.AddComponent(); + ColorPicker = gameObject.AddComponent(); + scriptEditor = gameObject.AddComponent(); + appearanceConfig = gameObject.AddComponent(); + + LoadConfig(); + + if (Config.UseModToolsConsole) + { + console = gameObject.AddComponent(); + Logger.SetCustomLogger(console); + } + } + + public void Update() + { + var middleButtonState = MouseButtonState.None; + if (Input.GetMouseButtonDown(2)) + { + middleButtonState = MouseButtonState.Pressed; + } + else if (Input.GetMouseButtonUp(2)) + { + middleButtonState = MouseButtonState.Released; + } + else if (Input.GetMouseButton(2)) + { + middleButtonState = MouseButtonState.Held; + } + + modalUI.Update(IsMouseOverWindow(), middleButtonState); + + if (!Input.GetKey(KeyCode.LeftControl)) + { + if (Config.UseModToolsConsole && Input.GetKeyDown(KeyCode.F7)) + { + console.Visible = !console.Visible; + } + + return; + } + + // The shortcuts below are 'Ctrl' + key + if (Input.GetKeyDown(KeyCode.Q)) + { + Visible = !Visible; + } + else if (Input.GetKeyDown(KeyCode.E)) + { + SceneExplorer.Visible = !SceneExplorer.Visible; + if (SceneExplorer.Visible) + { + SceneExplorer.Refresh(); + } + } + else if (Input.GetKeyDown(KeyCode.R)) + { + if (debugRenderer == null) + { + debugRenderer = FindObjectOfType().gameObject.AddComponent(); + } + + debugRenderer.DrawDebugInfo = !debugRenderer.DrawDebugInfo; + } + else if (Input.GetKeyDown(KeyCode.W)) + { + Watches.Visible = !Watches.Visible; + } + else if (Input.GetKeyDown(KeyCode.S)) + { + scriptEditor.Visible = !scriptEditor.Visible; + } + } + + protected override void OnWindowDestroyed() + { + Destroy(console); + Destroy(SceneExplorer); + Destroy(appearanceConfig); + Destroy(scriptEditor); + Destroy(Watches); + Destroy(ColorPicker); + } + + protected override void DrawWindow() + { + var newUseConsole = GUILayout.Toggle(Config.UseModToolsConsole, " Use ModTools console"); + + if (newUseConsole != Config.UseModToolsConsole) + { + Config.UseModToolsConsole = newUseConsole; + + if (Config.UseModToolsConsole) + { + console = gameObject.AddComponent(); + Logger.SetCustomLogger(console); + } + else + { + Destroy(console); + console = null; + Logger.SetCustomLogger(null); + } + + SaveConfig(); + } + + GUILayout.BeginHorizontal(); + GUILayout.Label("Console log level"); + var newLogLevel = GUILayout.SelectionGrid(Config.LogLevel, new[] { "Log", "Warn", "Err", "None" }, 4); + GUILayout.EndHorizontal(); + + if (newLogLevel != Config.LogLevel) + { + Config.LogLevel = newLogLevel; + SaveConfig(); + } + + var newLogExceptionsToConsole = GUILayout.Toggle(Config.LogExceptionsToConsole, " Log stack traces to console"); + if (newLogExceptionsToConsole != Config.LogExceptionsToConsole) + { + Config.LogExceptionsToConsole = newLogExceptionsToConsole; + SaveConfig(); + } + + var newExtendGamePanels = GUILayout.Toggle(Config.ExtendGamePanels, " Game panel extensions"); + + if (newExtendGamePanels != Config.ExtendGamePanels) + { + Config.ExtendGamePanels = newExtendGamePanels; + SaveConfig(); + + if (ToolManager.instance.m_properties.m_mode == ItemClass.Availability.Game) + { + var gamePanelExtender = gameObject.GetComponent(); + if (Config.ExtendGamePanels) + { + if (gamePanelExtender == null) + { + gameObject.AddComponent(); + } + } + else if (gamePanelExtender != null) + { + Destroy(gamePanelExtender); + } + } + } + + if (debugRenderer == null) + { + debugRenderer = FindObjectOfType().gameObject.AddComponent(); + } + + debugRenderer.DrawDebugInfo = GUILayout.Toggle(debugRenderer.DrawDebugInfo, " Debug Renderer (Ctrl + R)"); + + var customPrefabsObject = GUILayout.Toggle(Config.CustomPrefabsObject, " Custom Prefabs Object"); + if (customPrefabsObject != Config.CustomPrefabsObject) + { + Config.CustomPrefabsObject = customPrefabsObject; + if (Config.CustomPrefabsObject) + { + CustomPrefabs.Bootstrap(); + } + else + { + CustomPrefabs.Revert(); + } + + SaveConfig(); + } + + GUILayout.Space(Config.TreeIdentSpacing); + + if (GUILayout.Button("Debug console (F7)")) + { + if (console != null) + { + console.Visible = true; + } + else + { + var debugOutputPanel = GameObject.Find("(Library) DebugOutputPanel").GetComponent(); + debugOutputPanel.enabled = true; + debugOutputPanel.GetComponent().isVisible = true; + } + } + + if (GUILayout.Button("Watches (Ctrl + W)")) + { + Watches.Visible = !Watches.Visible; + } + + if (GUILayout.Button("Scene explorer (Ctrl + E)")) + { + SceneExplorer.Visible = !SceneExplorer.Visible; + if (SceneExplorer.Visible) + { + SceneExplorer.Refresh(); + } + } + + if (GUILayout.Button("Script editor (Ctrl + S)")) + { + scriptEditor.Visible = !scriptEditor.Visible; + } + + GUILayout.FlexibleSpace(); + + if (GUILayout.Button("Appearance settings")) + { + appearanceConfig.Visible = true; + var windowRect = appearanceConfig.WindowRect; + windowRect.position = WindowRect.position + new Vector2(32.0f, 32.0f); + appearanceConfig.MoveResize(windowRect); + } + } + + private void OnApplicationLogMessageReceivedThreaded(string condition, string trace, LogType type) + { + lock (LoggingLock) + { + if (Config.LogLevel > 2) + { + return; + } + + if (type == LogType.Exception) + { + var message = condition; + if (Config.LogExceptionsToConsole && trace != null) + { + message = $"{message}\n\n{trace}"; + } + + Logger.Error(message); + } + else if (type == LogType.Error || type == LogType.Assert) + { + Logger.Error(condition); + } + else if (type == LogType.Warning && Config.LogLevel < 2) + { + Logger.Warning(condition); + } + else if (Config.LogLevel == 0) + { + Logger.Message(condition); + } + } + } + + private void LoadConfig() + { + Config = ModConfiguration.Deserialize(ConfigPath); + if (Config == null) + { + Config = new ModConfiguration(); + SaveConfig(); + } + + console?.MoveResize(Config.ConsoleRect); + Watches.MoveResize(Config.WatchesRect); + SceneExplorer.MoveResize(Config.SceneExplorerRect); + scriptEditor.ReloadProjectWorkspace(); + } + } +} \ No newline at end of file diff --git a/Debugger/MeshViewer.cs b/Debugger/MeshViewer.cs deleted file mode 100644 index 1a0ad8f..0000000 --- a/Debugger/MeshViewer.cs +++ /dev/null @@ -1,266 +0,0 @@ -using System; -using System.Reflection; -using ColossalFramework.UI; -using ICities; -using ModTools.Utils; -using UnityEngine; - -namespace ModTools -{ - public class MeshViewer : GUIWindow - { - private Mesh previewMesh = null; - private Material previewMaterial; - private String assetName = null; - - private RenderTexture targetRT; - - private float m_Distance = 4f; - private Vector2 m_PreviewDir = new Vector2(120f, -20f); - private Bounds bounds; - private Vector2 lastLeftMousePos = Vector2.zero; - private Vector2 lastRightMousePos = Vector2.zero; - - private Camera meshViewerCamera; - - private Material material; - - private Light light; - - private bool useOriginalShader = false; - - private MeshViewer() - : base("Mesh Viewer", new Rect(512, 128, 512, 512), skin) - { - onDraw = DrawWindow; - onClose = () => - { - SetCitizenInfoObjects(true); - Destroy(this); - }; - onUnityDestroy = () => - { - Destroy(meshViewerCamera); - Destroy(targetRT); - }; - - try - { - light = GameObject.Find("Directional Light").GetComponent(); - } - catch (Exception) - { - light = null; - } - - meshViewerCamera = gameObject.AddComponent(); - meshViewerCamera.transform.position = new Vector3(-10000.0f, -10000.0f, -10000.0f); - meshViewerCamera.fieldOfView = 30f; - meshViewerCamera.backgroundColor = Color.grey; - meshViewerCamera.nearClipPlane = 1.0f; - meshViewerCamera.farClipPlane = 1000.0f; - meshViewerCamera.enabled = false; - meshViewerCamera.allowHDR = true; - - targetRT = new RenderTexture(512, 512, 24, RenderTextureFormat.ARGBHalf, RenderTextureReadWrite.Linear); - meshViewerCamera.targetTexture = targetRT; - } - - public static MeshViewer CreateMeshViewer(String assetName, Mesh mesh, Material material, bool calculateBounds = true) - { - var go = new GameObject("MeshViewer"); - go.transform.parent = ModTools.Instance.transform; - var meshViewer = go.AddComponent(); - meshViewer.assetName = assetName; - meshViewer.previewMesh = mesh; - meshViewer.material = material; - - meshViewer.previewMaterial = new Material(Shader.Find("Diffuse")); - if (material != null) - { - meshViewer.previewMaterial.mainTexture = material.mainTexture; - } - meshViewer.Setup(calculateBounds); - meshViewer.visible = true; - meshViewer.SetCitizenInfoObjects(false); - return meshViewer; - } - - void Update() - { - if (previewMesh == null) - { - return; - } - - float intensity = 0.0f; - Color color = Color.black; - bool enabled = false; - - if (light != null) - { - intensity = light.intensity; - color = light.color; - enabled = light.enabled; - light.intensity = 2.0f; - light.color = Color.white; - light.enabled = true; - } - - float magnitude = bounds.extents.magnitude; - float num1 = magnitude + 16f; - float num2 = magnitude * this.m_Distance; - this.meshViewerCamera.transform.position = -Vector3.forward * num2; - this.meshViewerCamera.transform.rotation = Quaternion.identity; - this.meshViewerCamera.nearClipPlane = Mathf.Max(num2 - num1 * 1.5f, 0.01f); - this.meshViewerCamera.farClipPlane = num2 + num1 * 1.5f; - Quaternion q = Quaternion.Euler(this.m_PreviewDir.y, 0.0f, 0.0f) * - Quaternion.Euler(0.0f, this.m_PreviewDir.x, 0.0f); - var trs = Matrix4x4.TRS(q * -bounds.center, q, Vector3.one); - - var material1 = (useOriginalShader && material != null) ? material : previewMaterial; - Graphics.DrawMesh(previewMesh, trs, material1, 0, meshViewerCamera, 0, null, false, false); - meshViewerCamera.RenderWithShader(material1.shader, ""); - - if (light != null) - { - light.intensity = intensity; - light.color = color; - light.enabled = enabled; - } - } - - void DrawWindow() - { - if (previewMesh != null) - { - title = $"Previewing \"{assetName ?? previewMesh.name}\""; - - GUILayout.BeginHorizontal(); - - if (material != null) - { - useOriginalShader = GUILayout.Toggle(useOriginalShader, "Original Shader"); - if (previewMesh.isReadable) - { - if (GUILayout.Button("Dump mesh+textures", GUILayout.Width(160))) - { - DumpUtil.DumpMeshAndTextures(assetName ?? previewMesh.name, previewMesh, material); - } - } - else - { - if (GUILayout.Button("Dump textures", GUILayout.Width(160))) - { - DumpUtil.DumpTextures(assetName, material); - } - } - } - else - { - useOriginalShader = false; - if (previewMesh.isReadable) - { - if (GUILayout.Button("Dump mesh", GUILayout.Width(160))) - { - DumpUtil.DumpMeshAndTextures($"{previewMesh.name}", previewMesh); - } - } - } - if (previewMesh.isReadable) - { - GUILayout.Label($"Triangles: {previewMesh.triangles.Length / 3}"); - } - else - { - var oldColor = GUI.color; - GUI.color = Color.yellow; - GUILayout.Label("Mesh insn't readable!"); - GUI.color = oldColor; - } - if (material?.mainTexture != null) - { - GUILayout.Label($"Texture size: {material.mainTexture.width}x{material.mainTexture.height}"); - } - - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - - if (Event.current.type == EventType.MouseDown) - { - if (Event.current.button == 0 || Event.current.button == 2) - { - lastLeftMousePos = Event.current.mousePosition; - } - else - { - lastRightMousePos = Event.current.mousePosition; - } - } - else if (Event.current.type == EventType.MouseDrag) - { - var pos = Event.current.mousePosition; - if (Event.current.button == 0 || Event.current.button == 2) - { - if (lastLeftMousePos != Vector2.zero) - { - Vector2 moveDelta = (pos - lastLeftMousePos) * 2.0f; - this.m_PreviewDir -= moveDelta / Mathf.Min(this.targetRT.width, this.targetRT.height) * - UIView.GetAView().ratio * 140f; - this.m_PreviewDir.y = Mathf.Clamp(this.m_PreviewDir.y, -90f, 90f); - } - lastLeftMousePos = pos; - } - else - { - if (lastRightMousePos != Vector2.zero) - { - Vector2 moveDelta1 = pos - lastRightMousePos; - this.m_Distance += (float) ((double) moveDelta1.y / (double) this.targetRT.height * - (double) UIView.GetAView().ratio * 40.0); - float num1 = 6f; - float magnitude = bounds.extents.magnitude; - float num2 = magnitude + 16f; - this.m_Distance = Mathf.Min(this.m_Distance, 4f, num1 * (num2 / magnitude)); - } - lastRightMousePos = pos; - } - } - - GUI.DrawTexture(new Rect(0.0f, 64.0f, rect.width, rect.height - 64.0f), targetRT, - ScaleMode.StretchToFill, false); - } - else - { - title = "Mesh Viewer"; - GUILayout.Label("Use the Scene Explorer to select a Mesh for preview"); - } - } - - public void Setup(bool calculateBounds) - { - if (!((UnityEngine.Object) this.previewMesh != (UnityEngine.Object) null)) - return; - if (calculateBounds && previewMesh.isReadable) - { - this.bounds = new Bounds(Vector3.zero, Vector3.zero); - foreach (Vector3 vertex in this.previewMesh.vertices) - this.bounds.Encapsulate(vertex); - } - else - { - this.bounds = new Bounds(new Vector3(0, 0, 0), new Vector3(3, 3, 3)); - } - this.m_Distance = 4f; - } - - public void SetCitizenInfoObjects(bool enabled) - { - CitizenInfo[] info = Resources.FindObjectsOfTypeAll(); - foreach (CitizenInfo i in info) - { - i.gameObject.SetActive(enabled); - } - } - } -} \ No newline at end of file diff --git a/Debugger/Mod.cs b/Debugger/Mod.cs deleted file mode 100644 index a8b85d7..0000000 --- a/Debugger/Mod.cs +++ /dev/null @@ -1,16 +0,0 @@ -using ICities; - -namespace ModTools -{ - public class Mod : IUserMod - { - - public string Name - { - get { ModToolsBootstrap.Bootstrap(); return "ModTools"; } - } - - public string Description => "Debugging toolkit for modders"; - } - -} diff --git a/Debugger/ModConfiguration.cs b/Debugger/ModConfiguration.cs new file mode 100644 index 0000000..4ef5e40 --- /dev/null +++ b/Debugger/ModConfiguration.cs @@ -0,0 +1,222 @@ +using System; +using System.IO; +using System.Xml.Serialization; +using UnityEngine; + +namespace ModTools +{ + [XmlRoot("Configuration")] + public sealed class ModConfiguration + { + private const float RGB = 255f; + private const float Opaque = 1f; + + #region General + + [XmlElement("customPrefabsObject")] + public bool CustomPrefabsObject { get; set; } = true; + + [XmlElement("extendGamePanels")] + public bool ExtendGamePanels { get; set; } = true; + + [XmlElement("logLevel")] + public int LogLevel { get; set; } + + #endregion + + #region Appearance + + [XmlElement("fontName")] + public string FontName { get; set; } = "Courier New Bold"; + + [XmlElement("fontSize")] + public int FontSize { get; set; } = 14; + + [XmlElement("backgroundColor")] + public Color BackgroundColor { get; set; } = new Color(100 / RGB, 100 / RGB, 110 / RGB, 235 / RGB); + + [XmlElement("titlebarColor")] + public Color TitleBarColor { get; set; } = new Color(0 / RGB, 0 / RGB, 0 / RGB, Opaque); + + [XmlElement("titlebarTextColor")] + public Color TitleBarTextColor { get; set; } = new Color(255 / RGB, 255 / RGB, 255 / RGB, Opaque); + + #endregion + + #region Window state + + [XmlElement("mainWindowRect")] + public Rect MainWindowRect { get; set; } = new Rect(128, 128, 356, 300); + + [XmlElement("consoleRect")] + public Rect ConsoleRect { get; set; } = new Rect(16f, 16f, 512f, 256f); + + [XmlElement("sceneExplorerRect")] + public Rect SceneExplorerRect { get; set; } = new Rect(128, 440, 800, 500); + + [XmlElement("watchesRect")] + public Rect WatchesRect { get; set; } = new Rect(504, 128, 800, 300); + + #endregion + + #region Console + + [XmlElement("useModToolsConsole")] + public bool UseModToolsConsole { get; set; } = true; + + [XmlElement("hiddenNotifications")] + public int HiddenNotifications { get; set; } + + [XmlElement("logExceptionsToConsole")] + public bool LogExceptionsToConsole { get; set; } = true; + + [XmlElement("consoleMaxHistoryLength")] + public int ConsoleMaxHistoryLength { get; set; } = 1024; + + [XmlElement("consoleFormatString")] + public string ConsoleFormatString { get; set; } = "[{{type}}] {{caller}}: {{message}}"; + + [XmlElement("showConsoleOnMessage")] + public bool ShowConsoleOnMessage { get; set; } + + [XmlElement("showConsoleOnWarning")] + public bool ShowConsoleOnWarning { get; set; } + + [XmlElement("showConsoleOnError")] + public bool ShowConsoleOnError { get; set; } = true; + + [XmlElement("consoleAutoScrollToBottom")] + public bool ConsoleAutoScrollToBottom { get; set; } = true; + + #region Appearance + + [XmlElement("consoleMessageColor")] + public Color ConsoleMessageColor { get; set; } = Color.white; + + [XmlElement("consoleWarningColor")] + public Color ConsoleWarningColor { get; set; } = Color.yellow; + + [XmlElement("consoleErrorColor")] + public Color ConsoleErrorColor { get; set; } = new Color(0.7f, 0.1f, 0.1f, 1f); + + [XmlElement("consoleExceptionColor")] + public Color ConsoleExceptionColor { get; set; } = new Color(1f, 0f, 0f, 1f); + + #endregion + + #endregion + + #region Scene explorer + + [XmlElement("sceneExplorerSortAlphabetically")] + public bool SortItemsAlphabetically { get; set; } = true; + + [XmlElement("sceneExplorerMaxHierarchyDepth")] + public int MaxHierarchyDepth { get; set; } = 32; + + [XmlElement("sceneExplorerEvaluatePropertiesAutomatically")] + public bool EvaluateProperties { get; set; } = true; + + [XmlElement("sceneExplorerShowFields")] + public bool ShowFields { get; set; } = true; + + [XmlElement("sceneExplorerShowConsts")] + public bool ShowConsts { get; set; } + + [XmlElement("sceneExplorerShowProperties")] + public bool ShowProperties { get; set; } = true; + + [XmlElement("sceneExplorerShowMethods")] + public bool ShowMethods { get; set; } + + [XmlElement("sceneExplorerShowModifiers")] + public bool ShowModifiers { get; set; } + + [XmlElement("sceneExplorerShowInheritedMembers")] + public bool ShowInheritedMembers { get; set; } + + #region Appearance + + [XmlElement("sceneExplorerTreeIdentSpacing")] + public float TreeIdentSpacing { get; set; } = 16f; + + [XmlElement("gameObjectColor")] + public Color GameObjectColor { get; set; } = new Color(80 / RGB, 200 / RGB, 180 / RGB, Opaque); + + [XmlElement("enabledComponentColor")] + public Color EnabledComponentColor { get; set; } = new Color(220 / RGB, 220 / RGB, 170 / RGB, Opaque); + + [XmlElement("disabledComponentColor")] + public Color DisabledComponentColor { get; set; } = new Color(180 / RGB, 180 / RGB, 190 / RGB, Opaque); + + [XmlElement("selectedComponentColor")] + public Color SelectedComponentColor { get; set; } = new Color(80 / RGB, 160 / RGB, 220 / RGB, Opaque); + + [XmlElement("nameColor")] + public Color NameColor { get; set; } = new Color(220 / RGB, 220 / RGB, 170 / RGB, Opaque); + + [XmlElement("typeColor")] + public Color TypeColor { get; set; } = new Color(80 / RGB, 200 / RGB, 180 / RGB, Opaque); + + [XmlElement("keywordColor")] + public Color KeywordColor { get; set; } = new Color(220 / RGB, 160 / RGB, 220 / RGB, Opaque); + + [XmlElement("modifierColor")] + public Color ModifierColor { get; set; } = new Color(80 / RGB, 160 / RGB, 220 / RGB, Opaque); + + [XmlElement("memberTypeColor")] + public Color MemberTypeColor { get; set; } = new Color(220 / RGB, 160 / RGB, 220 / RGB, Opaque); + + [XmlElement("valueColor")] + public Color ValueColor { get; set; } = new Color(160 / RGB, 220 / RGB, 255 / RGB, Opaque); + + #endregion + + #endregion + + #region Script editor + + [XmlElement("scriptEditorWorkspacePath")] + public string ScriptWorkspacePath { get; set; } = string.Empty; + + #endregion + + public static ModConfiguration Deserialize(string filename) + { + var serializer = new XmlSerializer(typeof(ModConfiguration)); + + try + { + using (var reader = new StreamReader(filename)) + { + return (ModConfiguration)serializer.Deserialize(reader); + } + } + catch (Exception e) + { + Debug.LogError("Error happened when deserializing configuration"); + Debug.LogException(e); + } + + return null; + } + + public void Serialize(string filename) + { + var serializer = new XmlSerializer(typeof(ModConfiguration)); + + try + { + using (var writer = new StreamWriter(filename)) + { + serializer.Serialize(writer, this); + } + } + catch (Exception ex) + { + Debug.LogError("Failed to serialize configuration"); + Debug.LogException(ex); + } + } + } +} \ No newline at end of file diff --git a/Debugger/ModTools.cs b/Debugger/ModTools.cs deleted file mode 100644 index f92e118..0000000 --- a/Debugger/ModTools.cs +++ /dev/null @@ -1,356 +0,0 @@ -using System; -using ColossalFramework.UI; -using ICities; -using UnityEngine; - -namespace ModTools -{ - - public class ModTools : GUIWindow - { - -#if DEBUG - public static readonly bool DEBUG_MODTOOLS = true; -#else - public static readonly bool DEBUG_MODTOOLS = false; -#endif - private static readonly object LoggingLock = new object(); - private static readonly object InstanceLock = new object(); - - private Vector2 mainScroll = Vector2.zero; - - public Console console; - public SceneExplorer sceneExplorer; - private DebugRenderer debugRenderer; - public SceneExplorerColorConfig sceneExplorerColorConfig; - - public ScriptEditor scriptEditor; - - public Watches watches; - public ColorPicker colorPicker; - - public Configuration config = new Configuration(); - public static readonly string configPath = "ModToolsConfig.xml"; - - private static ModTools instance = null; - - public void OnUnityDestroyCallback() - { - Destroy(console); - Destroy(sceneExplorer); - Destroy(sceneExplorerColorConfig); - Destroy(scriptEditor); - Destroy(watches); - Destroy(colorPicker); - - instance = null; - ModToolsBootstrap.initialized = false; - } - - public static ModTools Instance - { - get - { - lock (InstanceLock) - { - instance = instance ?? FindObjectOfType(); - return instance; - } - } - } - - public void LoadConfig() - { - config = Configuration.Deserialize(configPath); - if (config == null) - { - config = new Configuration(); - SaveConfig(); - } - - if (console != null) - { - console.rect = config.consoleRect; - console.visible = config.consoleVisible; - } - - watches.rect = config.watchesRect; - watches.visible = config.watchesVisible; - - sceneExplorer.rect = config.sceneExplorerRect; - sceneExplorer.visible = config.sceneExplorerVisible; - - if (sceneExplorer.visible) - { - sceneExplorer.Refresh(); - } - - scriptEditor.ReloadProjectWorkspace(); - } - - public void SaveConfig() - { - if (config != null) - { - if (console != null) - { - config.consoleRect = console.rect; - config.consoleVisible = console.visible; - } - - config.watchesRect = watches.rect; - config.watchesVisible = watches.visible; - - config.sceneExplorerRect = sceneExplorer.rect; - config.sceneExplorerVisible = sceneExplorer.visible; - - Configuration.Serialize(configPath, config); - } - } - - public ModTools() : base("Mod Tools", new Rect(128, 128, 356, 290), skin) - { - onDraw = DoMainWindow; - onUnityDestroy = OnUnityDestroyCallback; - resizable = false; - } - - private static bool loggingInitialized = false; - - public void Initialize() - { - - if (!loggingInitialized) - { - Application.logMessageReceivedThreaded += OnApplicationLogMessageReceivedThreaded; - - loggingInitialized = true; - } - - sceneExplorer = gameObject.AddComponent(); - watches = gameObject.AddComponent(); - colorPicker = gameObject.AddComponent(); - scriptEditor = gameObject.AddComponent(); - scriptEditor.visible = false; - sceneExplorerColorConfig = gameObject.AddComponent(); - - LoadConfig(); - - if (config.useModToolsConsole) - { - console = gameObject.AddComponent(); - } - - } - - private void OnApplicationLogMessageReceivedThreaded(string condition, string trace, LogType type) - { - if (!ModToolsBootstrap.initialized) - { - return; - } - lock (LoggingLock) - { - if (config.logLevel > 2) - { - return; - } - if (type == LogType.Exception) - { - var message = condition; - if (config.logExceptionsToConsole) - { - if (trace != null) - { - message = $"{message}\n\n{trace}"; - } - } - Log.Error(message); - } - else if (type == LogType.Error || type == LogType.Assert) - { - Log.Error(condition); - } - else if (type == LogType.Warning && config.logLevel < 2) - { - Log.Warning(condition); - } - else if (config.logLevel == 0) - { - Log.Message(condition); - } - } - } - - void Update() - { - UpdateMouseScrolling(); - - if (Input.GetKey(KeyCode.LeftControl) && Input.GetKeyDown(KeyCode.Q)) - { - visible = !visible; - } - - if (Input.GetKey(KeyCode.LeftControl) && Input.GetKeyDown(KeyCode.E)) - { - sceneExplorer.visible = !sceneExplorer.visible; - if (sceneExplorer.visible) - { - sceneExplorer.Refresh(); - } - } - - if (Input.GetKey(KeyCode.LeftControl) && Input.GetKeyDown(KeyCode.R)) - { - if (debugRenderer == null) - { - debugRenderer = GameObject.FindObjectOfType().gameObject.AddComponent(); - } - debugRenderer.drawDebugInfo = !debugRenderer.drawDebugInfo; - } - - if (Input.GetKey(KeyCode.LeftControl) && Input.GetKeyDown(KeyCode.W)) - { - watches.visible = !watches.visible; - } - - if (config.useModToolsConsole && Input.GetKeyDown(KeyCode.F7)) - { - console.visible = !console.visible; - } - - if (Input.GetKey(KeyCode.LeftControl) && Input.GetKeyDown(KeyCode.BackQuote)) - { - scriptEditor.visible = !scriptEditor.visible; - } - } - - void DoMainWindow() - { - GUILayout.BeginHorizontal(); - GUILayout.Label("Use ModTools console"); - var newUseConsole = GUILayout.Toggle(config.useModToolsConsole, ""); - GUILayout.EndHorizontal(); - - if (newUseConsole != config.useModToolsConsole) - { - config.useModToolsConsole = newUseConsole; - - if (config.useModToolsConsole) - { - console = gameObject.AddComponent(); - } - else - { - Destroy(console); - console = null; - } - - SaveConfig(); - } - - GUILayout.BeginHorizontal(); - GUILayout.Label("Console log level"); - var newLogLevel = GUILayout.SelectionGrid(config.logLevel, new[]{"Log", "Warn","Err", "None" }, 4); - GUILayout.EndHorizontal(); - - if (newLogLevel != config.logLevel) - { - config.logLevel = newLogLevel; - SaveConfig(); - } - - GUILayout.BeginHorizontal(); - GUILayout.Label("Log stack traces to console"); - var newLogExceptionsToConsole = GUILayout.Toggle(config.logExceptionsToConsole, ""); - GUILayout.EndHorizontal(); - if (newLogExceptionsToConsole != config.logExceptionsToConsole) - { - config.logExceptionsToConsole = newLogExceptionsToConsole; - SaveConfig(); - } - - GUILayout.BeginHorizontal(); - GUILayout.Label("Game panel extensions"); - var newExtendGamePanels = GUILayout.Toggle(config.extendGamePanels, ""); - GUILayout.EndHorizontal(); - - if (newExtendGamePanels != config.extendGamePanels) - { - config.extendGamePanels = newExtendGamePanels; - SaveConfig(); - - if (config.extendGamePanels) - { - gameObject.AddComponent(); - } - else - { - Destroy(gameObject.GetComponent()); - } - } - - GUILayout.BeginHorizontal(); - if (debugRenderer == null) - { - debugRenderer = GameObject.FindObjectOfType().gameObject.AddComponent(); - } - GUILayout.Label("Debug Renderer (Ctrl+R)"); - debugRenderer.drawDebugInfo = GUILayout.Toggle(debugRenderer.drawDebugInfo, ""); - GUILayout.EndHorizontal(); - - - GUILayout.BeginHorizontal(); - GUILayout.Label("Custom Prefabs Object"); - var customPrefabsObject = GUILayout.Toggle(config.customPrefabsObject, ""); - GUILayout.EndHorizontal(); - if (customPrefabsObject != config.customPrefabsObject) - { - config.customPrefabsObject = customPrefabsObject; - if (config.customPrefabsObject) - { - CustomPrefabs.Bootstrap(); - } - else - { - CustomPrefabs.Revert(); - } - SaveConfig(); - } - - if (GUILayout.Button("Debug console (F7)")) - { - if (console != null) - { - console.visible = true; - } - else - { - var debugOutputPanel = GameObject.Find("(Library) DebugOutputPanel").GetComponent(); - debugOutputPanel.enabled = true; - debugOutputPanel.GetComponent().isVisible = true; - } - } - - if (GUILayout.Button("Watches (Ctrl+W)")) - { - watches.visible = !watches.visible; - } - - if (GUILayout.Button("Scene explorer (Ctrl+E)")) - { - sceneExplorer.visible = !sceneExplorer.visible; - if (sceneExplorer.visible) - { - sceneExplorer.Refresh(); - } - } - - if (GUILayout.Button("Script editor (Ctrl+`)")) - { - scriptEditor.visible = !scriptEditor.visible; - } - } - } - -} diff --git a/Debugger/ModTools.csproj b/Debugger/ModTools.csproj index 92ddacc..418d4be 100644 --- a/Debugger/ModTools.csproj +++ b/Debugger/ModTools.csproj @@ -8,7 +8,7 @@ Library Properties ModTools - 000_ModTools + ModTools v3.5 512 @@ -23,6 +23,7 @@ prompt 4 true + ModTools.ruleset AnyCPU @@ -33,6 +34,7 @@ prompt 4 true + ModTools.ruleset @@ -58,75 +60,93 @@ - - + + + + + + + + + + + - - + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - + + + + + - - - - - - + + + + + + - - - - - - - - - + + + + + + + + + + - - - - - + + + + + 4.0.0 + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + 2.9.3 + + + 1.1.118 + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + - mkdir "$(LOCALAPPDATA)\Colossal Order\Cities_Skylines\Addons\Mods\$(ProjectName)" -del "$(LOCALAPPDATA)\Colossal Order\Cities_Skylines\Addons\Mods\$(ProjectName)\$(TargetFileName)" + if not exist "$(LOCALAPPDATA)\Colossal Order\Cities_Skylines\Addons\Mods\$(ProjectName)" mkdir "$(LOCALAPPDATA)\Colossal Order\Cities_Skylines\Addons\Mods\$(ProjectName)" xcopy /y "$(TargetPath)" "$(LOCALAPPDATA)\Colossal Order\Cities_Skylines\Addons\Mods\$(ProjectName)" xcopy /y "$(ProjectDir)\lib" "$(LOCALAPPDATA)\Colossal Order\Cities_Skylines\Addons\Mods\$(ProjectName)" /s /e diff --git a/Debugger/ModTools.ruleset b/Debugger/ModTools.ruleset new file mode 100644 index 0000000..bb0c406 --- /dev/null +++ b/Debugger/ModTools.ruleset @@ -0,0 +1,365 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Debugger/ModToolsBootstrap.cs b/Debugger/ModToolsBootstrap.cs deleted file mode 100644 index df9d240..0000000 --- a/Debugger/ModToolsBootstrap.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.Linq; -using ColossalFramework; -using ColossalFramework.Plugins; -using ICities; -using UnityEngine; - -namespace ModTools -{ - public static class ModToolsBootstrap - { - public static bool initialized; - private static bool bootstrapped; - - public static bool IsModToolsActive() - { -#if DEBUG - return true; -#else - var pluginManager = PluginManager.instance; - var plugins = pluginManager.GetPluginsInfo(); - - return (from item in plugins let instances = item.GetInstances() - where instances.FirstOrDefault() is Mod select item.isEnabled).FirstOrDefault(); - -#endif - } - - public static void Bootstrap() - { - if (!bootstrapped) - { - CODebugBase.verbose = true; - CODebugBase.EnableChannels(LogChannel.All); - bootstrapped = true; - } - if (initialized) - { - return; - } - try - { - InitModTools(); - initialized = true; - } - catch (Exception e) - { - DebugOutputPanel.AddMessage(PluginManager.MessageType.Error, e.Message); - } - } - - private static void InitModTools() - { - if (!IsModToolsActive()) - { - return; - } - var modToolsGameObject = GameObject.Find("ModTools"); - if (modToolsGameObject != null) - { - return; - } - - modToolsGameObject = new GameObject("ModTools"); - var modTools = modToolsGameObject.AddComponent(); - modTools.Initialize(); - } - - } -} diff --git a/Debugger/ModToolsMod.cs b/Debugger/ModToolsMod.cs new file mode 100644 index 0000000..efa1e5e --- /dev/null +++ b/Debugger/ModToolsMod.cs @@ -0,0 +1,56 @@ +using System; +using ColossalFramework; +using ColossalFramework.Plugins; +using ICities; +using ModTools.Utils; +using UnityEngine; + +namespace ModTools +{ + public sealed class ModToolsMod : IUserMod + { + private const string ModToolsName = "ModTools"; + + private GameObject mainObject; + + public static string Version { get; } = GitVersion.GetAssemblyVersion(typeof(ModToolsMod).Assembly); + + public string Name => ModToolsName; + + public string Description => "Debugging toolkit for modders, version " + Version; + + public void OnEnabled() + { + try + { + if (mainObject != null) + { + return; + } + + CODebugBase.verbose = true; + CODebugBase.EnableChannels(LogChannel.All); + + mainObject = new GameObject(ModToolsName); + UnityEngine.Object.DontDestroyOnLoad(mainObject); + + var modTools = mainObject.AddComponent(); + modTools.Initialize(); + } + catch (Exception e) + { + DebugOutputPanel.AddMessage(PluginManager.MessageType.Error, e.Message); + } + } + + public void OnDisabled() + { + if (mainObject != null) + { + CODebugBase.verbose = false; + UnityEngine.Object.Destroy(mainObject); + mainObject = null; + } + } + } +} \ No newline at end of file diff --git a/Debugger/ModalUI.cs b/Debugger/ModalUI.cs new file mode 100644 index 0000000..a80a3ac --- /dev/null +++ b/Debugger/ModalUI.cs @@ -0,0 +1,49 @@ +using ColossalFramework.UI; + +namespace ModTools +{ + internal enum MouseButtonState + { + None, + Pressed, + Held, + Released, + } + + internal sealed class ModalUI + { + private UIComponent modalView; + private bool isModal; + + public void Update(bool mouseOverWindow, MouseButtonState middleButtonState) + { + if (modalView == null) + { + modalView = UIView.GetAView()?.AddUIComponent(typeof(UILabel)); + if (modalView == null) + { + return; + } + } + + if (middleButtonState != MouseButtonState.None) + { + return; + } + + if (mouseOverWindow) + { + if (!isModal) + { + isModal = true; + UIView.PushModal(modalView); + } + } + else if (isModal && UIView.GetModalComponent() == modalView) + { + isModal = false; + UIView.PopModal(); + } + } + } +} diff --git a/Debugger/Plopper.cs b/Debugger/Plopper.cs deleted file mode 100644 index 8ccfe1d..0000000 --- a/Debugger/Plopper.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System; -using System.Reflection; -using ColossalFramework; -using UnityEngine; -using Object = UnityEngine.Object; - -namespace ModTools.Explorer -{ - public static class Plopper - { - private static PrefabInfo ploppedPrefab; - - public static void Reset() - { - ploppedPrefab = null; - } - - public static void Update() - { - if (ploppedPrefab == null) - { - return; - } - var toolManager = Singleton.instance; - if (toolManager?.m_properties == null) - { - return; - } - if (Input.GetKeyDown(KeyCode.Escape)) - { - Singleton.instance.m_properties.CurrentTool = ToolsModifierControl.GetTool(); - ploppedPrefab = null; - return; - - } - var currentTool = toolManager.m_properties.CurrentTool; - if (currentTool == null) - { - return; - } - if (currentTool is BuildingTool || currentTool is NetTool || currentTool is TreeTool || - currentTool is PropTool) - { - var prefabField = currentTool.GetType() - .GetField("m_prefab", BindingFlags.Instance | BindingFlags.Public); - if (prefabField != null) - { - var prefab = prefabField.GetValue(currentTool); - if ((PrefabInfo)prefab != ploppedPrefab) - { - ploppedPrefab = null; - } - } - else - { - ploppedPrefab = null; - } - } - else - { - ploppedPrefab = null; - } - } - - public static void StartPlopping(PrefabInfo prefabInfo) - { - var currentTool = Singleton.instance.m_properties.CurrentTool; - if (currentTool == null) - { - return; - } - - Type toolType; - if (prefabInfo is BuildingInfo) - { - toolType = typeof(BuildingTool); - } - else if (prefabInfo is NetInfo) - { - toolType = typeof(NetTool); - } - else if (prefabInfo is PropInfo) - { - toolType = typeof(PropTool); - } - else if (prefabInfo is TreeInfo) - { - toolType = typeof(TreeTool); - } - else - { - toolType = null; - } - if (toolType == null || (currentTool.GetType() != toolType && !(currentTool is DefaultTool))) - { - return; - } - if (prefabInfo is BuildingInfo) - { - var buildingInfo = (BuildingInfo)prefabInfo; - var buildingTool = ToolsModifierControl.GetTool(); - if (buildingTool == null) - { - Log.Warning("BuildingTool not found!"); - return; - } - Singleton.instance.m_properties.CurrentTool = buildingTool; - ploppedPrefab = buildingTool.m_prefab = buildingInfo; - buildingTool.m_relocate = 0; - } - else if (prefabInfo is NetInfo) - { - var netInfo = (NetInfo)prefabInfo; - var netTool = ToolsModifierControl.GetTool(); - if (netTool == null) - { - Log.Warning("NetTool not found!"); - return; - } - Singleton.instance.m_properties.CurrentTool = netTool; - ploppedPrefab = netTool.m_prefab = netInfo; - } - else if (prefabInfo is PropInfo) - { - var propInfo = (PropInfo)prefabInfo; - var propTool = ToolsModifierControl.GetTool(); - if (propTool == null) - { - Log.Warning("PropTool not found!"); - return; - } - Singleton.instance.m_properties.CurrentTool = propTool; - ploppedPrefab = propTool.m_prefab = propInfo; - } - else if (prefabInfo is TreeInfo) - { - var treeInfo = (TreeInfo)prefabInfo; - var treeTool = ToolsModifierControl.GetTool(); - if (treeTool == null) - { - Log.Warning("TreeTool not found!"); - return; - } - Singleton.instance.m_properties.CurrentTool = treeTool; - ploppedPrefab = treeTool.m_prefab = treeInfo; - } - } - } -} \ No newline at end of file diff --git a/Debugger/Properties/AssemblyInfo.cs b/Debugger/Properties/AssemblyInfo.cs index 8f9b076..456f62d 100644 --- a/Debugger/Properties/AssemblyInfo.cs +++ b/Debugger/Properties/AssemblyInfo.cs @@ -1,36 +1,13 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Debugger")] -[assembly: AssemblyDescription("")] +[assembly: AssemblyTitle("ModTools")] +[assembly: AssemblyDescription("Debugging toolkit for modders of Cities: Skylines")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Debugger")] -[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyProduct("ModTools")] +[assembly: AssemblyCopyright("Copyright © 2019")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("d0e21a0a-b62d-4230-8e13-c4af31010315")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: ComVisible(false)] \ No newline at end of file diff --git a/Debugger/ReferenceChain.cs b/Debugger/ReferenceChain.cs deleted file mode 100644 index b6e9c44..0000000 --- a/Debugger/ReferenceChain.cs +++ /dev/null @@ -1,321 +0,0 @@ -using System; -using System.Collections; -using System.Linq; -using System.Reflection; -using ModTools.Explorer; -using UnityEngine; - -namespace ModTools -{ - - public class ReferenceChain - { - public enum ReferenceType - { - GameObject = 0, - Component = 1, - Field = 2, - Property = 3, - Method = 4, - EnumerableItem = 5, - SpecialNamedProperty = 6 - } - - public object[] ChainObjects = new object[ModTools.Instance.config.sceneExplorerMaxHierarchyDepth]; - public ReferenceType[] ChainTypes = new ReferenceType[ModTools.Instance.config.sceneExplorerMaxHierarchyDepth]; - public int IdentOffset = 0; - - public int Ident => Length + IdentOffset - 1; - - public int Length { get; private set; } - - public object LastItem => ChainObjects[Length - 1]; - - public string LastItemName => ItemToString(Length - 1); - - public ReferenceType LastItemType => ChainTypes[Length - 1]; - - public bool CheckDepth() - { - return Length >= ModTools.Instance.config.sceneExplorerMaxHierarchyDepth; - } - - public ReferenceChain Copy() - { - var copy = new ReferenceChain { Length = Length }; - for (var i = 0; i < Length; i++) - { - copy.ChainObjects[i] = ChainObjects[i]; - copy.ChainTypes[i] = ChainTypes[i]; - } - - copy.IdentOffset = IdentOffset; - - return copy; - } - - public ReferenceChain Add(GameObject go) - { - var copy = Copy(); - copy.ChainObjects[Length] = go; - copy.ChainTypes[Length] = ReferenceType.GameObject; - copy.Length++; - return copy; - } - - public ReferenceChain Add(Component component) - { - var copy = Copy(); - copy.ChainObjects[Length] = component; - copy.ChainTypes[Length] = ReferenceType.Component; - copy.Length++; - return copy; - } - - public ReferenceChain Add(FieldInfo fieldInfo) - { - var copy = Copy(); - copy.ChainObjects[Length] = fieldInfo; - copy.ChainTypes[Length] = ReferenceType.Field; - copy.Length++; - return copy; - } - - public ReferenceChain Add(PropertyInfo propertyInfo) - { - var copy = Copy(); - copy.ChainObjects[Length] = propertyInfo; - copy.ChainTypes[Length] = ReferenceType.Property; - copy.Length++; - return copy; - } - - public ReferenceChain Add(MethodInfo methodInfo) - { - var copy = Copy(); - copy.ChainObjects[Length] = methodInfo; - copy.ChainTypes[Length] = ReferenceType.Method; - copy.Length++; - return copy; - } - - public ReferenceChain Add(int index) - { - var copy = Copy(); - copy.ChainObjects[Length] = index; - copy.ChainTypes[Length] = ReferenceType.EnumerableItem; - copy.Length++; - return copy; - } - - public ReferenceChain Add(string namedProperty) - { - var copy = Copy(); - copy.ChainObjects[Length] = namedProperty; - copy.ChainTypes[Length] = ReferenceType.SpecialNamedProperty; - copy.Length++; - return copy; - } - - public ReferenceChain Trim(int num) - { - var copy = Copy(); - copy.Length = Mathf.Min(num, Length); - return copy; - } - - public override bool Equals(object obj) - { - var other = obj as ReferenceChain; - - if (other?.Length != Length) - { - return false; - } - - for (var i = Length - 1; i >= 0; i--) - { - if (ChainTypes[i] != other.ChainTypes[i]) - { - return false; - } - - if (ChainObjects[i].GetHashCode() != other.ChainObjects[i].GetHashCode()) - { - return false; - } - } - - return true; - } - - public override int GetHashCode() - { - var hash = HashCodeUtil.Initialize(); - - for (var i = 0; i < Length; i++) - { - hash = HashCodeUtil.Hash(hash, ChainTypes[i]); - hash = HashCodeUtil.Hash(hash, ChainObjects[i]); - } - - return hash; - } - - private string ItemToString(int i) - { - switch (ChainTypes[i]) - { - case ReferenceType.GameObject: - return ((GameObject)ChainObjects[i]).name; - case ReferenceType.Component: - return ((Component)ChainObjects[i]).name; - case ReferenceType.Field: - return ((FieldInfo)ChainObjects[i]).Name; - case ReferenceType.Property: - return ((PropertyInfo)ChainObjects[i]).Name; - case ReferenceType.Method: - return ((MethodInfo)ChainObjects[i]).Name; - case ReferenceType.EnumerableItem: - return String.Format("[{0}]", (int)ChainObjects[i]); - case ReferenceType.SpecialNamedProperty: - return (string)ChainObjects[i]; - } - - return ""; - } - - public override string ToString() - { - string result = ""; - - for (int i = 0; i < Length; i++) - { - result += ItemToString(i); - - if (i != Length - 1) - { - result += '.'; - } - } - - return result; - } - - public ReferenceChain Reverse - { - get - { - var copy = new ReferenceChain(); - copy.Length = Length; - copy.IdentOffset = IdentOffset; - for (var i = 0; i < Length; i++) - { - copy.ChainObjects[Length - i - 1] = ChainObjects[i]; - copy.ChainTypes[Length - i - 1] = ChainTypes[i]; - } - return copy; - } - } - - public object Evaluate() - { - object current = null; - for (int i = 0; i < Length; i++) - { - switch (ChainTypes[i]) - { - case ReferenceType.GameObject: - case ReferenceType.Component: - current = ChainObjects[i]; - break; - case ReferenceType.Field: - current = ((FieldInfo)ChainObjects[i]).GetValue(current); - break; - case ReferenceType.Property: - current = ((PropertyInfo)ChainObjects[i]).GetValue(current, null); - break; - case ReferenceType.Method: - break; - case ReferenceType.EnumerableItem: - var collection = current as IEnumerable; - int itemCount = 0; - foreach (var item in collection) - { - if (itemCount == (int)ChainObjects[i]) - { - current = item; - break; - } - - itemCount++; - } - break; - case ReferenceType.SpecialNamedProperty: - break; - } - } - - return current; - } - - public bool SetValue(object value) - { - object current = null; - for (int i = 0; i < Length - 1; i++) - { - switch (ChainTypes[i]) - { - case ReferenceType.GameObject: - case ReferenceType.Component: - current = ChainObjects[i]; - break; - case ReferenceType.Field: - current = ((FieldInfo)ChainObjects[i]).GetValue(current); - break; - case ReferenceType.Property: - current = ((PropertyInfo)ChainObjects[i]).GetValue(current, null); - break; - case ReferenceType.Method: - break; - case ReferenceType.EnumerableItem: - var collection = current as IEnumerable; - int itemCount = 0; - foreach (var item in collection) - { - if (itemCount == (int)ChainObjects[i]) - { - current = item; - break; - } - - itemCount++; - } - break; - case ReferenceType.SpecialNamedProperty: - break; - } - } - - if (LastItemType == ReferenceType.Field) - { - ((FieldInfo)LastItem).SetValue(current, value); - return true; - } - - if (LastItemType == ReferenceType.Property) - { - var propertyInfo = ((PropertyInfo)LastItem); - if (propertyInfo.CanWrite) - { - propertyInfo.SetValue(current, value, null); - } - return true; - } - - return false; - } - - } - -} diff --git a/Debugger/SceneExplorer.cs b/Debugger/SceneExplorer.cs deleted file mode 100644 index 17bdd8d..0000000 --- a/Debugger/SceneExplorer.cs +++ /dev/null @@ -1,546 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Reflection; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using ColossalFramework; -using ColossalFramework.UI; -using ModTools.Explorer; -using ModTools.Utils; -using UnityEngine; - -namespace ModTools -{ - - public class SceneExplorer : GUIWindow - { - public Dictionary sceneRoots = new Dictionary(); - private string findGameObjectFilter = ""; - private string findObjectTypeFilter = ""; - private string searchDisplayString = ""; - - private GUIArea headerArea; - private GUIArea sceneTreeArea; - private GUIArea componentArea; - - private Vector2 sceneTreeScrollPosition = Vector2.zero; - private Vector2 componentScrollPosition = Vector2.zero; - private SceneExplorerState state; - - - private float windowTopMargin = 16.0f; - private float windowBottomMargin = 8.0f; - - private float headerHeightCompact = 1.65f; - private float headerHeightExpanded = 17.0f; - private bool headerExpanded = false; - - private float sceneTreeWidth = 320.0f; - - - - public SceneExplorer() - : base("Scene Explorer", new Rect(128, 440, 800, 500), skin) - { - onDraw = DrawWindow; - onException = ExceptionHandler; - onUnityGUI = GUIComboBox.DrawGUI; - - headerArea = new GUIArea(this); - sceneTreeArea = new GUIArea(this); - componentArea = new GUIArea(this); - state = new SceneExplorerState(); - - RecalculateAreas(); - } - - public void Awake() - { - Plopper.Reset(); - } - - public void Update() - { - Plopper.Update(); - } - - public void RecalculateAreas() - { - headerArea.absolutePosition.y = windowTopMargin; - headerArea.relativeSize.x = 1.0f; - - if (rect.width < (float)Screen.width / 4.0f && state.currentRefChain != null) - { - sceneTreeArea.relativeSize = Vector2.zero; - sceneTreeArea.relativeSize = Vector2.zero; - - componentArea.absolutePosition.x = 0.0f; - componentArea.relativeSize.x = 1.0f; - componentArea.relativeSize.y = 1.0f; - componentArea.absoluteSize.x = 0.0f; - } - else - { - sceneTreeArea.relativeSize.y = 1.0f; - sceneTreeArea.absoluteSize.x = sceneTreeWidth; - - componentArea.absolutePosition.x = sceneTreeWidth; - componentArea.relativeSize.x = 1.0f; - componentArea.relativeSize.y = 1.0f; - componentArea.absoluteSize.x = -sceneTreeWidth; - } - - float headerHeight = (headerExpanded ? headerHeightExpanded : headerHeightCompact); - headerHeight *= ModTools.Instance.config.fontSize; - headerHeight += 32.0f; - - headerArea.absoluteSize.y = headerHeight - windowTopMargin; - sceneTreeArea.absolutePosition.y = headerHeight - windowTopMargin; - sceneTreeArea.absoluteSize.y = -(headerHeight - windowTopMargin) - windowBottomMargin; - componentArea.absolutePosition.y = headerHeight - windowTopMargin; - componentArea.absoluteSize.y = -(headerHeight - windowTopMargin) - windowBottomMargin; - } - - void ExceptionHandler(Exception ex) - { - Debug.LogException(ex); - state = new SceneExplorerState(); - sceneRoots = GameObjectUtil.FindSceneRoots(); - TypeUtil.ClearTypeCache(); - } - - public void Refresh() - { - sceneRoots = GameObjectUtil.FindSceneRoots(); - TypeUtil.ClearTypeCache(); - } - - public void ExpandFromRefChain(ReferenceChain refChain) - { - if (refChain == null) - { - Log.Error("SceneExplorer: ExpandFromRefChain(): Null refChain"); - return; - } - if (refChain.Length == 0) - { - Log.Error("SceneExplorer: ExpandFromRefChain(): Invalid refChain, expected Length >= 0"); - return; - } - - if (refChain.ChainTypes[0] != ReferenceChain.ReferenceType.GameObject) - { - Log.Error(String.Format("SceneExplorer: ExpandFromRefChain(): invalid chain type for element [0] - expected {0}, got {1}", - ReferenceChain.ReferenceType.GameObject, refChain.ChainTypes[0])); - return; - } - - sceneRoots.Clear(); - ClearExpanded(); - searchDisplayString = String.Format("Showing results for \"{0}\"", refChain.ToString()); - - var rootGameObject = (GameObject)refChain.ChainObjects[0]; - sceneRoots.Add(rootGameObject, true); - - var expandedRefChain = new ReferenceChain().Add(rootGameObject); - state.expandedGameObjects.Add(expandedRefChain, true); - - for (int i = 1; i < refChain.Length; i++) - { - switch (refChain.ChainTypes[i]) - { - case ReferenceChain.ReferenceType.GameObject: - var go = (GameObject)refChain.ChainObjects[i]; - expandedRefChain = expandedRefChain.Add(go); - state.expandedGameObjects.Add(expandedRefChain, true); - break; - case ReferenceChain.ReferenceType.Component: - var component = (Component)refChain.ChainObjects[i]; - expandedRefChain = expandedRefChain.Add(component); - state.expandedComponents.Add(expandedRefChain, true); - break; - case ReferenceChain.ReferenceType.Field: - var field = (FieldInfo)refChain.ChainObjects[i]; - expandedRefChain = expandedRefChain.Add(field); - state.expandedObjects.Add(expandedRefChain, true); - break; - case ReferenceChain.ReferenceType.Property: - var property = (PropertyInfo)refChain.ChainObjects[i]; - expandedRefChain = expandedRefChain.Add(property); - state.expandedObjects.Add(expandedRefChain, true); - break; - case ReferenceChain.ReferenceType.Method: - break; - case ReferenceChain.ReferenceType.EnumerableItem: - var index = (int)refChain.ChainObjects[i]; - state.selectedArrayStartIndices[expandedRefChain] = index; - state.selectedArrayEndIndices[expandedRefChain] = index; - expandedRefChain = expandedRefChain.Add(index); - state.expandedObjects.Add(expandedRefChain, true); - break; - } - } - - state.currentRefChain = refChain.Copy(); - state.currentRefChain.IdentOffset = -state.currentRefChain.Length; - } - - public void DrawHeader() - { - headerArea.Begin(); - - if (headerExpanded) - { - DrawExpandedHeader(); - } - else - { - DrawCompactHeader(); - } - - headerArea.End(); - } - - public void DrawCompactHeader() - { - GUILayout.BeginHorizontal(); - - if (GUILayout.Button("▼", GUILayout.ExpandWidth(false))) - { - headerExpanded = true; - RecalculateAreas(); - } - - if (GUILayout.Button("Refresh", GUILayout.ExpandWidth(false))) - { - Refresh(); - } - - if (GUILayout.Button("Fold all/ Clear", GUILayout.ExpandWidth(false))) - { - ClearExpanded(); - Refresh(); - } - - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - } - - public void DrawExpandedHeader() - { - GUILayout.BeginHorizontal(); - - GUI.contentColor = Color.green; - GUILayout.Label("Show:", GUILayout.ExpandWidth(false)); - GUI.contentColor = Color.white; - - GUILayout.Label("Fields"); - var showFields = GUILayout.Toggle(ModTools.Instance.config.sceneExplorerShowFields, ""); - if (ModTools.Instance.config.sceneExplorerShowFields != showFields) - { - ModTools.Instance.config.sceneExplorerShowFields = showFields; - ModTools.Instance.SaveConfig(); - } - - GUILayout.Label("Properties"); - var showProperties = GUILayout.Toggle(ModTools.Instance.config.sceneExplorerShowProperties, ""); - if (ModTools.Instance.config.sceneExplorerShowProperties != showProperties) - { - ModTools.Instance.config.sceneExplorerShowProperties = showProperties; - ModTools.Instance.SaveConfig(); - } - - GUILayout.Label("Methods"); - var showMethods = GUILayout.Toggle(ModTools.Instance.config.sceneExplorerShowMethods, ""); - if (ModTools.Instance.config.sceneExplorerShowMethods != showMethods) - { - ModTools.Instance.config.sceneExplorerShowMethods = showMethods; - ModTools.Instance.SaveConfig(); - } - - GUILayout.FlexibleSpace(); - - GUILayout.EndHorizontal(); - - GUILayout.BeginHorizontal(); - if (GUILayout.Button("Configure font & colors", GUILayout.ExpandWidth(false))) - { - ModTools.Instance.sceneExplorerColorConfig.visible = true; - ModTools.Instance.sceneExplorerColorConfig.rect.position = rect.position + new Vector2(32.0f, 32.0f); - } - - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - - GUILayout.BeginHorizontal(); - GUI.contentColor = Color.green; - GUILayout.Label("Show field/ property modifiers:", GUILayout.ExpandWidth(false)); - var showModifiers = GUILayout.Toggle(ModTools.Instance.config.sceneExplorerShowModifiers, ""); - if (showModifiers != ModTools.Instance.config.sceneExplorerShowModifiers) - { - ModTools.Instance.config.sceneExplorerShowModifiers = showModifiers; - ModTools.Instance.SaveConfig(); - } - - GUI.contentColor = Color.white; - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - - GUILayout.BeginHorizontal(); - GUI.contentColor = Color.green; - GUILayout.Label("Show inherited members:", GUILayout.ExpandWidth(false)); - var showInheritedMembers = GUILayout.Toggle(ModTools.Instance.config.sceneExplorerShowInheritedMembers, ""); - if (showInheritedMembers != ModTools.Instance.config.sceneExplorerShowInheritedMembers) - { - ModTools.Instance.config.sceneExplorerShowInheritedMembers = showInheritedMembers; - ModTools.Instance.SaveConfig(); - TypeUtil.ClearTypeCache(); - } - - GUI.contentColor = Color.white; - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - - GUILayout.BeginHorizontal(); - GUI.contentColor = Color.green; - GUILayout.Label("Evaluate properties automatically:", GUILayout.ExpandWidth(false)); - var evaluatePropertiesAutomatically = GUILayout.Toggle(ModTools.Instance.config.sceneExplorerEvaluatePropertiesAutomatically, ""); - if (evaluatePropertiesAutomatically != ModTools.Instance.config.sceneExplorerEvaluatePropertiesAutomatically) - { - ModTools.Instance.config.sceneExplorerEvaluatePropertiesAutomatically = evaluatePropertiesAutomatically; - ModTools.Instance.SaveConfig(); - } - - GUI.contentColor = Color.white; - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - - GUILayout.BeginHorizontal(); - GUI.contentColor = Color.green; - GUILayout.Label("Sort alphabetically:", GUILayout.ExpandWidth(false)); - GUI.contentColor = Color.white; - var sortAlphabetically = GUILayout.Toggle(ModTools.Instance.config.sceneExplorerSortAlphabetically, ""); - if (sortAlphabetically != ModTools.Instance.config.sceneExplorerSortAlphabetically) - { - ModTools.Instance.config.sceneExplorerSortAlphabetically = sortAlphabetically; - ModTools.Instance.SaveConfig(); - } - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - - DrawFindGameObjectPanel(); - - GUILayout.BeginHorizontal(); - - if (GUILayout.Button("▲", GUILayout.ExpandWidth(false))) - { - headerExpanded = false; - RecalculateAreas(); - } - - if (GUILayout.Button("Refresh", GUILayout.ExpandWidth(false))) - { - Refresh(); - } - - if (GUILayout.Button("Fold all/ Clear", GUILayout.ExpandWidth(false))) - { - ClearExpanded(); - Refresh(); - } - - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - } - - void DrawFindGameObjectPanel() - { - GUILayout.BeginHorizontal(); - GUILayout.Label("GameObject.Find"); - findGameObjectFilter = GUILayout.TextField(findGameObjectFilter, GUILayout.Width(256)); - - if (findGameObjectFilter.Trim().Length == 0) - { - GUI.enabled = false; - } - - if (GUILayout.Button("Find")) - { - ClearExpanded(); - var go = GameObject.Find(findGameObjectFilter.Trim()); - if (go != null) - { - sceneRoots.Clear(); - state.expandedGameObjects.Add(new ReferenceChain().Add(go), true); - sceneRoots.Add(go, true); - sceneTreeScrollPosition = Vector2.zero; - searchDisplayString = String.Format("Showing results for GameObject.Find(\"{0}\")", findGameObjectFilter); - } - } - - if (GUILayout.Button("Reset")) - { - ClearExpanded(); - sceneRoots = GameObjectUtil.FindSceneRoots(); - sceneTreeScrollPosition = Vector2.zero; - searchDisplayString = ""; - } - - GUI.enabled = true; - - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - - GUILayout.BeginHorizontal(); - GUILayout.Label("GameObject.FindObjectsOfType"); - findObjectTypeFilter = GUILayout.TextField(findObjectTypeFilter, GUILayout.Width(256)); - - if (findObjectTypeFilter.Trim().Length == 0) - { - GUI.enabled = false; - } - - if (GUILayout.Button("Find")) - { - var gameObjects = GameObjectUtil.FindComponentsOfType(findObjectTypeFilter.Trim()); - - sceneRoots.Clear(); - foreach (var item in gameObjects) - { - ClearExpanded(); - state.expandedGameObjects.Add(new ReferenceChain().Add(item.Key), true); - if (gameObjects.Count == 1) - { - state.expandedComponents.Add(new ReferenceChain().Add(item.Key).Add(item.Value), true); - } - sceneRoots.Add(item.Key, true); - sceneTreeScrollPosition = Vector2.zero; - searchDisplayString = String.Format("Showing results for GameObject.FindObjectsOfType({0})", findObjectTypeFilter); - } - } - - if (GUILayout.Button("Reset")) - { - ClearExpanded(); - sceneRoots = GameObjectUtil.FindSceneRoots(); - sceneTreeScrollPosition = Vector2.zero; - searchDisplayString = ""; - } - - GUI.enabled = true; - - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - } - - public void DrawSceneTree() - { - sceneTreeArea.Begin(); - - if (searchDisplayString != "") - { - GUI.contentColor = Color.green; - GUILayout.Label(searchDisplayString); - GUI.contentColor = Color.white; - } - - sceneTreeScrollPosition = GUILayout.BeginScrollView(sceneTreeScrollPosition); - - var gameObjects = sceneRoots.Keys.ToArray(); - - if (ModTools.Instance.config.sceneExplorerSortAlphabetically) - { - try - { - Array.Sort(gameObjects, (o, o1) => - { - if (o.name == null) - { - return 1; - } - - if (o1.name == null) - { - return -1; - } - - return o.name.CompareTo(o1.name); - }); - } - catch (Exception e) - { - UnityEngine.Debug.LogException(e); - } - } - - foreach (var obj in gameObjects) - { - GUIRecursiveTree.OnSceneTreeRecursive(gameObject, state, new ReferenceChain().Add(obj), obj); - } - - GUILayout.EndScrollView(); - - sceneTreeArea.End(); - } - - public void DrawComponent() - { - componentArea.Begin(); - - componentScrollPosition = GUILayout.BeginScrollView(componentScrollPosition); - - if (state.currentRefChain != null) - { - try - { - GUIReflect.OnSceneTreeReflect(state, state.currentRefChain, state.currentRefChain.Evaluate()); - } - catch (Exception e) - { - UnityEngine.Debug.LogException(e); - state.currentRefChain = null; - throw; - } - } - - GUILayout.EndScrollView(); - - componentArea.End(); - } - - public void DrawWindow() - { - RecalculateAreas(); - - bool enterPressed = Event.current.type == EventType.KeyDown && (Event.current.keyCode == KeyCode.Return || Event.current.keyCode == KeyCode.KeypadEnter); - - if (enterPressed) - { - GUI.FocusControl(null); - } - - state.preventCircularReferences.Clear(); - - DrawHeader(); - DrawSceneTree(); - DrawComponent(); - } - - private void ClearExpanded() - { - state.expandedGameObjects.Clear(); - state.expandedComponents.Clear(); - state.expandedObjects.Clear(); - state.evaluatedProperties.Clear(); - state.selectedArrayStartIndices.Clear(); - state.selectedArrayEndIndices.Clear(); - searchDisplayString = ""; - sceneTreeScrollPosition = Vector2.zero; - state.currentRefChain = null; - TypeUtil.ClearTypeCache(); - } - } - -} \ No newline at end of file diff --git a/Debugger/SceneExplorerColorConfig.cs b/Debugger/SceneExplorerColorConfig.cs deleted file mode 100644 index 0a323f8..0000000 --- a/Debugger/SceneExplorerColorConfig.cs +++ /dev/null @@ -1,163 +0,0 @@ -using System; -using UnityEngine; - -namespace ModTools -{ - public class SceneExplorerColorConfig : GUIWindow - { - - private static Configuration config - { - get { return ModTools.Instance.config; } - } - - private string[] availableFonts; - private int selectedFont; - - public SceneExplorerColorConfig() : base("Font/ color configuration", new Rect(16.0f, 16.0f, 600.0f, 490.0f), skin) - { - onDraw = DrawWindow; - onException = HandleException; - visible = false; - resizable = false; - - availableFonts = Font.GetOSInstalledFontNames(); - int c = 0; - var configFont = config.fontName; - - foreach (var font in availableFonts) - { - if (font == configFont) - { - selectedFont = c; - break; - } - - c++; - } - } - - void DrawColorControl(string name, ref Color value, ColorPicker.OnColorChanged onColorChanged) - { - GUILayout.BeginHorizontal(); - GUILayout.Label(name); - GUILayout.FlexibleSpace(); - GUIControls.ColorField(name, "", ref value, 0.0f, null, true, true, onColorChanged); - GUILayout.EndHorizontal(); - } - - void DrawWindow() - { - var config = ModTools.Instance.config; - - GUILayout.BeginHorizontal(); - GUILayout.Label("Font"); - - var newSelectedFont = GUIComboBox.Box(selectedFont, availableFonts, "SceneExplorerColorConfigFontsComboBox"); - if (newSelectedFont != selectedFont) - { - config.fontName = availableFonts[newSelectedFont]; - selectedFont = newSelectedFont; - UpdateFont(); - } - - GUILayout.EndHorizontal(); - GUILayout.BeginHorizontal(); - - GUILayout.Label("Font size"); - - var newFontSize = (int)GUILayout.HorizontalSlider((float) config.fontSize, 13.0f, 39.0f, GUILayout.Width(256)); - - if (newFontSize != config.fontSize) - { - config.fontSize = newFontSize; - UpdateFont(); - } - - GUILayout.EndHorizontal(); - - DrawColorControl("Background", ref config.backgroundColor, color => - { - config.backgroundColor = color; - bgTexture.SetPixel(0, 0, config.backgroundColor); - bgTexture.Apply(); - }); - - DrawColorControl("Titlebar", ref config.titlebarColor, color => - { - config.titlebarColor = color; - moveNormalTexture.SetPixel(0, 0, config.titlebarColor); - moveNormalTexture.Apply(); - - moveHoverTexture.SetPixel(0, 0, config.titlebarColor * 1.2f); - moveHoverTexture.Apply(); - }); - - DrawColorControl("Titlebar text", ref config.titlebarTextColor, color => - { - config.titlebarTextColor = color; - }); - - DrawColorControl("GameObject", ref config.gameObjectColor, color => config.gameObjectColor = color); - DrawColorControl("Component (enabled)", ref config.enabledComponentColor, color => config.enabledComponentColor = color); - DrawColorControl("Component (disabled)", ref config.disabledComponentColor, color => config.disabledComponentColor = color); - DrawColorControl("Selected component", ref config.selectedComponentColor, color => config.selectedComponentColor = color); - DrawColorControl("Keyword", ref config.keywordColor, color => config.keywordColor = color); - DrawColorControl("Member name", ref config.nameColor, color => config.nameColor = color); - DrawColorControl("Member type", ref config.typeColor, color => config.typeColor = color); - DrawColorControl("Member modifier", ref config.modifierColor, color => config.modifierColor = color); - DrawColorControl("Field type", ref config.memberTypeColor, color => config.memberTypeColor = color); - DrawColorControl("Member value", ref config.valueColor, color => config.valueColor = color); - - GUILayout.BeginHorizontal(); - - if (GUILayout.Button("Save", GUILayout.Width(128))) - { - ModTools.Instance.SaveConfig(); - } - - if (GUILayout.Button("Reset", GUILayout.Width(128))) - { - var template = new Configuration(); - - config.backgroundColor = template.backgroundColor; - bgTexture.SetPixel(0, 0, config.backgroundColor); - bgTexture.Apply(); - - config.titlebarColor = template.titlebarColor; - moveNormalTexture.SetPixel(0, 0, config.titlebarColor); - moveNormalTexture.Apply(); - - moveHoverTexture.SetPixel(0, 0, config.titlebarColor * 1.2f); - moveHoverTexture.Apply(); - - config.titlebarTextColor = template.titlebarTextColor; - - config.gameObjectColor = template.gameObjectColor; - config.enabledComponentColor = template.enabledComponentColor; - config.disabledComponentColor = template.disabledComponentColor; - config.selectedComponentColor = template.selectedComponentColor; - config.nameColor = template.nameColor; - config.typeColor = template.typeColor; - config.modifierColor = template.modifierColor; - config.memberTypeColor = template.memberTypeColor; - config.valueColor = template.valueColor; - config.fontName = template.fontName; - config.fontSize = template.fontSize; - - UpdateFont(); - ModTools.Instance.SaveConfig(); - } - - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - } - - void HandleException(Exception ex) - { - Log.Error("Exception in SceneExplorerColorConfig - " + ex.Message); - visible = false; - } - - } -} diff --git a/Debugger/SceneExplorerState.cs b/Debugger/SceneExplorerState.cs deleted file mode 100644 index 0ca3205..0000000 --- a/Debugger/SceneExplorerState.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Collections; -using System.Collections.Generic; - -namespace ModTools -{ - public class SceneExplorerState - { - public Dictionary expandedGameObjects = new Dictionary(); - public Dictionary expandedComponents = new Dictionary(); - public Dictionary expandedObjects = new Dictionary(); - public Dictionary evaluatedProperties = new Dictionary(); - public Dictionary selectedArrayStartIndices = new Dictionary(); - public Dictionary selectedArrayEndIndices = new Dictionary(); - public List preventCircularReferences = new List(); - public ReferenceChain currentRefChain = null; - } -} \ No newline at end of file diff --git a/Debugger/IModEntryPoint.cs b/Debugger/Scripting/IModEntryPoint.cs similarity index 76% rename from Debugger/IModEntryPoint.cs rename to Debugger/Scripting/IModEntryPoint.cs index 48ce72c..0fb8f1e 100644 --- a/Debugger/IModEntryPoint.cs +++ b/Debugger/Scripting/IModEntryPoint.cs @@ -1,4 +1,4 @@ -namespace ModTools +namespace ModTools.Scripting { public interface IModEntryPoint { @@ -6,4 +6,4 @@ public interface IModEntryPoint void OnModUnloaded(); } -} +} \ No newline at end of file diff --git a/Debugger/ScriptCompiler.cs b/Debugger/Scripting/ScriptCompiler.cs similarity index 61% rename from Debugger/ScriptCompiler.cs rename to Debugger/Scripting/ScriptCompiler.cs index ac35270..8b50b3f 100644 --- a/Debugger/ScriptCompiler.cs +++ b/Debugger/Scripting/ScriptCompiler.cs @@ -4,55 +4,43 @@ using System.Linq; using System.Reflection; using ColossalFramework.Plugins; +using ModTools.Utils; using UnityEngine; using Random = UnityEngine.Random; -namespace ModTools +namespace ModTools.Scripting { - public static class ScriptCompiler + internal static class ScriptCompiler { + private static readonly string WorkspacePath = Path.Combine(Application.temporaryCachePath, "ModTools"); + private static readonly string SourcesPath = Path.Combine(WorkspacePath, "src"); + private static readonly string DllsPath = Path.Combine(WorkspacePath, "dll"); - private static string workspacePath; - private static string sourcesPath; - private static string dllsPath; - - private static readonly string[] gameAssemblies = + private static readonly string[] GameAssemblies = { "Assembly-CSharp.dll", "ICities.dll", "ColossalManaged.dll", - "UnityEngine.dll" + "UnityEngine.dll", }; static ScriptCompiler() { - var tempPath = Application.temporaryCachePath; - workspacePath = Path.Combine(tempPath, "ModTools"); - if (!Directory.Exists(workspacePath)) + if (!Directory.Exists(WorkspacePath)) { - Directory.CreateDirectory(workspacePath); + Directory.CreateDirectory(WorkspacePath); } - ClearFolder(workspacePath); - - sourcesPath = Path.Combine(workspacePath, "src"); - Directory.CreateDirectory(sourcesPath); - - dllsPath = Path.Combine(workspacePath, "dll"); - Directory.CreateDirectory(dllsPath); - - PluginManager.eventLogMessage += (type, message) => - { - // Log.Error(String.Format("PluginManager error ({0}) - {1}", type, message)); - }; + ClearFolder(WorkspacePath); + Directory.CreateDirectory(SourcesPath); + Directory.CreateDirectory(DllsPath); } public static bool RunSource(List sources, out string errorMessage, out IModEntryPoint modInstance) { modInstance = null; - string dllPath; - if (CompileSource(sources, out dllPath)) + if (CompileSource(sources, out var dllPath)) { var assembly = Assembly.LoadFile(dllPath); @@ -63,7 +51,7 @@ public static bool RunSource(List sources, out string errorMes } Type entryPointType = null; - foreach (Type type in assembly.GetTypes()) + foreach (var type in assembly.GetTypes()) { if (typeof(IModEntryPoint).IsAssignableFrom(type)) { @@ -97,25 +85,25 @@ public static bool RunSource(List sources, out string errorMes public static bool CompileSource(List sources, out string dllPath) { - var name = String.Format("tmp_{0}", Random.Range(0, int.MaxValue)); + var name = $"tmp_{Random.Range(0, int.MaxValue)}"; - var sourcePath = Path.Combine(sourcesPath, name); + var sourcePath = Path.Combine(SourcesPath, name); Directory.CreateDirectory(sourcePath); - var outputPath = Path.Combine(dllsPath, name); + var outputPath = Path.Combine(DllsPath, name); Directory.CreateDirectory(outputPath); foreach (var file in sources) { - var sourceFilePath = Path.Combine(sourcePath, Path.GetFileName(file.path)); - File.WriteAllText(sourceFilePath, file.source); + var sourceFilePath = Path.Combine(sourcePath, Path.GetFileName(file.Path)); + File.WriteAllText(sourceFilePath, file.Source); } dllPath = Path.Combine(outputPath, Path.GetFileName(outputPath) + ".dll"); - var modToolsAssembly = FileUtil.FindPluginPath(typeof(Mod)); - var additionalAssemblies = gameAssemblies.Concat(new[]{modToolsAssembly}).ToArray(); + var modToolsAssembly = FileUtil.FindPluginPath(typeof(ModToolsMod)); + var additionalAssemblies = GameAssemblies.Concat(new[] { modToolsAssembly }).ToArray(); PluginManager.CompileSourceInFolder(sourcePath, outputPath, additionalAssemblies); return File.Exists(dllPath); @@ -123,20 +111,18 @@ public static bool CompileSource(List sources, out string dllP private static void ClearFolder(string path) { - DirectoryInfo directory = new DirectoryInfo(path); + var directory = new DirectoryInfo(path); - foreach (FileInfo file in directory.GetFiles()) + foreach (var file in directory.GetFiles()) { file.Delete(); } - foreach (DirectoryInfo dir in directory.GetDirectories()) + foreach (var dir in directory.GetDirectories()) { ClearFolder(dir.FullName); dir.Delete(); } } - } - -} +} \ No newline at end of file diff --git a/Debugger/ScriptEditor.cs b/Debugger/Scripting/ScriptEditor.cs similarity index 52% rename from Debugger/ScriptEditor.cs rename to Debugger/Scripting/ScriptEditor.cs index 459ec0a..ec9f320 100644 --- a/Debugger/ScriptEditor.cs +++ b/Debugger/Scripting/ScriptEditor.cs @@ -1,95 +1,71 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using ICities; +using ModTools.UI; +using ModTools.Utils; using UnityEngine; -namespace ModTools +namespace ModTools.Scripting { - - public class ScriptEditorFile + internal sealed class ScriptEditor : GUIWindow { - public string source = ""; - public string path = ""; - public FileSystemWatcher filesystemWatcher = null; - } - - public class ScriptEditor : GUIWindow - { - - private readonly string textAreaControlName = "ModToolsScriptEditorTextArea"; - + private const string TextAreaControlName = "ModToolsScriptEditorTextArea"; private const string ExampleScriptFileName = "ExampleScript.cs"; - private IModEntryPoint currentMod; - private string lastError = ""; + private const float HeaderHeight = 120.0f; + private const float FooterHeight = 60.0f; - private float compactHeaderHeight = 50.0f; - private float expandedHeaderHeight = 120.0f; - private float footerHeight = 60.0f; + private readonly List projectFiles = new List(); - private bool headerExpanded = true; + private readonly GUIArea headerArea; + private readonly GUIArea editorArea; + private readonly GUIArea footerArea; - private GUIArea headerArea; - private GUIArea editorArea; - private GUIArea footerArea; + private IModEntryPoint currentMod; + private string lastError = string.Empty; private Vector2 editorScrollPosition = Vector2.zero; private Vector2 projectFilesScrollPosition = Vector2.zero; - private string projectWorkspacePath = ""; - private List projectFiles = new List(); - private ScriptEditorFile currentFile = null; + private string projectWorkspacePath = string.Empty; - public ScriptEditor() : base("Script Editor", new Rect(16.0f, 16.0f, 640.0f, 480.0f), skin) - { - onDraw = DrawWindow; - onException = HandleException; - visible = true; - - headerArea = new GUIArea(this); - editorArea = new GUIArea(this); - footerArea = new GUIArea(this); - RecalculateAreas(); - } + private ScriptEditorFile currentFile; - void RecalculateAreas() + public ScriptEditor() + : base("Script Editor", new Rect(16.0f, 16.0f, 640.0f, 480.0f), Skin) { - float headerHeight = headerExpanded ? expandedHeaderHeight : compactHeaderHeight; - - headerArea.absolutePosition.y = 32.0f; - headerArea.relativeSize.x = 1.0f; - headerArea.absoluteSize.y = headerHeight; - - editorArea.absolutePosition.y = 32.0f + headerHeight; - editorArea.relativeSize.x = 1.0f; - editorArea.relativeSize.y = 1.0f; - editorArea.absoluteSize.y = -(32.0f + headerHeight + footerHeight); - - footerArea.relativePosition.y = 1.0f; - footerArea.absolutePosition.y = -footerHeight; - footerArea.absoluteSize.y = footerHeight; - footerArea.relativeSize.x = 1.0f; + headerArea = new GUIArea(this) + .OffsetBy(vertical: 32f) + .ChangeSizeRelative(height: 0) + .ChangeSizeBy(height: HeaderHeight); + + editorArea = new GUIArea(this) + .OffsetBy(vertical: 32.0f + HeaderHeight) + .ChangeSizeBy(height: -(32.0f + HeaderHeight + FooterHeight)); + + footerArea = new GUIArea(this) + .OffsetRelative(vertical: 1f) + .OffsetBy(vertical: -FooterHeight) + .ChangeSizeRelative(height: 0) + .ChangeSizeBy(height: FooterHeight); } public void ReloadProjectWorkspace() { - projectWorkspacePath = ModTools.Instance.config.scriptEditorWorkspacePath; + projectWorkspacePath = MainWindow.Instance.Config.ScriptWorkspacePath; if (projectWorkspacePath.Length == 0) { lastError = "Invalid project workspace path"; return; } - bool exampleFileExists = false; + var exampleFileExists = false; projectFiles.Clear(); try { - var files = FileUtil.ListFilesInDirectory(projectWorkspacePath); - foreach (var file in files) + foreach (var file in FileUtil.ListFilesInDirectory(projectWorkspacePath)) { if (Path.GetExtension(file) == ".cs") { @@ -98,17 +74,13 @@ public void ReloadProjectWorkspace() exampleFileExists = true; } - projectFiles.Add(new ScriptEditorFile() { path = file, source = File.ReadAllText(file) }); + projectFiles.Add(new ScriptEditorFile(File.ReadAllText(file), file)); } } + if (!exampleFileExists) { - var exampleFile = new ScriptEditorFile() - { - path = Path.Combine(projectWorkspacePath, ExampleScriptFileName), - source = defaultSource - }; - + var exampleFile = new ScriptEditorFile(ScriptEditorFile.DefaultSource, Path.Combine(projectWorkspacePath, ExampleScriptFileName)); projectFiles.Add(exampleFile); SaveProjectFile(exampleFile); } @@ -118,10 +90,36 @@ public void ReloadProjectWorkspace() lastError = ex.Message; return; } - lastError = ""; + + lastError = string.Empty; + } + + protected override void DrawWindow() + { + DrawHeader(); + + if (projectFiles.Count > 0) + { + DrawEditor(); + DrawFooter(); + } + else + { + editorArea.Begin(); + GUILayout.Label("Select a valid project workspace path"); + editorArea.End(); + } + } + + protected override void HandleException(Exception ex) + { + Logger.Error("Exception in ScriptEditor - " + ex.Message); + Visible = false; } - void SaveAllProjectFiles() + private static void SaveProjectFile(ScriptEditorFile file) => File.WriteAllText(file.Path, file.Source); + + private void SaveAllProjectFiles() { try { @@ -135,22 +133,11 @@ void SaveAllProjectFiles() lastError = ex.Message; return; } - lastError = ""; - } - void SaveProjectFile(ScriptEditorFile file) - { - File.WriteAllText(file.path, file.source); + lastError = string.Empty; } - void Update() - { - if (GUI.GetNameOfFocusedControl() == textAreaControlName) - { - } - } - - void DrawHeader() + private void DrawHeader() { headerArea.Begin(); GUILayout.BeginHorizontal(); @@ -163,8 +150,8 @@ void DrawHeader() if (!newProjectWorkspacePath.Equals(projectWorkspacePath)) { projectWorkspacePath = newProjectWorkspacePath.Trim(); - ModTools.Instance.config.scriptEditorWorkspacePath = projectWorkspacePath; - ModTools.Instance.SaveConfig(); + MainWindow.Instance.Config.ScriptWorkspacePath = projectWorkspacePath; + MainWindow.Instance.SaveConfig(); } if (GUILayout.Button("Reload", GUILayout.Width(100))) @@ -179,7 +166,7 @@ void DrawHeader() foreach (var file in projectFiles) { - if (GUILayout.Button(Path.GetFileName(file.path), GUILayout.ExpandWidth(false))) + if (GUILayout.Button(Path.GetFileName(file.Path), GUILayout.ExpandWidth(false))) { currentFile = file; } @@ -191,40 +178,32 @@ void DrawHeader() headerArea.End(); } - void DrawEditor() + private void DrawEditor() { editorArea.Begin(); editorScrollPosition = GUILayout.BeginScrollView(editorScrollPosition); - GUI.SetNextControlName(textAreaControlName); + GUI.SetNextControlName(TextAreaControlName); - string text = GUILayout.TextArea(currentFile != null ? currentFile.source : "No file loaded..", GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true)); - TextEditor editor = (TextEditor)GUIUtility.GetStateObject(typeof(TextEditor), GUIUtility.keyboardControl); + var text = GUILayout.TextArea(currentFile != null ? currentFile.Source : "No file loaded..", GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true)); + var editor = (TextEditor)GUIUtility.GetStateObject(typeof(TextEditor), GUIUtility.keyboardControl); if (GUIUtility.keyboardControl == editor.controlID && Event.current.Equals(Event.KeyboardEvent("tab"))) { -#if OLDVERSION - if (text.Length > editor.pos) - { - text = text.Insert(editor.pos, "\t"); - editor.pos++; - editor.selectPos = editor.pos; - } -#else if (text.Length > editor.cursorIndex) { text = text.Insert(editor.cursorIndex, "\t"); editor.cursorIndex++; editor.selectIndex = editor.cursorIndex; } -#endif + Event.current.Use(); } if (currentFile != null) { - currentFile.source = text; + currentFile.Source = text; } GUILayout.EndScrollView(); @@ -232,7 +211,7 @@ void DrawEditor() editorArea.End(); } - void DrawFooter() + private void DrawFooter() { footerArea.Begin(); @@ -245,23 +224,21 @@ void DrawFooter() if (GUILayout.Button("Compile")) { - string dllPath; - if (ScriptCompiler.CompileSource(projectFiles, out dllPath)) + if (ScriptCompiler.CompileSource(projectFiles, out var dllPath)) { - Log.Message("Source compiled to \"" + dllPath + "\""); + Logger.Message("Source compiled to \"" + dllPath + "\""); } else { - Log.Error("Failed to compile script!"); + Logger.Error("Failed to compile script!"); } } if (GUILayout.Button("Run")) { - string errorMessage; - if (ScriptCompiler.RunSource(projectFiles, out errorMessage, out currentMod)) + if (ScriptCompiler.RunSource(projectFiles, out var errorMessage, out currentMod)) { - Log.Message("Running IModEntryPoint.OnModLoaded()"); + Logger.Message("Running IModEntryPoint.OnModLoaded()"); try { @@ -269,13 +246,13 @@ void DrawFooter() } catch (Exception ex) { - Log.Error($"Exception while calling IModEntryPoint.OnModLoaded() - {ex.Message}"); + Logger.Error($"Exception while calling IModEntryPoint.OnModLoaded() - {ex.Message}"); } } else { lastError = errorMessage; - Log.Error("Failed to compile or run source, reason: " + errorMessage); + Logger.Error("Failed to compile or run source, reason: " + errorMessage); } } @@ -287,14 +264,14 @@ void DrawFooter() if (GUILayout.Button("Stop")) { - Log.Message("Running IModEntryPoint.OnModUnloaded()"); + Logger.Message("Running IModEntryPoint.OnModUnloaded()"); try { currentMod.OnModUnloaded(); } catch (Exception ex) { - Log.Error($"Exception while calling IModEntryPoint.OnModUnloaded() - {ex.Message}"); + Logger.Error($"Exception while calling IModEntryPoint.OnModUnloaded() - {ex.Message}"); } currentMod = null; @@ -322,7 +299,8 @@ void DrawFooter() lastError = ex.Message; return; } - lastError = ""; + + lastError = string.Empty; } GUI.enabled = true; @@ -336,56 +314,5 @@ void DrawFooter() footerArea.End(); } - - void DrawWindow() - { - DrawHeader(); - - if (projectFiles.Any()) - { - DrawEditor(); - DrawFooter(); - } - else - { - editorArea.Begin(); - GUILayout.Label("Select a valid project workspace path"); - editorArea.End(); - } - } - - void HandleException(Exception ex) - { - Log.Error("Exception in ScriptEditor - " + ex.Message); - visible = false; - } - - private readonly string defaultSource = @"//You can copy this script's file and use it for your own scripts -using System; -using System.Collections; -using System.Collections.Generic; -using System.Reflection; -using System.IO; -using System.Linq; -using ColossalFramework.UI; -using UnityEngine; - - -namespace ModTools -{ - class ExampleScript : IModEntryPoint - { - public void OnModLoaded() - { - throw new Exception(""Hello World!""); //replace this line with your script - } - public void OnModUnloaded() - { - throw new Exception(""Goodbye Cruel World!""); //replace this line with your clean up script - } - } -}"; - } - -} +} \ No newline at end of file diff --git a/Debugger/Scripting/ScriptEditorFile.cs b/Debugger/Scripting/ScriptEditorFile.cs new file mode 100644 index 0000000..f574473 --- /dev/null +++ b/Debugger/Scripting/ScriptEditorFile.cs @@ -0,0 +1,40 @@ +namespace ModTools.Scripting +{ + internal sealed class ScriptEditorFile + { + public const string DefaultSource = @"//You can copy this script's file and use it for your own scripts +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System.IO; +using System.Linq; +using ColossalFramework.UI; +using UnityEngine; + +namespace ModTools.Scripting +{ + class ExampleScript : IModEntryPoint + { + public void OnModLoaded() + { + throw new Exception(""Hello World!""); //replace this line with your script + } + public void OnModUnloaded() + { + throw new Exception(""Goodbye Cruel World!""); //replace this line with your clean up script + } + } +}"; + + public ScriptEditorFile(string source, string path) + { + Source = source ?? string.Empty; + Path = path ?? string.Empty; + } + + public string Source { get; set; } + + public string Path { get; } + } +} \ No newline at end of file diff --git a/Debugger/TextureViewer.cs b/Debugger/TextureViewer.cs deleted file mode 100644 index ee5b621..0000000 --- a/Debugger/TextureViewer.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using ModTools.Utils; -using UnityEngine; - -namespace ModTools -{ - public class TextureViewer : GUIWindow - { - - public Texture previewTexture = null; - public ReferenceChain caller = null; - - private TextureViewer() : base("Texture Viewer", new Rect(512, 128, 512, 512), skin) - { - onDraw = DrawWindow; - onClose = HandleClose; - } - - public static TextureViewer CreateTextureViewer(ReferenceChain refChain, Texture texture) - { - var go = new GameObject("TextureViewer"); - go.transform.parent = ModTools.Instance.transform; - var textureViewer = go.AddComponent(); - textureViewer.caller = refChain; - var texture3D = texture as Texture3D; - textureViewer.previewTexture = texture3D == null ? texture: texture3D.ToTexture2D(); - textureViewer.visible = true; - return textureViewer; - } - - void HandleClose() - { - Destroy(this); - } - - void DrawWindow() - { - if (previewTexture != null) - { - title = String.Format("Previewing \"{0}\"", previewTexture.name); - - if (GUILayout.Button("Dump .png", GUILayout.Width(128))) - { - TextureUtil.DumpTextureToPNG(previewTexture); - } - - float aspect = (float)previewTexture.width / ((float)previewTexture.height + 60.0f); - rect.width = rect.height * aspect; - GUI.DrawTexture(new Rect(0.0f, 60.0f, rect.width, rect.height - 60.0f), previewTexture, ScaleMode.ScaleToFit, false); - } - else - { - title = "Texture Viewer"; - GUILayout.Label("Use the Scene Explorer to select a Texture for preview"); - } - } - - } -} diff --git a/Debugger/UI/ColorPicker.cs b/Debugger/UI/ColorPicker.cs new file mode 100644 index 0000000..e46c44b --- /dev/null +++ b/Debugger/UI/ColorPicker.cs @@ -0,0 +1,180 @@ +using System; +using System.Collections.Generic; +using ModTools.Utils; +using UnityEngine; + +namespace ModTools.UI +{ + internal sealed class ColorPicker : GUIWindow, IGameObject + { + private static readonly Dictionary TextureCache = new Dictionary(); + private static readonly Color LineColor = Color.white; + + private readonly int colorPickerSize = 142; + private readonly int hueBarWidth = 26; + + private Texture2D colorPickerTexture; + private Texture2D hueBarTexture; + private ColorUtil.HSV currentHSV; + private float originalAlpha; + + private Rect colorPickerRect; + private Rect hueBarRect; + + private Texture2D lineTexTexture; + + public ColorPicker() + : base("ColorPicker", new Rect(16.0f, 16.0f, 188.0f, 156.0f), Skin, resizable: false, hasTitlebar: false) + { + colorPickerRect = new Rect(8.0f, 8.0f, colorPickerSize, colorPickerSize); + hueBarRect = new Rect(colorPickerRect.x + colorPickerSize + 4.0f, colorPickerRect.y, hueBarWidth, colorPickerRect.height); + } + + public string CurrentValueId { get; private set; } + + public Color SelectedColor + { + get + { + var result = ColorUtil.HSV.HSV2RGB(currentHSV); + result.a = originalAlpha; + return result; + } + } + + private Texture2D Texture => colorPickerTexture ?? (colorPickerTexture = new Texture2D(colorPickerSize, colorPickerSize)); + + private Texture2D HueBar => hueBarTexture ?? (hueBarTexture = DrawHueBar(hueBarWidth, colorPickerSize)); + + private Texture2D LineTex => lineTexTexture ?? (lineTexTexture = DrawLineTex()); + + public static Texture2D GetColorTexture(string id, Color color) + { + if (!TextureCache.TryGetValue(id, out var texture)) + { + texture = new Texture2D(1, 1); + TextureCache.Add(id, texture); + } + + texture.SetPixel(0, 0, color); + texture.Apply(); + return texture; + } + + public void Show(string id, Color initialColor) + { + CurrentValueId = id; + originalAlpha = initialColor.a; + currentHSV = ColorUtil.HSV.RGB2HSV(initialColor); + currentHSV.H = 360.0f - currentHSV.H; + UpdateColorTexture(); + + Vector2 mouse = Input.mousePosition; + mouse.y = Screen.height - mouse.y; + + var windowRect = WindowRect; + windowRect.position = mouse; + MoveResize(windowRect); + Visible = true; + } + + public void Update() + { + Vector2 mouse = Input.mousePosition; + mouse.y = Screen.height - mouse.y; + + if (Input.GetMouseButton(0)) + { + if (!WindowRect.Contains(mouse)) + { + Visible = false; + return; + } + } + else + { + return; + } + + mouse -= WindowRect.position; + + if (hueBarRect.Contains(mouse)) + { + currentHSV.H = (1.0f - Mathf.Clamp01((mouse.y - hueBarRect.y) / hueBarRect.height)) * 360.0f; + UpdateColorTexture(); + } + + if (colorPickerRect.Contains(mouse)) + { + currentHSV.S = Mathf.Clamp01((mouse.x - colorPickerRect.x) / colorPickerRect.width); + currentHSV.V = Mathf.Clamp01((mouse.y - colorPickerRect.y) / colorPickerRect.height); + } + } + + protected override void HandleException(Exception ex) + { + Logger.Error("Exception in ColorPicker - " + ex.Message); + Visible = false; + } + + protected override void DrawWindow() + { + GUI.DrawTexture(colorPickerRect, Texture); + GUI.DrawTexture(hueBarRect, HueBar); + + var hueBarLineY = hueBarRect.y + (1.0f - (float)currentHSV.H / 360.0f) * hueBarRect.height; + GUI.DrawTexture(new Rect(hueBarRect.x - 2.0f, hueBarLineY, hueBarRect.width + 4.0f, 2.0f), LineTex); + + var colorPickerLineY = colorPickerRect.x + (float)currentHSV.V * colorPickerRect.width; + GUI.DrawTexture(new Rect(colorPickerRect.x - 1.0f, colorPickerLineY, colorPickerRect.width + 2.0f, 1.0f), LineTex); + + var colorPickerLineX = colorPickerRect.y + (float)currentHSV.S * colorPickerRect.height; + GUI.DrawTexture(new Rect(colorPickerLineX, colorPickerRect.y - 1.0f, 1.0f, colorPickerRect.height + 2.0f), LineTex); + } + + private static Texture2D DrawHueBar(int width, int height) + { + var texture = new Texture2D(width, height); + + for (var y = 0; y < height; y++) + { + var color = GetColorAtT(y / (float)height * 360.0f); + + for (var x = 0; x < width; x++) + { + texture.SetPixel(x, y, color); + } + } + + texture.Apply(); + return texture; + } + + private static Texture2D DrawLineTex() + { + var tex = new Texture2D(1, 1); + tex.SetPixel(0, 0, LineColor); + tex.Apply(); + return tex; + } + + private static Color GetColorAtT(double hue) + => ColorUtil.HSV.HSV2RGB(new ColorUtil.HSV { H = hue, S = 1.0f, V = 1.0f }); + + private static Color GetColorAtXY(double hue, float xT, float yT) + => ColorUtil.HSV.HSV2RGB(new ColorUtil.HSV { H = hue, S = xT, V = yT }); + + private void UpdateColorTexture() + { + for (var x = 0; x < Texture.width; x++) + { + for (var y = 0; y < Texture.height; y++) + { + Texture.SetPixel(x, y, GetColorAtXY(currentHSV.H, x / (float)Texture.width, 1.0f - y / (float)Texture.height)); + } + } + + Texture.Apply(); + } + } +} \ No newline at end of file diff --git a/Debugger/UI/GUIArea.cs b/Debugger/UI/GUIArea.cs new file mode 100644 index 0000000..29437ca --- /dev/null +++ b/Debugger/UI/GUIArea.cs @@ -0,0 +1,89 @@ +using UnityEngine; + +namespace ModTools.UI +{ + internal sealed class GUIArea + { + private readonly Vector2 margin = new Vector2(8f, 8f); + private readonly GUIWindow window; + + private Vector2 absoluteOffset; + private Vector2 relativeOffset; + + private Vector2 absoluteSize; + private Vector2 relativeSize = Vector2.one; + + private bool drawingArea; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Used by Unity components")] + public GUIArea(GUIWindow window) + { + this.window = window; + } + + public GUIArea OffsetBy(float? horizontal = null, float? vertical = null) + { + absoluteOffset = new Vector2( + horizontal ?? absoluteOffset.x, + vertical ?? absoluteOffset.y); + return this; + } + + public GUIArea OffsetRelative(float? horizontal = null, float? vertical = null) + { + relativeOffset = new Vector2( + Mathf.Clamp(horizontal ?? relativeOffset.x, -1f, 1f), + Mathf.Clamp(vertical ?? relativeOffset.y, -1f, 1f)); + return this; + } + + public GUIArea ChangeSizeBy(float? width = null, float? height = null) + { + absoluteSize = new Vector2( + width ?? absoluteSize.x, + height ?? absoluteSize.y); + return this; + } + + public GUIArea ChangeSizeRelative(float? width = null, float? height = null) + { + relativeSize = new Vector2( + Mathf.Clamp(width ?? relativeSize.x, -1f, 1f), + Mathf.Clamp(height ?? relativeSize.y, -1f, 1f)); + return this; + } + + public bool Begin() + { + var bounds = GetBounds(); + if (bounds == Rect.zero) + { + return false; + } + + GUILayout.BeginArea(bounds); + drawingArea = true; + return true; + } + + public void End() + { + if (drawingArea) + { + GUILayout.EndArea(); + drawingArea = false; + } + } + + private Rect GetBounds() + { + var windowRect = window.WindowRect; + + return new Rect( + absoluteOffset.x + relativeOffset.x * windowRect.width + margin.x, + absoluteOffset.y + relativeOffset.y * windowRect.height + margin.y, + absoluteSize.x + relativeSize.x * windowRect.width - margin.x * 2, + absoluteSize.y + relativeSize.y * windowRect.height - margin.y * 2); + } + } +} \ No newline at end of file diff --git a/Debugger/UI/GUIComboBox.cs b/Debugger/UI/GUIComboBox.cs new file mode 100644 index 0000000..c784a7f --- /dev/null +++ b/Debugger/UI/GUIComboBox.cs @@ -0,0 +1,234 @@ +using UnityEngine; + +namespace ModTools.UI +{ + internal static class GUIComboBox + { + private const string ExpandDownButtonText = " ▼ "; + private static PopupWindow popupWindow; + + public static int Box(int itemIndex, string[] items, string callerId) + { + switch (items.Length) + { + case 0: + return -1; + + case 1: + GUILayout.Label(items[0]); + return 0; + } + + if (popupWindow != null + && callerId == popupWindow.OwnerId + && popupWindow.CloseAndGetSelection(out var newSelectedIndex)) + { + itemIndex = newSelectedIndex; + Object.Destroy(popupWindow); + popupWindow = null; + } + + var popupSize = GetPopupDimensions(items); + + GUILayout.Box(items[itemIndex], GUILayout.Width(popupSize.x)); + var popupPusition = GUIUtility.GUIToScreenPoint(GUILayoutUtility.GetLastRect().position); + if (GUILayout.Button(ExpandDownButtonText, GUILayout.Width(24f)) && EnsurePopupWindow()) + { + popupWindow.Show(callerId, items, itemIndex, popupPusition, popupSize); + } + + return itemIndex; + } + + private static bool EnsurePopupWindow() + { + if (popupWindow != null) + { + return true; + } + + var modTools = GameObject.Find("ModTools"); + if (modTools == null) + { + return false; + } + + if (modTools.GetComponent() == null) + { + popupWindow = modTools.AddComponent(); + } + + return popupWindow != null; + } + + private static Vector2 GetPopupDimensions(string[] items) + { + float width = 0; + float height = 0; + + for (var i = 0; i < items.Length; ++i) + { + var itemSize = GUI.skin.button.CalcSize(new GUIContent(items[i])); + if (itemSize.x > width) + { + width = itemSize.x; + } + + height += itemSize.y; + } + + return new Vector2(width + 36, height + 36); + } + + private sealed class PopupWindow : MonoBehaviour, IUIObject, IGameObject + { + private const float MaxPopupHeight = 400f; + + private static readonly GUIStyle WindowStyle = CreateWindowStyle(); + + private readonly int popupWindowId = GUIUtility.GetControlID(FocusType.Passive); + private readonly GUIStyle hoverStyle; + + private Vector2 popupScrollPosition = Vector2.zero; + + private Rect popupRect; + private Vector2? mouseClickPoint; + private bool readyToClose; + private int selectedIndex; + + private string[] popupItems; + + public PopupWindow() + { + hoverStyle = CreateHoverStyle(); + } + + public string OwnerId { get; private set; } + + public void Show(string ownerId, string[] items, int currentIndex, Vector2 position, Vector2 popupSize) + { + OwnerId = ownerId; + popupItems = items; + selectedIndex = currentIndex; + popupRect = new Rect(position, new Vector2(popupSize.x, Mathf.Min(MaxPopupHeight, popupSize.y))); + popupScrollPosition = default; + mouseClickPoint = null; + readyToClose = false; + } + + public bool CloseAndGetSelection(out int currentIndex) + { + if (readyToClose) + { + currentIndex = selectedIndex; + Close(); + return true; + } + + currentIndex = -1; + return false; + } + + public void OnGUI() + { + if (OwnerId != null) + { + GUI.ModalWindow(popupWindowId, popupRect, WindowFunction, string.Empty, WindowStyle); + } + } + + public void Update() + { + if (OwnerId == null) + { + return; + } + + if (Input.GetMouseButtonDown(0) || Input.GetMouseButtonDown(1) || Input.GetMouseButtonDown(2)) + { + var mousePos = Input.mousePosition; + mousePos.y = Screen.height - mousePos.y; + mouseClickPoint = mousePos; + } + else + { + mouseClickPoint = null; + } + } + + private static GUIStyle CreateHoverStyle() + { + var result = new GUIStyle(GUI.skin.label); + result.hover.textColor = Color.yellow; + var t = new Texture2D(1, 1); + t.SetPixel(0, 0, default); + t.Apply(); + result.hover.background = t; + result.font = GUIWindow.Skin.font; + + return result; + } + + private static GUIStyle CreateWindowStyle() + { + var background = new Texture2D(16, 16, TextureFormat.RGBA32, mipmap: false) + { + wrapMode = TextureWrapMode.Clamp, + }; + + for (var x = 0; x < background.width; x++) + { + for (var y = 0; y < background.height; y++) + { + if (x == 0 || x == background.width - 1 || y == 0 || y == background.height - 1) + { + background.SetPixel(x, y, new Color(0, 0, 0, 1)); + } + else + { + background.SetPixel(x, y, new Color(0.05f, 0.05f, 0.05f, 0.95f)); + } + } + } + + background.Apply(); + + var result = new GUIStyle(GUI.skin.window); + result.normal.background = background; + result.onNormal.background = background; + result.border.top = result.border.bottom; + result.padding.top = result.padding.bottom; + + return result; + } + + private void WindowFunction(int windowId) + { + if (OwnerId == null) + { + return; + } + + popupScrollPosition = GUILayout.BeginScrollView(popupScrollPosition, false, false); + + var oldSelectedIndex = selectedIndex; + selectedIndex = GUILayout.SelectionGrid(selectedIndex, popupItems, xCount: 1, hoverStyle); + + GUILayout.EndScrollView(); + + if (oldSelectedIndex != selectedIndex || mouseClickPoint.HasValue && !popupRect.Contains(mouseClickPoint.Value)) + { + readyToClose = true; + } + } + + private void Close() + { + OwnerId = null; + popupItems = null; + selectedIndex = -1; + mouseClickPoint = null; + } + } + } +} \ No newline at end of file diff --git a/Debugger/UI/GUIControls.cs b/Debugger/UI/GUIControls.cs new file mode 100644 index 0000000..5c18d3b --- /dev/null +++ b/Debugger/UI/GUIControls.cs @@ -0,0 +1,338 @@ +using System; +using ModTools.Utils; +using UnityEngine; + +namespace ModTools.UI +{ + internal static class GUIControls + { + private const float NumberFieldSize = 100f; + private const float StringFieldSize = 200f; + private const float ByteFieldSize = 40f; + private const float CharFieldSize = 25f; + + private static string lastFocusedFieldId; + private static bool lastFocusedFieldEmpty; + + public delegate T ValuePresenterDelegate(string id, T value) + where T : struct; + + private static ModConfiguration Config => MainWindow.Instance.Config; + + public static T PrimitiveValueField(string id, string name, T value) + where T : struct + { + GUILayout.BeginHorizontal(); + + if (!string.IsNullOrEmpty(name)) + { + GUI.contentColor = Config.NameColor; + GUILayout.Label(name); + } + + GUI.contentColor = Config.ValueColor; + + var newText = BufferedTextField(id, value.ToString(), GetTextFieldSize(typeof(T))); + + GUI.contentColor = Color.white; + + GUILayout.EndHorizontal(); + + return ParseHelper.TryParse(newText, out var newValue) ? newValue : value; + } + + public static object PrimitiveValueField(string id, string name, object value) + { + GUILayout.BeginHorizontal(); + + if (!string.IsNullOrEmpty(name)) + { + GUI.contentColor = Config.NameColor; + GUILayout.Label(name); + } + + GUI.contentColor = Config.ValueColor; + + var valueType = value.GetType(); + var newText = BufferedTextField(id, value.ToString(), GetTextFieldSize(valueType)); + + GUI.contentColor = Color.white; + + GUILayout.EndHorizontal(); + + return ParseHelper.TryParse(newText, valueType, out var newValue) ? newValue : value; + } + + public static object EditorValueField(string id, Type type, object value) + { + if (type.IsPrimitive && type != typeof(bool) || type == typeof(string)) + { + return PrimitiveValueField(id, string.Empty, value); + } + + if (type == typeof(bool)) + { + return BoolField(string.Empty, (bool)value); + } + + if (type.IsEnum) + { + return EnumField(id, string.Empty, value); + } + + if (type == typeof(Vector2)) + { + return CustomValueField(id, string.Empty, PresentVector2, (Vector2)value); + } + + if (type == typeof(Vector3)) + { + return CustomValueField(id, string.Empty, PresentVector3, (Vector3)value); + } + + if (type == typeof(Vector4)) + { + return CustomValueField(id, string.Empty, PresentVector4, (Vector4)value); + } + + if (type == typeof(Quaternion)) + { + return CustomValueField(id, string.Empty, PresentQuaternion, (Quaternion)value); + } + + if (type == typeof(Color)) + { + return CustomValueField(id, string.Empty, PresentColor, (Color)value); + } + + if (type == typeof(Color32)) + { + return (Color32)CustomValueField(id, string.Empty, PresentColor, (Color)(Color32)value); + } + + return value; + } + + public static bool BoolField(string name, bool value) + { + GUILayout.BeginHorizontal(); + + if (!string.IsNullOrEmpty(name)) + { + GUI.contentColor = Config.NameColor; + GUILayout.Label(name); + } + + GUI.contentColor = Config.ValueColor; + + value = GUILayout.Toggle(value, string.Empty); + + GUI.contentColor = Color.white; + + GUILayout.EndHorizontal(); + + return value; + } + + public static object EnumField(string id, string name, object value) + { + GUILayout.BeginHorizontal(); + + try + { + var enumType = value.GetType(); + + if (!string.IsNullOrEmpty(name)) + { + GUI.contentColor = Config.NameColor; + GUILayout.Label(name); + } + + GUI.contentColor = Config.ValueColor; + + if (TypeUtil.IsBitmaskEnum(enumType)) + { + GUILayout.Label(value.ToString()); + } + else + { + var names = Enum.GetNames(enumType); + var values = Enum.GetValues(enumType); + var valueIndex = Array.IndexOf(values, value); + var newValueIndex = GUIComboBox.Box(valueIndex, names, id); + if (newValueIndex != valueIndex) + { + value = values.GetValue(newValueIndex); + } + } + + GUI.contentColor = Color.white; + } + finally + { + GUILayout.EndHorizontal(); + } + + return value; + } + + public static Vector2 PresentVector2(string id, Vector2 value) + { + value.x = PrimitiveValueField(id + ".x", "x", value.x); + value.y = PrimitiveValueField(id + ".y", "y", value.y); + return value; + } + + public static Vector3 PresentVector3(string id, Vector3 value) + { + value.x = PrimitiveValueField(id + ".x", "x", value.x); + value.y = PrimitiveValueField(id + ".y", "y", value.y); + value.z = PrimitiveValueField(id + ".z", "z", value.z); + return value; + } + + public static Vector4 PresentVector4(string id, Vector4 value) + { + value.x = PrimitiveValueField(id + ".x", "x", value.x); + value.y = PrimitiveValueField(id + ".y", "y", value.y); + value.z = PrimitiveValueField(id + ".z", "z", value.z); + value.w = PrimitiveValueField(id + ".w", "w", value.w); + return value; + } + + public static Quaternion PresentQuaternion(string id, Quaternion value) + { + var euler = value.eulerAngles; + euler.x = PrimitiveValueField(id + ".x", "x", euler.x); + euler.y = PrimitiveValueField(id + ".y", "y", euler.y); + euler.z = PrimitiveValueField(id + ".z", "z", euler.z); + if (euler != value.eulerAngles) + { + value = Quaternion.Euler(euler); + } + + return value; + } + + public static Color PresentColor(string id, Color value) + { + var r = (byte)Mathf.Clamp(value.r * 255.0f, byte.MinValue, byte.MaxValue); + var g = (byte)Mathf.Clamp(value.g * 255.0f, byte.MinValue, byte.MaxValue); + var b = (byte)Mathf.Clamp(value.b * 255.0f, byte.MinValue, byte.MaxValue); + var a = (byte)Mathf.Clamp(value.a * 255.0f, byte.MinValue, byte.MaxValue); + + r = PrimitiveValueField(id + ".r", "r", r); + g = PrimitiveValueField(id + ".g", "g", g); + b = PrimitiveValueField(id + ".b", "b", b); + a = PrimitiveValueField(id + ".a", "a", a); + + value.r = Mathf.Clamp01(r / 255.0f); + value.g = Mathf.Clamp01(g / 255.0f); + value.b = Mathf.Clamp01(b / 255.0f); + value.a = Mathf.Clamp01(a / 255.0f); + + if (GUILayout.Button(string.Empty, GUILayout.Width(72))) + { + var picker = MainWindow.Instance.ColorPicker; + picker?.Show(id, value); + } + else + { + var picker = MainWindow.Instance.ColorPicker; + if (picker?.Visible == true && picker.CurrentValueId == id) + { + value = picker.SelectedColor; + } + } + + var lastRect = GUILayoutUtility.GetLastRect(); + lastRect.x += 4.0f; + lastRect.y += 4.0f; + lastRect.width -= 8.0f; + lastRect.height -= 8.0f; + GUI.DrawTexture(lastRect, ColorPicker.GetColorTexture(id, value), ScaleMode.StretchToFill); + + return value; + } + + public static T CustomValueField(string id, string name, ValuePresenterDelegate presenter, T value, float ident = float.NaN) + where T : struct + { + GUILayout.BeginHorizontal(); + + if (!float.IsNaN(ident)) + { + GUILayout.Space(ident); + GUI.contentColor = Config.TypeColor; + GUILayout.Label(value.GetType().Name); + } + + GUI.contentColor = Config.NameColor; + GUILayout.Label(name); + + GUI.contentColor = Config.ValueColor; + + if (!float.IsNaN(ident)) + { + GUILayout.FlexibleSpace(); + } + + var result = presenter(id, value); + + GUI.contentColor = Color.white; + + GUILayout.EndHorizontal(); + + return result; + } + + private static string BufferedTextField(string id, string value, float fieldSize) + { + var focusedFieldId = GUI.GetNameOfFocusedControl(); + + if (focusedFieldId != lastFocusedFieldId) + { + lastFocusedFieldEmpty = string.IsNullOrEmpty(value); + lastFocusedFieldId = focusedFieldId; + } + + if (focusedFieldId == id && lastFocusedFieldEmpty) + { + value = string.Empty; + } + + GUI.SetNextControlName(id); + var newValue = GUILayout.TextField(value, GUILayout.Width(fieldSize), GUILayout.Height(22f)); + + if (focusedFieldId == id) + { + lastFocusedFieldEmpty = string.IsNullOrEmpty(newValue); + if (lastFocusedFieldEmpty) + { + return null; + } + } + + return value != newValue ? newValue : null; + } + + private static float GetTextFieldSize(Type valueType) + { + switch (Type.GetTypeCode(valueType)) + { + case TypeCode.Char: + return CharFieldSize; + + case TypeCode.Byte: + case TypeCode.SByte: + return ByteFieldSize; + + case TypeCode.String: + return StringFieldSize; + + default: + return NumberFieldSize; + } + } + } +} \ No newline at end of file diff --git a/Debugger/GUICommon/GUIStackTrace.cs b/Debugger/UI/GUIStackTrace.cs similarity index 61% rename from Debugger/GUICommon/GUIStackTrace.cs rename to Debugger/UI/GUIStackTrace.cs index d402979..d522b3e 100644 --- a/Debugger/GUICommon/GUIStackTrace.cs +++ b/Debugger/UI/GUIStackTrace.cs @@ -1,9 +1,10 @@ using System.Diagnostics; +using ModTools.Console; using UnityEngine; -namespace ModTools +namespace ModTools.UI { - public static class GUIStackTrace + internal static class GUIStackTrace { public static void StackTraceButton(StackTrace stackTrace) { @@ -12,8 +13,12 @@ public static void StackTraceButton(StackTrace stackTrace) var viewer = StackTraceViewer.CreateStackTraceViewer(stackTrace); var mouse = Input.mousePosition; mouse.y = Screen.height - mouse.y; - viewer.rect.position = mouse; - viewer.visible = true; + + var windowRect = viewer.WindowRect; + windowRect.position = mouse; + viewer.MoveResize(windowRect); + + viewer.Visible = true; } } } diff --git a/Debugger/UI/GUIWindow.cs b/Debugger/UI/GUIWindow.cs new file mode 100644 index 0000000..b122a3c --- /dev/null +++ b/Debugger/UI/GUIWindow.cs @@ -0,0 +1,410 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace ModTools.UI +{ + internal abstract class GUIWindow : MonoBehaviour, IDestroyableObject, IUIObject + { + // TODO: make this field to private-instance + public static GUISkin Skin; + + protected const float UIScale = 1.0f; + + private static readonly List Windows = new List(); + + private static GUIWindow resizingWindow; + private static Vector2 resizeDragHandle = Vector2.zero; + + private static GUIWindow movingWindow; + private static Vector2 moveDragHandle = Vector2.zero; + + private readonly int id; + private readonly bool resizable; + private readonly bool hasTitlebar; + + private Vector2 minSize = Vector2.zero; + private Rect windowRect = new Rect(0, 0, 64, 64); + + private bool visible; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811", Justification = ".ctor of a Unity component")] + protected GUIWindow(string title, Rect rect, GUISkin skin, bool resizable = true, bool hasTitlebar = true) + { + id = UnityEngine.Random.Range(1024, int.MaxValue); + Title = title; + windowRect = rect; + Skin = skin; + this.resizable = resizable; + this.hasTitlebar = hasTitlebar; + minSize = new Vector2(64.0f, 64.0f); + Windows.Add(this); + } + + public Rect WindowRect => windowRect; + + public bool Visible + { + get => visible; + + set + { + var wasVisible = visible; + visible = value; + if (visible && !wasVisible) + { + GUI.BringWindowToFront(id); + OnWindowOpened(); + } + } + } + + protected static Texture2D BgTexture { get; set; } + + protected static Texture2D ResizeNormalTexture { get; set; } + + protected static Texture2D ResizeHoverTexture { get; set; } + + protected static Texture2D CloseNormalTexture { get; set; } + + protected static Texture2D CloseHoverTexture { get; set; } + + protected static Texture2D MoveNormalTexture { get; set; } + + protected static Texture2D MoveHoverTexture { get; set; } + + protected string Title { get; set; } + + private static ModConfiguration Config => MainWindow.Instance.Config; + + public static void UpdateFont() + { + Skin.font = Font.CreateDynamicFontFromOSFont(Config.FontName, Config.FontSize); + MainWindow.Instance?.SceneExplorer?.RecalculateAreas(); + } + + public void OnDestroy() + { + OnWindowDestroyed(); + Windows.Remove(this); + } + + public void OnGUI() + { + if (Skin == null) + { + BgTexture = new Texture2D(1, 1); + BgTexture.SetPixel(0, 0, Config.BackgroundColor); + BgTexture.Apply(); + + ResizeNormalTexture = new Texture2D(1, 1); + ResizeNormalTexture.SetPixel(0, 0, Color.white); + ResizeNormalTexture.Apply(); + + ResizeHoverTexture = new Texture2D(1, 1); + ResizeHoverTexture.SetPixel(0, 0, Color.blue); + ResizeHoverTexture.Apply(); + + CloseNormalTexture = new Texture2D(1, 1); + CloseNormalTexture.SetPixel(0, 0, Color.red); + CloseNormalTexture.Apply(); + + CloseHoverTexture = new Texture2D(1, 1); + CloseHoverTexture.SetPixel(0, 0, Color.white); + CloseHoverTexture.Apply(); + + MoveNormalTexture = new Texture2D(1, 1); + MoveNormalTexture.SetPixel(0, 0, Config.TitleBarColor); + MoveNormalTexture.Apply(); + + MoveHoverTexture = new Texture2D(1, 1); + MoveHoverTexture.SetPixel(0, 0, Config.TitleBarColor * 1.2f); + MoveHoverTexture.Apply(); + + Skin = ScriptableObject.CreateInstance(); + Skin.box = new GUIStyle(GUI.skin.box); + Skin.button = new GUIStyle(GUI.skin.button); + Skin.horizontalScrollbar = new GUIStyle(GUI.skin.horizontalScrollbar); + Skin.horizontalScrollbarLeftButton = new GUIStyle(GUI.skin.horizontalScrollbarLeftButton); + Skin.horizontalScrollbarRightButton = new GUIStyle(GUI.skin.horizontalScrollbarRightButton); + Skin.horizontalScrollbarThumb = new GUIStyle(GUI.skin.horizontalScrollbarThumb); + Skin.horizontalSlider = new GUIStyle(GUI.skin.horizontalSlider); + Skin.horizontalSliderThumb = new GUIStyle(GUI.skin.horizontalSliderThumb); + Skin.label = new GUIStyle(GUI.skin.label); + Skin.scrollView = new GUIStyle(GUI.skin.scrollView); + Skin.textArea = new GUIStyle(GUI.skin.textArea); + Skin.textField = new GUIStyle(GUI.skin.textField); + Skin.toggle = new GUIStyle(GUI.skin.toggle); + Skin.verticalScrollbar = new GUIStyle(GUI.skin.verticalScrollbar); + Skin.verticalScrollbarDownButton = new GUIStyle(GUI.skin.verticalScrollbarDownButton); + Skin.verticalScrollbarThumb = new GUIStyle(GUI.skin.verticalScrollbarThumb); + Skin.verticalScrollbarUpButton = new GUIStyle(GUI.skin.verticalScrollbarUpButton); + Skin.verticalSlider = new GUIStyle(GUI.skin.verticalSlider); + Skin.verticalSliderThumb = new GUIStyle(GUI.skin.verticalSliderThumb); + Skin.window = new GUIStyle(GUI.skin.window); + Skin.window.normal.background = BgTexture; + Skin.window.onNormal.background = BgTexture; + + Skin.settings.cursorColor = GUI.skin.settings.cursorColor; + Skin.settings.cursorFlashSpeed = GUI.skin.settings.cursorFlashSpeed; + Skin.settings.doubleClickSelectsWord = GUI.skin.settings.doubleClickSelectsWord; + Skin.settings.selectionColor = GUI.skin.settings.selectionColor; + Skin.settings.tripleClickSelectsLine = GUI.skin.settings.tripleClickSelectsLine; + + UpdateFont(); + } + + if (!Visible) + { + return; + } + + var oldSkin = GUI.skin; + if (Skin != null) + { + GUI.skin = Skin; + } + + var matrix = GUI.matrix; + GUI.matrix = Matrix4x4.Scale(new Vector3(UIScale, UIScale, UIScale)); + + windowRect = GUI.Window(id, windowRect, WindowFunction, string.Empty); + + OnWindowDrawn(); + + GUI.matrix = matrix; + + GUI.skin = oldSkin; + } + + public void MoveResize(Rect newWindowRect) => windowRect = newWindowRect; + + protected static bool IsMouseOverWindow() + { + var mouse = Input.mousePosition; + mouse.y = Screen.height - mouse.y; + return Windows.FindIndex(window => window.Visible && window.windowRect.Contains(mouse)) >= 0; + } + + protected abstract void DrawWindow(); + + protected virtual void HandleException(Exception ex) + { + } + + protected virtual void OnWindowDrawn() + { + } + + protected virtual void OnWindowOpened() + { + } + + protected virtual void OnWindowClosed() + { + } + + protected virtual void OnWindowResized(Vector2 size) + { + } + + protected virtual void OnWindowMoved(Vector2 position) + { + } + + protected virtual void OnWindowDestroyed() + { + } + + private void WindowFunction(int windowId) + { + GUILayout.Space(8.0f); + + try + { + DrawWindow(); + } + catch (Exception ex) + { + HandleException(ex); + } + + GUILayout.Space(16.0f); + + var mouse = Input.mousePosition; + mouse.y = Screen.height - mouse.y; + + DrawBorder(); + + if (hasTitlebar) + { + DrawTitlebar(mouse); + DrawCloseButton(mouse); + } + + if (resizable) + { + DrawResizeHandle(mouse); + } + } + + private void DrawBorder() + { + var leftRect = new Rect(0.0f, 0.0f, 1.0f, windowRect.height); + var rightRect = new Rect(windowRect.width - 1.0f, 0.0f, 1.0f, windowRect.height); + var bottomRect = new Rect(0.0f, windowRect.height - 1.0f, windowRect.width, 1.0f); + GUI.DrawTexture(leftRect, MoveNormalTexture); + GUI.DrawTexture(rightRect, MoveNormalTexture); + GUI.DrawTexture(bottomRect, MoveNormalTexture); + } + + private void DrawTitlebar(Vector3 mouse) + { + var moveRect = new Rect(windowRect.x * UIScale, windowRect.y * UIScale, windowRect.width * UIScale, 20.0f); + var moveTex = MoveNormalTexture; + + // TODO: reduce nesting + if (!GUIUtility.hasModalWindow) + { + if (movingWindow != null) + { + if (movingWindow == this) + { + moveTex = MoveHoverTexture; + + if (Input.GetMouseButton(0)) + { + var pos = new Vector2(mouse.x, mouse.y) + moveDragHandle; + windowRect.x = pos.x; + windowRect.y = pos.y; + if (windowRect.x < 0.0f) + { + windowRect.x = 0.0f; + } + + if (windowRect.x + windowRect.width > Screen.width) + { + windowRect.x = Screen.width - windowRect.width; + } + + if (windowRect.y < 0.0f) + { + windowRect.y = 0.0f; + } + + if (windowRect.y + windowRect.height > Screen.height) + { + windowRect.y = Screen.height - windowRect.height; + } + } + else + { + movingWindow = null; + MainWindow.Instance.SaveConfig(); + + OnWindowMoved(windowRect.position); + } + } + } + else if (moveRect.Contains(mouse)) + { + moveTex = MoveHoverTexture; + if (Input.GetMouseButton(0) && resizingWindow == null) + { + movingWindow = this; + moveDragHandle = new Vector2(windowRect.x, windowRect.y) - new Vector2(mouse.x, mouse.y); + } + } + } + + GUI.DrawTexture(new Rect(0.0f, 0.0f, windowRect.width * UIScale, 20.0f), moveTex, ScaleMode.StretchToFill); + GUI.contentColor = Config.TitleBarTextColor; + GUI.Label(new Rect(8.0f, 0.0f, windowRect.width * UIScale, 20.0f), Title); + GUI.contentColor = Color.white; + } + + private void DrawCloseButton(Vector3 mouse) + { + var closeRect = new Rect(windowRect.x * UIScale + windowRect.width * UIScale - 20.0f, windowRect.y * UIScale, 16.0f, 8.0f); + var closeTex = CloseNormalTexture; + + if (!GUIUtility.hasModalWindow && closeRect.Contains(mouse)) + { + closeTex = CloseHoverTexture; + + if (Input.GetMouseButton(0)) + { + resizingWindow = null; + movingWindow = null; + Visible = false; + OnWindowClosed(); + } + } + + GUI.DrawTexture(new Rect(windowRect.width - 20.0f, 0.0f, 16.0f, 8.0f), closeTex, ScaleMode.StretchToFill); + } + + private void DrawResizeHandle(Vector3 mouse) + { + var resizeRect = new Rect(windowRect.x * UIScale + windowRect.width * UIScale - 16.0f, windowRect.y * UIScale + windowRect.height * UIScale - 8.0f, 16.0f, 8.0f); + var resizeTex = ResizeNormalTexture; + + // TODO: reduce nesting + if (!GUIUtility.hasModalWindow) + { + if (resizingWindow != null) + { + if (resizingWindow == this) + { + resizeTex = ResizeHoverTexture; + + if (Input.GetMouseButton(0)) + { + var size = new Vector2(mouse.x, mouse.y) + resizeDragHandle - new Vector2(windowRect.x, windowRect.y); + + if (size.x < minSize.x) + { + size.x = minSize.x; + } + + if (size.y < minSize.y) + { + size.y = minSize.y; + } + + windowRect.width = size.x; + windowRect.height = size.y; + + if (windowRect.x + windowRect.width >= Screen.width) + { + windowRect.width = Screen.width - windowRect.x; + } + + if (windowRect.y + windowRect.height >= Screen.height) + { + windowRect.height = Screen.height - windowRect.y; + } + } + else + { + resizingWindow = null; + MainWindow.Instance.SaveConfig(); + OnWindowResized(windowRect.size); + } + } + } + else if (resizeRect.Contains(mouse)) + { + resizeTex = ResizeHoverTexture; + if (Input.GetMouseButton(0)) + { + resizingWindow = this; + resizeDragHandle = new Vector2(windowRect.x + windowRect.width, windowRect.y + windowRect.height) - new Vector2(mouse.x, mouse.y); + } + } + } + + GUI.DrawTexture(new Rect(windowRect.width - 16.0f, windowRect.height - 8.0f, 16.0f, 8.0f), resizeTex, ScaleMode.StretchToFill); + } + } +} \ No newline at end of file diff --git a/Debugger/UpdateHook.cs b/Debugger/UpdateHook.cs deleted file mode 100644 index 5cfb133..0000000 --- a/Debugger/UpdateHook.cs +++ /dev/null @@ -1,25 +0,0 @@ -using UnityEngine; - -namespace ModTools -{ - public class UpdateHook : MonoBehaviour - { - public delegate void OnUnityUpdate(); - - public OnUnityUpdate onUnityUpdate = null; - public bool once = true; - - void Update() - { - if (onUnityUpdate != null) - { - onUnityUpdate(); - if (once) - { - Destroy(this); - } - } - } - - } -} diff --git a/Debugger/UserNotifications.cs b/Debugger/UserNotifications.cs index 85045b5..bfa51af 100644 --- a/Debugger/UserNotifications.cs +++ b/Debugger/UserNotifications.cs @@ -2,37 +2,34 @@ namespace ModTools { - - public static class UserNotifications + internal static class UserNotifications { - private static Configuration config - { - get { return ModTools.Instance.config; } - } + private const string LoggingChangeNotification = @"You are using the new ModTools console. +It offers an improved experience over the old one but requires a change to your logging code. +You should no longer use DebugOutputPanel for logging as messages sent to it won't get displayed by ModTools. +Instead you should use the built-in Unity Debug API (http://docs.unity3d.com/ScriptReference/Debug.html). Example: Debug.Log(""Hello world!"");"; - private static List> notifications = new List>(); - private static int notificationsCount = 0; + private const string UnityLoggingHookNotification = @"Your version of ModTools has a new feature which allows it to hook Unity's Debug logging so you can safely log from the simulation thread (or any other thread). +This feature is currently marked as experimental and is off by default. You can find it in the main menu (Ctrl+Q) as ""Hook Unity's logging (experimental)"". After enabling it you should see a warning in the console saying so. +It is recommended that you enable this (as it will probably become the default mode in the future) and report any issues."; - static UserNotifications() + private static readonly List> Notifications = new List> { - Add(LoggingChangeNotification); - Add(UnityLoggingHookNotification); - } + new KeyValuePair(0, LoggingChangeNotification), + new KeyValuePair(1, UnityLoggingHookNotification), + }; - private static void Add(string notification) - { - notifications.Add(new KeyValuePair(notificationsCount++, notification)); - } + private static ModConfiguration Config => MainWindow.Instance.Config; public static List> GetNotifications() { - List> result = new List>(); + var result = new List>(); - foreach (var item in notifications) + foreach (var item in Notifications) { - if ((config.hiddenNotifications & (1 << item.Key)) == 0) + if ((Config.HiddenNotifications & (1 << item.Key)) == 0) { - result.Add(item); + result.Add(item); } } @@ -41,19 +38,8 @@ public static List> GetNotifications() public static void HideNotification(int index) { - config.hiddenNotifications |= 1 << index; - ModTools.Instance.SaveConfig(); + Config.HiddenNotifications |= 1 << index; + MainWindow.Instance.SaveConfig(); } - - private static string LoggingChangeNotification = @"You are using the new ModTools console. -It offers an improved experience over the old one but requires a change to your logging code. -You should no longer use DebugOutputPanel for logging as messages sent to it won't get displayed by ModTools. -Instead you should use the built-in Unity Debug API (http://docs.unity3d.com/ScriptReference/Debug.html). Example: Debug.Log(""Hello world!"");"; - - private static string UnityLoggingHookNotification = @"Your version of ModTools has a new feature which allows it to hook Unity's Debug logging so you can safely log from the simulation thread (or any other thread). -This feature is currently marked as experimental and is off by default. You can find it in the main menu (Ctrl+Q) as ""Hook Unity's logging (experimental)"". After enabling it you should see a warning in the console saying so. -It is recommended that you enable this (as it will probably become the default mode in the future) and report any issues."; - } - -} +} \ No newline at end of file diff --git a/Debugger/Util.cs b/Debugger/Util.cs deleted file mode 100644 index 1eacc4b..0000000 --- a/Debugger/Util.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Reflection; -using ColossalFramework; -using ColossalFramework.IO; -using ColossalFramework.Plugins; -using ICities; -using ModTools.Utils; -using UnityEngine; - -namespace ModTools -{ - - public static class Util - { - - - public static void SetMouseScrolling(bool isEnabled) - { - try - { - var mouseWheelZoom = ReflectionUtil.GetPrivate(ToolsModifierControl.cameraController, "m_mouseWheelZoom"); - if (mouseWheelZoom.value != isEnabled) - { - mouseWheelZoom.value = isEnabled; - } - } - catch (Exception) - { - } - } - - public static bool ComponentIsEnabled(Component component) - { - var prop = component.GetType().GetProperty("enabled"); - if (prop == null) - { - return true; - } - - return (bool)prop.GetValue(component, null); - } - - - } - -} diff --git a/Debugger/Utils/ColorUtil.cs b/Debugger/Utils/ColorUtil.cs index 6fd65e4..e21edc5 100644 --- a/Debugger/Utils/ColorUtil.cs +++ b/Debugger/Utils/ColorUtil.cs @@ -1,81 +1,73 @@ using System; using UnityEngine; -namespace ModTools +namespace ModTools.Utils { - public static class ColorUtil + internal static class ColorUtil { - public struct HSV { - public double h; - public double s; - public double v; + public double H; + public double S; + public double V; - public override string ToString() - { - return String.Format("H: {0}, S: {1}, V:{2}", h.ToString("0.00"), s.ToString("0.00"), v.ToString("0.00")); - } - - public static HSV RGB2HSV(Color color) - { - return RGB2HSV((int)(color.r * 255), (int)(color.g * 255), (int)(color.b * 255)); - } + public static HSV RGB2HSV(Color color) => RGB2HSV((int)(color.r * 255), (int)(color.g * 255), (int)(color.b * 255)); public static HSV RGB2HSV(double r, double b, double g) { - - double delta, min; - double h = 0, s, v; + double delta; + double min; + double h = 0; + double s; + double v; min = Math.Min(Math.Min(r, g), b); v = Math.Max(Math.Max(r, g), b); delta = v - min; - if (v == 0.0) - { - s = 0; - - } - else - s = delta / v; + s = v == 0.0 ? 0 : delta / v; if (s == 0) + { h = 0.0f; - + } else { if (r == v) + { h = (g - b) / delta; + } else if (g == v) + { h = 2 + (b - r) / delta; + } else if (b == v) + { h = 4 + (r - g) / delta; + } h *= 60; if (h < 0.0) - h = h + 360; - + { + h += 360; + } } - HSV hsvColor = new HSV(); - hsvColor.h = h; - hsvColor.s = s; - hsvColor.v = v / 255; - - return hsvColor; - + return new HSV + { + H = h, + S = s, + V = v / 255, + }; } - public static Color HSV2RGB(HSV color) - { - return HSV2RGB(color.h, color.s, color.v); - } + public static Color HSV2RGB(HSV color) => HSV2RGB(color.H, color.S, color.V); public static Color HSV2RGB(double h, double s, double v) { - - double r = 0, g = 0, b = 0; + double r; + double g; + double b; if (s == 0) { @@ -86,20 +78,26 @@ public static Color HSV2RGB(double h, double s, double v) else { int i; - double f, p, q, t; - + double f; + double p; + double q; + double t; if (h == 360) + { h = 0; + } else - h = h / 60; + { + h /= 60; + } - i = (int)(h); + i = (int)h; f = h - i; p = v * (1.0 - s); - q = v * (1.0 - (s * f)); - t = v * (1.0 - (s * (1.0f - f))); + q = v * (1.0 - s * f); + t = v * (1.0 - s * (1.0f - f)); switch (i) { @@ -139,12 +137,12 @@ public static Color HSV2RGB(double h, double s, double v) b = q; break; } - } return new Color((float)r, (float)g, (float)b, 1); } - } + public override string ToString() => $"H: {H.ToString("0.00")}, S: {S.ToString("0.00")}, V:{V.ToString("0.00")}"; + } } -} +} \ No newline at end of file diff --git a/Debugger/Utils/DateTimeUtil.cs b/Debugger/Utils/DateTimeUtil.cs deleted file mode 100644 index 5f5cb06..0000000 --- a/Debugger/Utils/DateTimeUtil.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using UnityEngine; - -namespace ModTools -{ - public static class DateTimeUtil - { - - private static readonly float secondsInMinute = 60.0f; - private static readonly float secondsInHour = secondsInMinute * 60.0f; - private static readonly float secondsInDay = secondsInHour * 24.0f; - private static readonly float secondsInWeek = secondsInDay * 7.0f; - private static readonly float secondsInYear = secondsInWeek * 52.0f; - public static string TimeSpanToString(TimeSpan timeSpan) - { - var seconds = Math.Abs(timeSpan.TotalSeconds); - if (seconds < secondsInMinute) - { - return "Less than a minute ago"; - } - - if (seconds < secondsInHour) - { - return TimeSpanToString((int)(seconds / secondsInMinute), "minute"); - } - - if (seconds < secondsInDay) - { - return TimeSpanToString((int)(seconds / secondsInHour), "hour"); - } - - if (seconds < secondsInWeek) - { - return TimeSpanToString((int)(seconds / secondsInDay), "day"); - } - - if (seconds < secondsInYear) - { - return TimeSpanToString((int)(seconds / secondsInWeek), "week"); - } - - return TimeSpanToString((int)(seconds / secondsInYear), "years"); - } - - private static string TimeSpanToString(int count, string s) - { - return String.Format("{0} {1} ago", count.ToString(), Pluralize(s, count)); - } - - public static string Pluralize(string s, int count) - { - if (count < 2) - { - return s; - } - - return s + "s"; - } - - } - -} diff --git a/Debugger/Utils/DumpUtil.cs b/Debugger/Utils/DumpUtil.cs index 04e1de0..9f14977 100644 --- a/Debugger/Utils/DumpUtil.cs +++ b/Debugger/Utils/DumpUtil.cs @@ -1,21 +1,26 @@ -using System.IO; -using System.Linq; +using System; +using System.IO; using ColossalFramework.IO; using ColossalFramework.UI; +using ObjUnity3D; using UnityEngine; namespace ModTools.Utils { - public static class DumpUtil + internal static class DumpUtil { - public static void DumpAsset(string assetName, Mesh mesh, Material material, - Mesh lodMesh = null, Material lodMaterial = null) + public static void DumpAsset( + string assetName, + Mesh mesh, + Material material, + Mesh lodMesh = null, + Material lodMaterial = null) { - assetName = assetName.Replace("_Data", ""); - Log.Warning($"Dumping asset \"{assetName}\"..."); + assetName = assetName.Replace("_Data", string.Empty); + Logger.Warning($"Dumping asset \"{assetName}\"..."); DumpMeshAndTextures(assetName, mesh, material); DumpMeshAndTextures($"{assetName}_lod", lodMesh, lodMaterial); - Log.Warning($"Successfully dumped asset \"{assetName}\""); + Logger.Warning($"Successfully dumped asset \"{assetName}\""); var path = Path.Combine(DataLocation.addonsPath, "Import"); UIView.library.ShowModal("ExceptionPanel").SetMessage( "Asset dump completed", @@ -25,12 +30,13 @@ public static void DumpAsset(string assetName, Mesh mesh, Material material, public static void DumpMeshAndTextures(string assetName, Mesh mesh, Material material = null) { - assetName = assetName.Replace("_Data", "").LegalizeFileName(); + assetName = FileUtil.LegalizeFileName(assetName.Replace("_Data", string.Empty)); - if (mesh != null && mesh.isReadable) + if (mesh?.isReadable == true) { - MeshUtil.DumpMeshToOBJ(mesh, $"{assetName}.obj"); + DumpMeshToOBJ(mesh, $"{assetName}.obj"); } + if (material != null) { DumpTextures(assetName, material); @@ -39,20 +45,69 @@ public static void DumpMeshAndTextures(string assetName, Mesh mesh, Material mat public static void DumpTextures(string assetName, Material material) { - assetName = assetName.Replace("_Data", "").LegalizeFileName(); - DumpMainTex(assetName, (Texture2D) material.GetTexture("_MainTex")); - DumpACI(assetName, (Texture2D) material.GetTexture("_ACIMap")); - DumpXYS(assetName, (Texture2D) material.GetTexture("_XYSMap")); - DumpXYCA(assetName, (Texture2D) material.GetTexture("_XYCAMap")); + assetName = FileUtil.LegalizeFileName(assetName.Replace("_Data", string.Empty)); + DumpMainTex(assetName, (Texture2D)material.GetTexture("_MainTex")); + DumpACI(assetName, (Texture2D)material.GetTexture("_ACIMap")); + DumpXYS(assetName, (Texture2D)material.GetTexture("_XYSMap")); + DumpXYCA(assetName, (Texture2D)material.GetTexture("_XYCAMap")); DumpAPR(assetName, (Texture2D)material.GetTexture("_APRMap")); } + public static void DumpMeshToOBJ(Mesh mesh, string fileName) + { + fileName = Path.Combine(Path.Combine(DataLocation.addonsPath, "Import"), fileName); + if (File.Exists(fileName)) + { + File.Delete(fileName); + } + + var meshToDump = mesh; + + if (!mesh.isReadable) + { + Logger.Warning($"Mesh \"{mesh.name}\" is marked as non-readable, running workaround.."); + + try + { + // copy the relevant data to the temporary mesh + meshToDump = new Mesh + { + vertices = mesh.vertices, + colors = mesh.colors, + triangles = mesh.triangles, + normals = mesh.normals, + tangents = mesh.tangents, + }; + meshToDump.RecalculateBounds(); + } + catch (Exception ex) + { + Logger.Error($"Workaround failed with error - {ex.Message}"); + return; + } + } + + try + { + using (var stream = new FileStream(fileName, FileMode.Create)) + { + OBJLoader.ExportOBJ(meshToDump.EncodeOBJ(), stream); + Logger.Warning($"Dumped mesh \"{mesh.name}\" to \"{fileName}\""); + } + } + catch (Exception ex) + { + Logger.Error($"There was an error while trying to dump mesh \"{mesh.name}\" - {ex.Message}"); + } + } + private static void DumpMainTex(string assetName, Texture2D mainTex, bool extract = true) { if (mainTex == null) { return; } + if (extract) { var length = mainTex.width * mainTex.height; @@ -64,7 +119,6 @@ private static void DumpMainTex(string assetName, Texture2D mainTex, bool extrac { TextureUtil.DumpTextureToPNG(mainTex, $"{assetName}_MainTex"); } - } private static void DumpACI(string assetName, Texture2D aciMap, bool extract = true) @@ -73,6 +127,7 @@ private static void DumpACI(string assetName, Texture2D aciMap, bool extract = t { return; } + if (extract) { var length = aciMap.width * aciMap.height; @@ -96,6 +151,7 @@ private static void DumpXYS(string assetName, Texture2D xysMap, bool extract = t { return; } + if (extract) { var length = xysMap.width * xysMap.height; @@ -117,6 +173,7 @@ private static void DumpXYCA(string assetName, Texture2D xycaMap, bool extract = { return; } + if (extract) { var length = xycaMap.width * xycaMap.height; diff --git a/Debugger/Utils/FileUtil.cs b/Debugger/Utils/FileUtil.cs index d554a12..a60bace 100644 --- a/Debugger/Utils/FileUtil.cs +++ b/Debugger/Utils/FileUtil.cs @@ -6,28 +6,25 @@ using ColossalFramework.Plugins; using ICities; -namespace ModTools +namespace ModTools.Utils { - public static class FileUtil + internal static class FileUtil { - - public static List ListFilesInDirectory(string path, List _filesMustBeNull = null) + public static List ListFilesInDirectory(string path, List filesMustBeNull = null) { - _filesMustBeNull = _filesMustBeNull ?? new List(); + filesMustBeNull = filesMustBeNull ?? new List(); - foreach (string file in Directory.GetFiles(path)) + foreach (var file in Directory.GetFiles(path)) { - _filesMustBeNull.Add(file); + filesMustBeNull.Add(file); } - return _filesMustBeNull; + + return filesMustBeNull; } public static string FindPluginPath(Type type) { - var pluginManager = PluginManager.instance; - var plugins = pluginManager.GetPluginsInfo(); - - foreach (var item in plugins) + foreach (var item in PluginManager.instance.GetPluginsInfo()) { var instances = item.GetInstances(); var instance = instances.FirstOrDefault(); @@ -35,6 +32,7 @@ public static string FindPluginPath(Type type) { continue; } + foreach (var file in Directory.GetFiles(item.modPath)) { if (Path.GetExtension(file) == ".dll") @@ -43,19 +41,20 @@ public static string FindPluginPath(Type type) } } } - throw new Exception("Failed to find assembly!"); + + throw new FileNotFoundException("Failed to find assembly!"); } - public static string LegalizeFileName(this string illegal) + public static string LegalizeFileName(string illegal) { if (string.IsNullOrEmpty(illegal)) { return DateTime.Now.ToString("yyyyMMddhhmmss"); } - var regexSearch = new string(Path.GetInvalidFileNameChars()/* + new string(Path.GetInvalidPathChars()*/); + + var regexSearch = new string(Path.GetInvalidFileNameChars()); var r = new Regex($"[{Regex.Escape(regexSearch)}]"); return r.Replace(illegal, "_"); } } - -} +} \ No newline at end of file diff --git a/Debugger/Utils/GitVersion.cs b/Debugger/Utils/GitVersion.cs new file mode 100644 index 0000000..0f0fcac --- /dev/null +++ b/Debugger/Utils/GitVersion.cs @@ -0,0 +1,78 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +using System; +using System.Reflection; + +namespace ModTools.Utils +{ + /// + /// A helper class that interacts with the special types injected by the GitVersion toolset. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Git", Justification = "Reviewed")] + public static class GitVersion + { + private const string GitVersionTypeName = "GitVersionInformation"; + private const string VersionFieldName = "FullSemVer"; + + /// + /// Gets a string representation of the full semantic assembly version of the specified . + /// This assembly should be built using the GitVersion toolset; otherwise, a "?" version string will + /// be returned. + /// + /// + /// Thrown when the argument is null. + /// + /// An to get the version of. Should be built using the GitVersion toolset. + /// + /// A string representation of the full semantic version of the specified , + /// or "?" if the version could not be determined. + public static string GetAssemblyVersion(Assembly assembly) + { + if (assembly == null) + { + throw new ArgumentNullException(nameof(assembly)); + } + + var gitVersionInformationType = + assembly.GetType(GitVersionTypeName) + ?? assembly.GetType($"{assembly.GetName().Name}.{GitVersionTypeName}"); + + if (gitVersionInformationType == null) + { + Logger.Error("Attempting to retrieve the assembly version of an assembly that is built without GitVersion support."); + return "?"; + } + + var versionField = gitVersionInformationType.GetField(VersionFieldName); + if (versionField == null) + { + Logger.Error($"Internal error: the '{GitVersionTypeName}' type has no field '{VersionFieldName}'."); + return "?"; + } + + try + { + var version = versionField.GetValue(null) as string; + if (string.IsNullOrEmpty(version)) + { + Logger.Warning($"The '{GitVersionTypeName}.{VersionFieldName}' value is empty."); + return "?"; + } + + return version; + } + catch (TargetException) + { + Logger.Warning($"The API of GitVersion has changed: '{GitVersionTypeName}.{VersionFieldName}' is not static."); + return "?"; + } + catch (FieldAccessException) + { + Logger.Warning($"We are in restricted security context, the field '{GitVersionTypeName}.{VersionFieldName}' cannot be accessed."); + return "?"; + } + } + } +} diff --git a/Debugger/Utils/HashCodeUtil.cs b/Debugger/Utils/HashCodeUtil.cs deleted file mode 100644 index cea7a39..0000000 --- a/Debugger/Utils/HashCodeUtil.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2009 The Noda Time Authors. All rights reserved. -// Use of this source code is governed by the Apache License 2.0 - -using System.Diagnostics.CodeAnalysis; - -namespace ModTools -{ - /// - /// Provides method to help with generating hash codes for structures and classes. This handles - /// value types, nullable type, and objects. - /// - /// - /// The basic usage pattern is: - /// - /// - /// public override int GetHashCode() - /// { - /// int hash = HashCodeHelper.Initialize(); - /// hash = HashCodeHelper.Hash(hash, Field1); - /// hash = HashCodeHelper.Hash(hash, Field1); - /// hash = HashCodeHelper.Hash(hash, Field1); - /// ... - /// return hash; - /// } - /// - /// - /// - internal static class HashCodeUtil - { - /// - /// The multiplier for each value. - /// - private const int HashcodeMultiplier = 37; - - /// - /// The initial hash value. - /// - private const int HashcodeInitializer = 17; - - /// - /// Returns the initial value for a hash code. - /// - /// The initial interger value. - internal static int Initialize() { return HashcodeInitializer; } - - /// - /// Adds the hash value for the given value to the current hash and returns the new value. - /// - /// The type of the value being hashed. - /// The previous hash code. - /// The value to hash. - /// The new hash code. - internal static int Hash(int code, T value) - { - int hash = 0; - if (value != null) - { - hash = value.GetHashCode(); - } - return MakeHash(code, hash); - } - - /// - /// Adds the hash value for a int to the current hash value and returns the new value. - /// - /// The previous hash code. - /// The value to add to the hash code. - /// The new hash code. - [SuppressMessage("Microsoft.Usage", "CA2233:OperationsShouldNotOverflow", Justification = "Deliberately overflowing.")] - private static int MakeHash(int code, int value) - { - unchecked - { - code = (code * HashcodeMultiplier) + value; - } - return code; - } - } -} \ No newline at end of file diff --git a/Debugger/Utils/HashUtil.cs b/Debugger/Utils/HashUtil.cs index b98c75f..a4b2949 100644 --- a/Debugger/Utils/HashUtil.cs +++ b/Debugger/Utils/HashUtil.cs @@ -1,10 +1,18 @@ -using System; -using UnityEngine; +using UnityEngine; -namespace ModTools +namespace ModTools.Utils { - public static class HashUtil + internal static class HashUtil { + private static readonly long[] LargePrimes = new[] + { + 8100529L, + 12474907L, + 15485039L, + 21768739L, + 28644467L, + 32452681L, + }; public static long HashRect(Rect rect) { @@ -16,26 +24,8 @@ public static long HashRect(Rect rect) return state; } - public static string HashToString(long hash) - { - return String.Format("{0:X}", hash); - } - - private static long[] largePrimes = new[] - { - 8100529L, - 12474907L, - 15485039L, - 21768739L, - 28644467L, - 32452681L - }; - - private static void Accumulate(ref long state, float value, int index) - { - state ^= ((long)value) * largePrimes[index]; - } + public static string HashToString(long hash) => $"{hash:X}"; + private static void Accumulate(ref long state, float value, int index) => state ^= (long)value * LargePrimes[index]; } - -} +} \ No newline at end of file diff --git a/Debugger/Utils/MeshUtil.cs b/Debugger/Utils/MeshUtil.cs deleted file mode 100644 index dbb3bfb..0000000 --- a/Debugger/Utils/MeshUtil.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.IO; -using ColossalFramework.IO; -using ObjUnity3D; -using UnityEngine; - -namespace ModTools.Utils -{ - public static class MeshUtil - { - public static void DumpMeshToOBJ(Mesh mesh, string fileName) - { - fileName = Path.Combine(Path.Combine(DataLocation.addonsPath, "Import"), fileName); - if (File.Exists(fileName)) - { - File.Delete(fileName); - } - - Mesh meshToDump = mesh; - - if (!mesh.isReadable) - { - Log.Warning($"Mesh \"{mesh.name}\" is marked as non-readable, running workaround.."); - - try - { - // copy the relevant data to the temporary mesh - meshToDump = new Mesh - { - vertices = mesh.vertices, - colors = mesh.colors, - triangles = mesh.triangles, - normals = mesh.normals, - tangents = mesh.tangents - }; - meshToDump.RecalculateBounds(); - } - catch (Exception ex) - { - Log.Error($"Workaround failed with error - {ex.Message}"); - return; - } - } - - try - { - using (var stream = new FileStream(fileName, FileMode.Create)) - { - OBJLoader.ExportOBJ(meshToDump.EncodeOBJ(), stream); - stream.Close(); - Log.Warning($"Dumped mesh \"{((Mesh) mesh).name}\" to \"{fileName}\""); - } - } - catch (Exception ex) - { - Log.Error($"There was an error while trying to dump mesh \"{mesh.name}\" - {ex.Message}"); - } - } - } -} \ No newline at end of file diff --git a/Debugger/Utils/ParseHelper.cs b/Debugger/Utils/ParseHelper.cs new file mode 100644 index 0000000..71c3d19 --- /dev/null +++ b/Debugger/Utils/ParseHelper.cs @@ -0,0 +1,56 @@ +using System; + +namespace ModTools.Utils +{ + internal static class ParseHelper + { + public static bool TryParse(string text, Type targetType, out object result) + { + if (string.IsNullOrEmpty(text)) + { + result = default; + return false; + } + + try + { + result = Convert.ChangeType(text, targetType); + return true; + } + catch + { + result = default; + return false; + } + } + + public static bool TryParse(string text, out T result) + where T : struct + { + if (string.IsNullOrEmpty(text)) + { + result = default; + return false; + } + + try + { + return Parsers.TryParse(text, out result); + } + catch + { + result = default; + return false; + } + } + + private static class Parsers + { + private static readonly TryParseDelegate Parser = (TryParseDelegate)Delegate.CreateDelegate(typeof(TryParseDelegate), typeof(T), "TryParse"); + + private delegate bool TryParseDelegate(string text, out T result); + + public static bool TryParse(string text, out T result) => Parser(text, out result); + } + } +} diff --git a/Debugger/Utils/ReflectionUtil.cs b/Debugger/Utils/ReflectionUtil.cs deleted file mode 100644 index 486a562..0000000 --- a/Debugger/Utils/ReflectionUtil.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System.Reflection; - -namespace ModTools.Utils -{ - public static class ReflectionUtil - { - public static FieldInfo FindField(T o, string fieldName) - { - var fields = typeof(T).GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - - foreach (var f in fields) - { - if (f.Name == fieldName) - { - return f; - } - } - - return null; - } - - public static T GetFieldValue(FieldInfo field, object o) - { - return (T)field.GetValue(o); - } - - public static void SetFieldValue(FieldInfo field, object o, object value) - { - field.SetValue(o, value); - } - - public static Q GetPrivate(object o, string fieldName) - { - var fields = o.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - FieldInfo field = null; - - foreach (var f in fields) - { - if (f.Name == fieldName) - { - field = f; - break; - } - } - - return (Q)field.GetValue(o); - } - - public static void SetPrivate(object o, string fieldName, object value) - { - var fields = o.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - FieldInfo field = null; - - foreach (var f in fields) - { - if (f.Name == fieldName) - { - field = f; - break; - } - } - - field.SetValue(o, value); - } - } -} \ No newline at end of file diff --git a/Debugger/Utils/TextureUtil.cs b/Debugger/Utils/TextureUtil.cs index 987565c..6a13054 100644 --- a/Debugger/Utils/TextureUtil.cs +++ b/Debugger/Utils/TextureUtil.cs @@ -5,9 +5,8 @@ namespace ModTools.Utils { - public static class TextureUtil + internal static class TextureUtil { - public static void DumpTexture2D(Texture2D texture, string filename) { byte[] bytes; @@ -23,36 +22,15 @@ public static void DumpTexture2D(Texture2D texture, string filename) } catch (Exception ex) { - Log.Error("There was an error while dumping the texture - " + ex.Message); + Logger.Error("There was an error while dumping the texture - " + ex.Message); return; } } - File.WriteAllBytes(filename, bytes); - Log.Warning($"Texture dumped to \"{filename}\""); - } - - private static Texture2D MakeReadable(this Texture texture) - { - Log.Warning($"Texture \"{texture.name}\" is marked as read-only, running workaround.."); - var rt = RenderTexture.GetTemporary(texture.width, texture.height, 0); - Graphics.Blit(texture, rt); - var tex = ToTexture2D(rt); - RenderTexture.ReleaseTemporary(rt); - return tex; - } - private static Texture2D ToTexture2D(this RenderTexture rt) - { - var oldRt = RenderTexture.active; - RenderTexture.active = rt; - var tex = new Texture2D(rt.width, rt.height); - tex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0); - tex.Apply(); - RenderTexture.active = oldRt; - return tex; + File.WriteAllBytes(filename, bytes); + Logger.Warning($"Texture dumped to \"{filename}\""); } - public static Texture2D ToTexture2D(this Texture3D t3d) { var pixels = t3d.GetPixels(); @@ -66,10 +44,11 @@ public static Texture2D ToTexture2D(this Texture3D t3d) { for (var j = 0; j < height; j++) { - tex.SetPixel(j * width + i, (height - k - 1), pixels[width * depth * j + k * depth + i]); + tex.SetPixel(j * width + i, height - k - 1, pixels[width * depth * j + k * depth + i]); } } } + tex.Apply(); return tex; } @@ -87,30 +66,18 @@ public static Texture2D ToTexture2D(this Cubemap cubemap) return texture; } - private static void SetCubemapFace(Texture2D texture, CubemapFace face, Cubemap cubemap, int positionY, int positionX) - { - for (var x = 0; x < cubemap.height; x++) - { - for (var y = 0; y < cubemap.width; y++) - { - var color = cubemap.GetPixel(face, x, y); - texture.SetPixel(positionX * cubemap.width + x, positionY * cubemap.height + y, color); - } - } - } - public static void DumpTextureToPNG(Texture previewTexture, string filename = null) { if (string.IsNullOrEmpty(filename)) { - var filenamePrefix = $"rt_dump_{previewTexture.name.LegalizeFileName()}"; + var filenamePrefix = $"rt_dump_{FileUtil.LegalizeFileName(previewTexture.name)}"; if (!File.Exists($"{filenamePrefix}.png")) { filename = $"{filenamePrefix}.png"; } else { - int i = 1; + var i = 1; while (File.Exists($"{filenamePrefix}_{i}.png")) { i++; @@ -119,39 +86,38 @@ public static void DumpTextureToPNG(Texture previewTexture, string filename = nu filename = $"{filenamePrefix}_{i}.png"; } } - else + else if (!filename.EndsWith(".png")) { - if (!filename.EndsWith(".png")) - { - filename = $"{filename}.png"; - } + filename = $"{filename}.png"; } + filename = Path.Combine(Path.Combine(DataLocation.addonsPath, "Import"), filename); if (File.Exists(filename)) { File.Delete(filename); } - if (previewTexture is Texture2D) + + if (previewTexture is Texture2D texture2D) { - DumpTexture2D((Texture2D)previewTexture, filename); + DumpTexture2D(texture2D, filename); } - else if (previewTexture is RenderTexture) + else if (previewTexture is RenderTexture renderTexture) { - DumpTexture2D(((RenderTexture)previewTexture).ToTexture2D(), filename); - Log.Warning($"Texture dumped to \"{filename}\""); + DumpTexture2D(renderTexture.ToTexture2D(), filename); + Logger.Warning($"Texture dumped to \"{filename}\""); } - else if (previewTexture is Texture3D) + else if (previewTexture is Texture3D texture3D) { - DumpTexture2D(((Texture3D)previewTexture).ToTexture2D(), filename); + DumpTexture2D(texture3D.ToTexture2D(), filename); } - else if (previewTexture is Cubemap) + else if (previewTexture is Cubemap cubemap) { - DumpTexture2D(((Cubemap)previewTexture).ToTexture2D(), filename); + DumpTexture2D(cubemap.ToTexture2D(), filename); } else { - Log.Error($"Don't know how to dump type \"{previewTexture.GetType()}\""); + Logger.Error($"Don't know how to dump type \"{previewTexture.GetType()}\""); } } @@ -166,6 +132,7 @@ public static Color32[] TextureToColors(this Texture2D texture2D) { input = texture2D.MakeReadable().GetPixels32(); } + return input; } @@ -182,11 +149,12 @@ public static Color32[] Invert(this Color32[] colors) var result = new Color32[colors.Length]; for (var i = 0; i < colors.Length; i++) { - result[i].r = (byte) (byte.MaxValue - colors[i].r); + result[i].r = (byte)(byte.MaxValue - colors[i].r); result[i].g = (byte)(byte.MaxValue - colors[i].g); result[i].b = (byte)(byte.MaxValue - colors[i].b); result[i].a = (byte)(byte.MaxValue - colors[i].a); } + return result; } @@ -200,17 +168,106 @@ public static void ExtractChannels(this Texture2D texture, Color32[] r, Color32[ var bb = input[index].b; var aa = input[index].a; if (rinvert) + { rr = (byte)(byte.MaxValue - rr); + } + if (ginvert) + { gg = (byte)(byte.MaxValue - gg); + } + if (binvert) + { bb = (byte)(byte.MaxValue - bb); + } + if (ainvert) + { aa = (byte)(byte.MaxValue - aa); - if (r != null) if (ralpha) { r[index].r = rr; r[index].g = rr; r[index].b = rr; } else { r[index].r = rr; } - if (g != null) if (galpha) { g[index].r = gg; g[index].g = gg; g[index].b = gg; } else { g[index].g = gg; } - if (b != null) if (balpha) { b[index].r = bb; b[index].g = bb; b[index].b = bb; } else { b[index].b = bb; } - if (a != null) { a[index].r = aa; a[index].g = aa; a[index].b = aa;} + } + + if (r != null) + { + if (ralpha) + { + r[index].r = rr; + r[index].g = rr; + r[index].b = rr; + } + else + { + r[index].r = rr; + } + } + + if (g != null) + { + if (galpha) + { + g[index].r = gg; + g[index].g = gg; + g[index].b = gg; + } + else + { + g[index].g = gg; + } + } + + if (b != null) + { + if (balpha) + { + b[index].r = bb; + b[index].g = bb; + b[index].b = bb; + } + else + { + b[index].b = bb; + } + } + + if (a != null) + { + a[index].r = aa; + a[index].g = aa; + a[index].b = aa; + } + } + } + + private static Texture2D MakeReadable(this Texture texture) + { + Logger.Warning($"Texture \"{texture.name}\" is marked as read-only, running workaround.."); + var rt = RenderTexture.GetTemporary(texture.width, texture.height, 0); + Graphics.Blit(texture, rt); + var tex = ToTexture2D(rt); + RenderTexture.ReleaseTemporary(rt); + return tex; + } + + private static Texture2D ToTexture2D(this RenderTexture rt) + { + var oldRt = RenderTexture.active; + RenderTexture.active = rt; + var tex = new Texture2D(rt.width, rt.height); + tex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0); + tex.Apply(); + RenderTexture.active = oldRt; + return tex; + } + + private static void SetCubemapFace(Texture2D texture, CubemapFace face, Cubemap cubemap, int positionY, int positionX) + { + for (var x = 0; x < cubemap.height; x++) + { + for (var y = 0; y < cubemap.width; y++) + { + var color = cubemap.GetPixel(face, x, y); + texture.SetPixel(positionX * cubemap.width + x, positionY * cubemap.height + y, color); + } } } } diff --git a/Debugger/Utils/TypeUtil.cs b/Debugger/Utils/TypeUtil.cs index ce97129..f2823cc 100644 --- a/Debugger/Utils/TypeUtil.cs +++ b/Debugger/Utils/TypeUtil.cs @@ -4,93 +4,78 @@ using System.Linq; using System.Reflection; -namespace ModTools +namespace ModTools.Utils { - - public static class TypeUtil + internal static class TypeUtil { + private static Dictionary typeCache = new Dictionary(); public static bool IsSpecialType(Type t) { - if (t == typeof (System.Char)) return true; - if (t == typeof (System.String)) return true; - if (t == typeof (System.Boolean)) return true; - if (t == typeof (System.Single)) return true; - if (t == typeof (System.Double)) return true; - if (t == typeof (System.Byte)) return true; - if (t == typeof (System.Int32)) return true; - if (t == typeof (System.UInt32)) return true; - if (t == typeof (System.Int64)) return true; - if (t == typeof (System.UInt64)) return true; - if (t == typeof (System.Int16)) return true; - if (t == typeof (System.UInt16)) return true; - if (t == typeof (UnityEngine.Vector2)) return true; - if (t == typeof (UnityEngine.Vector3)) return true; - if (t == typeof (UnityEngine.Vector4)) return true; - if (t == typeof (UnityEngine.Quaternion)) return true; - if (t == typeof (UnityEngine.Color)) return true; - if (t == typeof (UnityEngine.Color32)) return true; - if (t.IsEnum) return true; - return false; + return t.IsPrimitive + || t.IsEnum + || t == typeof(string) + || t == typeof(UnityEngine.Vector2) + || t == typeof(UnityEngine.Vector3) + || t == typeof(UnityEngine.Vector4) + || t == typeof(UnityEngine.Quaternion) + || t == typeof(UnityEngine.Color) + || t == typeof(UnityEngine.Color32); } - public static bool IsBitmaskEnum(Type t) - { - return t.IsDefined(typeof(FlagsAttribute), false); - } + public static bool IsBitmaskEnum(Type t) => t.IsDefined(typeof(FlagsAttribute), false); public static bool IsTextureType(Type t) { - return t == typeof(UnityEngine.Texture) || t == typeof(UnityEngine.Texture2D) || - t == typeof(UnityEngine.RenderTexture) || t == typeof(UnityEngine.Texture3D) || t == typeof(UnityEngine.Cubemap); + return t == typeof(UnityEngine.Texture) || t == typeof(UnityEngine.Texture2D) + || t == typeof(UnityEngine.RenderTexture) || t == typeof(UnityEngine.Texture3D) || t == typeof(UnityEngine.Cubemap); } - public static bool IsMeshType(Type t) - { - return t == typeof(UnityEngine.Mesh); - } + public static bool IsMeshType(Type t) => t == typeof(UnityEngine.Mesh); public static MemberInfo[] GetAllMembers(Type type, bool recursive = false) - { - return GetMembersInternal(type, recursive, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); - } + => GetMembersInternal(type, recursive, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); + + public static void ClearTypeCache() => typeCache = new Dictionary(); - public static MemberInfo[] GetPublicMembers(Type type, bool recursive = false) + public static bool IsEnumerable(object myProperty) { - return GetMembersInternal(type, recursive, BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static); + return typeof(IEnumerable).IsInstanceOfType(myProperty) + || typeof(IEnumerable<>).IsInstanceOfType(myProperty); } - public static MemberInfo[] GetPrivateMembers(Type type, bool recursive = false) + public static bool IsCollection(object myProperty) { - return GetMembersInternal(type, recursive, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static); + return typeof(ICollection).IsAssignableFrom(myProperty.GetType()) + || typeof(ICollection<>).IsAssignableFrom(myProperty.GetType()); } - public static void ClearTypeCache() + public static bool IsList(object myProperty) { - _typeCache = new Dictionary(); + return typeof(IList).IsAssignableFrom(myProperty.GetType()) + || typeof(IList<>).IsAssignableFrom(myProperty.GetType()); } - private static Dictionary _typeCache = new Dictionary(); + public static FieldInfo FindField(Type type, string fieldName) + => Array.Find(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance), f => f.Name == fieldName); private static MemberInfo[] GetMembersInternal(Type type, bool recursive, BindingFlags bindingFlags) { - if (_typeCache.ContainsKey(type)) + if (typeCache.ContainsKey(type)) { - return _typeCache[type]; + return typeCache[type]; } var results = new Dictionary(); GetMembersInternal2(type, recursive, bindingFlags, results); var members = results.Values.ToArray(); - _typeCache[type] = members; + typeCache[type] = members; return members; } - private static void GetMembersInternal2(Type type, bool recursive, BindingFlags bindingFlags, - Dictionary outResults) + private static void GetMembersInternal2(Type type, bool recursive, BindingFlags bindingFlags, Dictionary outResults) { - var selfMembers = type.GetMembers(bindingFlags); - foreach (var member in selfMembers) + foreach (var member in type.GetMembers(bindingFlags)) { if (!outResults.ContainsKey(member.Name)) { @@ -98,41 +83,10 @@ private static void GetMembersInternal2(Type type, bool recursive, BindingFlags } } - if (recursive) + if (recursive && type.BaseType != null) { - if (type.BaseType != null) - { - GetMembersInternal2(type.BaseType, true, bindingFlags, outResults); - } + GetMembersInternal2(type.BaseType, true, bindingFlags, outResults); } } - - public static bool IsEnumerable(object myProperty) - { - if (typeof(IEnumerable).IsInstanceOfType(myProperty) - || typeof(IEnumerable<>).IsInstanceOfType(myProperty)) - return true; - - return false; - } - - public static bool IsCollection(object myProperty) - { - if (typeof(ICollection).IsAssignableFrom(myProperty.GetType()) - || typeof(ICollection<>).IsAssignableFrom(myProperty.GetType())) - return true; - - return false; - } - - public static bool IsList(object myProperty) - { - if (typeof(IList).IsAssignableFrom(myProperty.GetType()) - || typeof(IList<>).IsAssignableFrom(myProperty.GetType())) - return true; - - return false; - } } - -} +} \ No newline at end of file diff --git a/MECHJEB-LICENSE b/MECHJEB-LICENSE deleted file mode 100644 index 22880c1..0000000 --- a/MECHJEB-LICENSE +++ /dev/null @@ -1,637 +0,0 @@ -This software is released under the GNU GPL version 3, 29 July 2007. -The complete copy of the license is attached to this document. - ------- -SUMMARY: - - MechJeb2 Copyright (C) 2013 - This program comes with ABSOLUTELY NO WARRANTY! - This is free software, and you are welcome to redistribute it - under certain conditions, as outlined in the full content of - the GNU General Public License (GNU GPL), version 3, revision - date 29 June 2007. - ------- -FULL LICENSE CONTENT: - - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS \ No newline at end of file