From b70f65340214d099e9ce34b94025389aabe184f3 Mon Sep 17 00:00:00 2001 From: "George L. Albany" Date: Tue, 9 Nov 2021 01:28:24 -0500 Subject: [PATCH 01/14] Improved Options system Deprecates old options system, marked for deletion in next major version Enables overriding default behavior Default behavior saves and loads to configs according to its Id, and in accordance to default value Tab names and tab ids can be separated Tabs are optionally modifiable Tabs and Options store their position and size data in DrawData Simplifies PathfinderOptions immensely Manages config loading and registers saving in PostLoad DrawData allows storing and modifying position and size structs easily --- PathfinderAPI/GUI/DrawData.cs | 57 ++++++++ PathfinderAPI/Options/BasePluginOption.cs | 61 +++++++++ PathfinderAPI/Options/Options.cs | 2 + PathfinderAPI/Options/OptionsManager.cs | 70 +++++++++- PathfinderAPI/Options/PathfinderOptions.cs | 36 +---- .../Options/PathfinderOptionsMenu.cs | 29 ++-- PathfinderAPI/Options/PluginCheckbox.cs | 23 ++++ PathfinderAPI/Options/PluginOptionTab.cs | 127 ++++++++++++++++++ PathfinderAPI/PathfinderAPIPlugin.cs | 13 ++ 9 files changed, 376 insertions(+), 42 deletions(-) create mode 100644 PathfinderAPI/GUI/DrawData.cs create mode 100644 PathfinderAPI/Options/BasePluginOption.cs create mode 100644 PathfinderAPI/Options/PluginCheckbox.cs create mode 100644 PathfinderAPI/Options/PluginOptionTab.cs diff --git a/PathfinderAPI/GUI/DrawData.cs b/PathfinderAPI/GUI/DrawData.cs new file mode 100644 index 00000000..1e915f80 --- /dev/null +++ b/PathfinderAPI/GUI/DrawData.cs @@ -0,0 +1,57 @@ +using Microsoft.Xna.Framework; + +namespace Pathfinder.GUI +{ + public struct DrawData + { + public Rectangle _rectangle; + public Rectangle Rectangle { get => _rectangle; set => _rectangle = value; } + public int X { get => _rectangle.X; set => _rectangle.X = value; } + public int Y { get => _rectangle.Y; set => _rectangle.Y = value; } + public int Width { get => _rectangle.Width; set => _rectangle.Width = value; } + public int Height { get => _rectangle.Height; set => _rectangle.Height = value; } + public Point Position + { + get => new Point(X, Y); + set + { + X = value.X; + Y = value.Y; + } + } + public Point Size + { + get => new Point(Width, Height); + set + { + Width = value.X; + Height = value.Y; + } + } + public DrawData Add(int x, int? y = null, int width = 0, int? height = null) + { + if(!y.HasValue) y = x; + if(!height.HasValue) height = width; + X += x; + Y += y.Value; + Width += width; + Height = height.Value; + return this; + } + public DrawData Set(int? x = null, int? y = null, int? width = null, int? height = null) + { + if(x.HasValue) + X = x.Value; + if(y.HasValue) + Y = y.Value; + if(width.HasValue) + Width = width.Value; + if(height.HasValue) + Height = height.Value; + return this; + } + public static implicit operator Vector2(DrawData d) => new Vector2(d.X, d.Y); + public static implicit operator Point(DrawData d) => d.Position; + public static implicit operator Rectangle(DrawData d) => d.Rectangle; + } +} \ No newline at end of file diff --git a/PathfinderAPI/Options/BasePluginOption.cs b/PathfinderAPI/Options/BasePluginOption.cs new file mode 100644 index 00000000..f07158a4 --- /dev/null +++ b/PathfinderAPI/Options/BasePluginOption.cs @@ -0,0 +1,61 @@ +using BepInEx.Configuration; +using Pathfinder.GUI; +using Microsoft.Xna.Framework; + +namespace Pathfinder.Options; + +public interface IPluginOption +{ + PluginOptionTab Tab { get; set; } + string Id { get; } + DrawData DrawData { get; set; } + void OnRegistered(); + void OnDraw(GameTime gameTime); + void OnSave(ConfigFile config); + void OnLoad(ConfigFile config); +} + +public abstract class BasePluginOption : IPluginOption +{ + public PluginOptionTab Tab { get; set; } + + public DrawData DrawDataField; + public DrawData DrawData { get => DrawDataField; set => DrawDataField = value; } + public int HacknetGuiId { get; private set; } + + public virtual ValueT Value { get; set; } + public virtual ValueT DefaultValue { get; set; } = default; + public virtual string HeaderText { get; protected set; } + public virtual string DescriptionText { get; protected set; } + public virtual string Id { get; private set; } + + public bool TrySetHeaderText(string text) + { + HeaderText = text; + return HeaderText == text; + } + + public bool TrySetDescriptionText(string text) + { + DescriptionText = text; + return DescriptionText == text; + } + + public virtual void OnRegistered() + { + HacknetGuiId = PFButton.GetNextID(); + Id = string.Concat(HeaderText.Where(c => !char.IsWhiteSpace(c) && c != '=')); + } + + public abstract void OnDraw(GameTime gameTime); + public virtual void OnSave(ConfigFile config) + { + config.Bind(Tab.Id, Id, DefaultValue); + } + public virtual void OnLoad(ConfigFile config) + { + if(config.TryGetEntry(Tab.Id, Id, out var entry)) + Value = entry.Value; + else Value = DefaultValue; + } +} \ No newline at end of file diff --git a/PathfinderAPI/Options/Options.cs b/PathfinderAPI/Options/Options.cs index 95c6d630..eb671e07 100644 --- a/PathfinderAPI/Options/Options.cs +++ b/PathfinderAPI/Options/Options.cs @@ -4,6 +4,7 @@ namespace Pathfinder.Options; +[Obsolete("Use BasePluginOption")] public abstract class Option { public string Name; @@ -22,6 +23,7 @@ public Option(string name, string description="") public abstract void Draw(int x, int y); } +[Obsolete("Use PluginCheckbox")] public class OptionCheckbox : Option { public bool Value; diff --git a/PathfinderAPI/Options/OptionsManager.cs b/PathfinderAPI/Options/OptionsManager.cs index 871861d5..b663dd61 100644 --- a/PathfinderAPI/Options/OptionsManager.cs +++ b/PathfinderAPI/Options/OptionsManager.cs @@ -1,13 +1,15 @@ using Pathfinder.GUI; +using BepInEx.Configuration; namespace Pathfinder.Options; public static class OptionsManager { + [Obsolete("Use PluginTabs")] public readonly static Dictionary Tabs = new Dictionary(); + public readonly static List PluginTabs = new List(); - static OptionsManager() { } - + [Obsolete("Use RegisterOption or PluginOptionTab.AddOption")] public static void AddOption(string tag, Option opt) { if (!Tabs.TryGetValue(tag, out var tab)) { @@ -16,8 +18,70 @@ public static void AddOption(string tag, Option opt) } tab.Options.Add(opt); } + + public static bool TryGetTab(string tabName, out PluginOptionTab tab) + { + tab = PluginTabs.Find(t => t.Id == tabName); + return tab != null; + } + + public static PluginOptionTab GetTab(string tabName) + { + if(!TryGetTab(tabName, out var tab)) + return tab; + return null; + } + + public static PluginOptionTab RegisterTab(string tabName, string tabId = null) + { + if(GetTab(tabName) != null) + throw new InvalidOperationException("Can not deliberately register an existing tab"); + PluginOptionTab tab; + PluginTabs.Add(tab = new PluginOptionTab(tabName, tabId)); + tab.OnRegistered(); + return tab; + } + + public static PluginOptionTab GetOrRegisterTab(string tabName, string tabId = null) + { + if(!TryGetTab(tabName, out var tab)) + tab = RegisterTab(tabName, tabId); + return tab; + } + + public static T RegisterOption(string tabName, string tabId, T option) + where T : IPluginOption + { + GetOrRegisterTab(tabName, tabId).AddOption(option); + return option; + } + + public static T RegisterOption(string tabName, T option) + where T : IPluginOption + { + return RegisterOption(tabName, null, option); + } + + public static T RegisterOption(string tabName, string tabId = null) + where T : IPluginOption, new() + { + return RegisterOption(tabName, tabId, (T)Activator.CreateInstance(typeof(T))); + } + + public static void OnConfigSave(ConfigFile config) + { + foreach(var tab in PluginTabs) + tab.OnSave(config); + } + + public static void OnConfigLoad(ConfigFile config) + { + foreach(var tab in PluginTabs) + tab.OnLoad(config); + } } +[Obsolete("Use PluginOptionTab")] public class OptionsTab { public string Name; @@ -29,4 +93,4 @@ public class OptionsTab public OptionsTab(string name) { Name = name; } -} \ No newline at end of file +} diff --git a/PathfinderAPI/Options/PathfinderOptions.cs b/PathfinderAPI/Options/PathfinderOptions.cs index 35640124..f4969b8e 100644 --- a/PathfinderAPI/Options/PathfinderOptions.cs +++ b/PathfinderAPI/Options/PathfinderOptions.cs @@ -1,42 +1,20 @@ -using Pathfinder.Event; -using Pathfinder.Event.Options; - namespace Pathfinder.Options; internal static class PathfinderOptions { private const string OPTION_TAG = "Pathfinder"; - internal static OptionCheckbox PreloadAllThemes = new OptionCheckbox("Preload All Themes", + internal static PluginCheckbox PreloadAllThemes = new PluginCheckbox("Preload All Themes", "Preload all themes at extension start\nimproves performance at the cost of memory"); - internal static OptionCheckbox DisableSteamCloudError = new OptionCheckbox("Disable Steam Cloud Message", + internal static PluginCheckbox DisableSteamCloudError = new PluginCheckbox("Disable Steam Cloud Message", "Disables the Steam Cloud disabled message on the main menu"); - + [Util.Initialize] static void Initialize() { - OptionsManager.AddOption(OPTION_TAG, PreloadAllThemes); - OptionsManager.AddOption(OPTION_TAG, DisableSteamCloudError); - EventManager.AddHandler(onOptionsSave); - initConfig(); - } - - private static void initConfig() - { - var preloadAllThemesDef = PathfinderAPIPlugin.Config.Bind("PathfinderAPI", "PreloadAllThemes", false); - var disableSteamCloudDef = PathfinderAPIPlugin.Config.Bind("PathfinderAPI", "DisableSteamCloudMessage", false); - PreloadAllThemes.Value = preloadAllThemesDef.Value; - DisableSteamCloudError.Value = disableSteamCloudDef.Value; - } - - private static void onOptionsSave(CustomOptionsSaveEvent _) - { - PathfinderAPIPlugin.Config.TryGetEntry("PathfinderAPI", "PreloadAllThemes", out var preloadAllThemesVal); - PathfinderAPIPlugin.Config.TryGetEntry("PathfinderAPI", "DisableSteamCloudMessage", out var disableCloudMessage); - - preloadAllThemesVal.Value = PreloadAllThemes.Value; - disableCloudMessage.Value = DisableSteamCloudError.Value; - PathfinderAPIPlugin.Config.Save(); + OptionsManager.RegisterTab(OPTION_TAG) + .AddOption(PreloadAllThemes) + .AddOption(DisableSteamCloudError); } -} \ No newline at end of file +} diff --git a/PathfinderAPI/Options/PathfinderOptionsMenu.cs b/PathfinderAPI/Options/PathfinderOptionsMenu.cs index a9645cda..de6da9dd 100644 --- a/PathfinderAPI/Options/PathfinderOptionsMenu.cs +++ b/PathfinderAPI/Options/PathfinderOptionsMenu.cs @@ -13,7 +13,7 @@ namespace Pathfinder.Options; internal static class PathfinderOptionsMenu { private static bool isInPathfinderMenu = false; - private static string currentTabName = null; + internal static string currentTabName = null; private static PFButton ReturnButton = new PFButton(10, 10, 220, 54, "Back to Options", Color.Yellow); @@ -21,13 +21,13 @@ internal static class PathfinderOptionsMenu [HarmonyPatch(typeof(OptionsMenu), nameof(OptionsMenu.Draw))] internal static bool Draw(ref OptionsMenu __instance, GameTime gameTime) { - if (!isInPathfinderMenu) + if (!isInPathfinderMenu) return true; - + PostProcessor.begin(); - GuiData.startDraw(); - PatternDrawer.draw(new Rectangle(0, 0, __instance.ScreenManager.GraphicsDevice.Viewport.Width, __instance.ScreenManager.GraphicsDevice.Viewport.Height), 0.5f, Color.Black, new Color(2, 2, 2), GuiData.spriteBatch); - + GuiData.startDraw(); + PatternDrawer.draw(new Rectangle(0, 0, __instance.ScreenManager.GraphicsDevice.Viewport.Width, __instance.ScreenManager.GraphicsDevice.Viewport.Height), 0.5f, Color.Black, new Color(2, 2, 2), GuiData.spriteBatch); + if (ReturnButton.Do()) { currentTabName = null; @@ -39,10 +39,19 @@ internal static bool Draw(ref OptionsMenu __instance, GameTime gameTime) return false; } + #pragma warning disable 618 var tabs = OptionsManager.Tabs; - + #pragma warning restore 618 + int tabX = 10; + foreach (var tab in OptionsManager.PluginTabs) + { + tab.ButtonData = tab.ButtonData.Set(tabX); + tab.OnDraw(gameTime); + tabX += 10 + tab.ButtonData.Width; + } + foreach (var tab in tabs.Values) { if (currentTabName == null) @@ -69,7 +78,7 @@ internal static bool Draw(ref OptionsMenu __instance, GameTime gameTime) } GuiData.endDraw(); - PostProcessor.end(); + PostProcessor.end(); return false; } @@ -80,7 +89,7 @@ internal static bool Draw(ref OptionsMenu __instance, GameTime gameTime) internal static void BeforeEndDrawOptions(ILContext il) { ILCursor c = new ILCursor(il); - + c.GotoNext(MoveType.AfterLabel, x => x.MatchCallOrCallvirt(AccessTools.Method(typeof(GuiData), nameof(GuiData.endDraw)))); c.EmitDelegate(() => @@ -91,4 +100,4 @@ internal static void BeforeEndDrawOptions(ILContext il) } }); } -} \ No newline at end of file +} diff --git a/PathfinderAPI/Options/PluginCheckbox.cs b/PathfinderAPI/Options/PluginCheckbox.cs new file mode 100644 index 00000000..5f9acd9b --- /dev/null +++ b/PathfinderAPI/Options/PluginCheckbox.cs @@ -0,0 +1,23 @@ +using BepInEx.Configuration; +using Hacknet.Gui; +using Microsoft.Xna.Framework; + +namespace Pathfinder.Options; + +public class PluginCheckbox : BasePluginOption +{ + public PluginCheckbox(string headerText, string descriptionText = null, bool defaultValue = false) + { + HeaderText = headerText; + DescriptionText = descriptionText; + DefaultValue = defaultValue; + } + + public override void OnDraw(GameTime gameTime) + { + TextItem.doLabel(DrawDataField, HeaderText, null, 200); + Value = CheckBox.doCheckBox(HacknetGuiId, DrawDataField.X, DrawDataField.Y + 34, Value, null); + + TextItem.doSmallLabel(DrawDataField.Add(32, 30), DescriptionText, null); + } +} \ No newline at end of file diff --git a/PathfinderAPI/Options/PluginOptionTab.cs b/PathfinderAPI/Options/PluginOptionTab.cs new file mode 100644 index 00000000..ef13728d --- /dev/null +++ b/PathfinderAPI/Options/PluginOptionTab.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using BepInEx.Configuration; +using Hacknet.Gui; +using Microsoft.Xna.Framework; +using Pathfinder.GUI; + +namespace Pathfinder.Options; + +public class PluginOptionTab : IReadOnlyList +{ + private List options = new List(); + public string Id { get; private set; } + public string TabName { get; private set; } + public int HacknetGuiId { get; } + public bool IsRegistered { get; internal set; } + public DrawData ButtonData { get; set; } = new DrawData + { + X = 0, + Y = 70, + Width = 128, + Height = 20 + }; + + public PluginOptionTab(string tabName, string tabId = null) + { + TabName = tabName; + Id = tabId ?? string.Concat(TabName.Where(c => !char.IsWhiteSpace(c) && c != '=')); + HacknetGuiId = PFButton.GetNextID(); + } + + public int Count => options.Count; + + public IPluginOption this[int index] => options[index]; + + public PluginOptionTab AddOption(IPluginOption option) + { + if(options.Any(p => p.Id == option.Id)) + throw new InvalidOperationException("Option tabs may not have two options with the same id"); + options.Add(option); + option.Tab = this; + if(IsRegistered) + { + option.OnRegistered(); + int optX = ButtonData.Rectangle.Bottom, optY = 110; + foreach (var opt in this) + { + option.DrawData = option.DrawData.Set(optX, optY); + optY += 10 + option.DrawData.Height; + } + } + return this; + } + + public IPluginOption GetOption(string id) + { + return options.Find(p => p.Id == id); + } + + public OptionT GetOption(string id) where OptionT : IPluginOption + => (OptionT)GetOption(id); + + public OptionT GetOptionAs(string id) where OptionT : class, IPluginOption + => GetOption(id) as OptionT; + + public bool RemoveOption(IPluginOption option) + { + return options.Remove(option); + } + + public bool RemoveOption(string id) + { + return RemoveOption(options.Find(p => p.Id == id)); + } + + public virtual void OnRegistered() + { + foreach(var opt in options) + opt.OnRegistered(); + int optX = ButtonData.Rectangle.Bottom, optY = 110; + foreach (var option in this) + { + option.DrawData = option.DrawData.Set(optX, optY); + optY += 10 + option.DrawData.Height; + } + + } + + public virtual void OnSave(ConfigFile config) + { + foreach(var opt in options) + opt.OnSave(config); + } + + public virtual void OnLoad(ConfigFile config) + { + foreach(var opt in options) + opt.OnLoad(config); + } + + public virtual void OnDraw(GameTime gameTime) + { + if (PathfinderOptionsMenu.currentTabName == null) + PathfinderOptionsMenu.currentTabName = TabName; + var active = PathfinderOptionsMenu.currentTabName == TabName; + // Display tab button + if (Button.doButton(HacknetGuiId, ButtonData.X, ButtonData.Y, ButtonData.Width, ButtonData.Height, TabName, active ? Color.Green : Color.Gray)) + { + PathfinderOptionsMenu.currentTabName = TabName; + return; + } + + if (PathfinderOptionsMenu.currentTabName != TabName) + return; + + // Display options + foreach (var option in this) + option.OnDraw(gameTime); + } + + public IEnumerator GetEnumerator() + => options.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} \ No newline at end of file diff --git a/PathfinderAPI/PathfinderAPIPlugin.cs b/PathfinderAPI/PathfinderAPIPlugin.cs index 372b7e69..b7d3e9d3 100644 --- a/PathfinderAPI/PathfinderAPIPlugin.cs +++ b/PathfinderAPI/PathfinderAPIPlugin.cs @@ -3,6 +3,9 @@ using BepInEx.Hacknet; using HarmonyLib; using Pathfinder.Meta; +using Pathfinder.Event; +using Pathfinder.Event.Options; +using Pathfinder.Options; using Pathfinder.Util; using System.Globalization; @@ -41,4 +44,14 @@ public override bool Load() CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture; return true; } + + public override void PostLoad() + { + OptionsManager.OnConfigLoad(Config); + EventManager.AddHandler(_ => + { + OptionsManager.OnConfigSave(Config); + Config.Save(); + }); + } } \ No newline at end of file From 692d78c8e479aff1dfe9bb6401cc728b3ecdc574 Mon Sep 17 00:00:00 2001 From: "George L. Albany" Date: Tue, 9 Nov 2021 03:10:33 -0500 Subject: [PATCH 02/14] Added capability to define config descriptions for options Updater now uses new option/config system --- PathfinderAPI/Options/BasePluginOption.cs | 7 ++++++ PathfinderAPI/Options/PluginCheckbox.cs | 3 ++- PathfinderUpdater/MainMenuOverride.cs | 28 +++++++++++++++++------ 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/PathfinderAPI/Options/BasePluginOption.cs b/PathfinderAPI/Options/BasePluginOption.cs index f07158a4..2a9d2ef4 100644 --- a/PathfinderAPI/Options/BasePluginOption.cs +++ b/PathfinderAPI/Options/BasePluginOption.cs @@ -27,6 +27,7 @@ public abstract class BasePluginOption : IPluginOption public virtual ValueT DefaultValue { get; set; } = default; public virtual string HeaderText { get; protected set; } public virtual string DescriptionText { get; protected set; } + public virtual string ConfigDescription { get; protected set; } public virtual string Id { get; private set; } public bool TrySetHeaderText(string text) @@ -41,6 +42,12 @@ public bool TrySetDescriptionText(string text) return DescriptionText == text; } + public bool TrySetConfigDescription(string desc) + { + ConfigDescription = desc; + return ConfigDescription == desc; + } + public virtual void OnRegistered() { HacknetGuiId = PFButton.GetNextID(); diff --git a/PathfinderAPI/Options/PluginCheckbox.cs b/PathfinderAPI/Options/PluginCheckbox.cs index 5f9acd9b..db1d3208 100644 --- a/PathfinderAPI/Options/PluginCheckbox.cs +++ b/PathfinderAPI/Options/PluginCheckbox.cs @@ -6,11 +6,12 @@ namespace Pathfinder.Options; public class PluginCheckbox : BasePluginOption { - public PluginCheckbox(string headerText, string descriptionText = null, bool defaultValue = false) + public PluginCheckbox(string headerText, string descriptionText = null, bool defaultValue = false, string configDesc = null) { HeaderText = headerText; DescriptionText = descriptionText; DefaultValue = defaultValue; + ConfigDescription = configDesc; } public override void OnDraw(GameTime gameTime) diff --git a/PathfinderUpdater/MainMenuOverride.cs b/PathfinderUpdater/MainMenuOverride.cs index dd94b81e..b3e274b5 100644 --- a/PathfinderUpdater/MainMenuOverride.cs +++ b/PathfinderUpdater/MainMenuOverride.cs @@ -12,9 +12,23 @@ namespace PathfinderUpdater; [HarmonyPatch] internal static class MainMenuOverride { - private static OptionCheckbox IsEnabledBox = new OptionCheckbox("Enabled", "Enables the auto updater", PathfinderUpdaterPlugin.IsEnabled.Value); - private static OptionCheckbox IncludePrerelease = new OptionCheckbox("IncludePreReleases", "Autoupdate to pre-releases", PathfinderUpdaterPlugin.EnablePreReleases.Value); - internal static OptionCheckbox NoRestartPrompt = new OptionCheckbox("NoRestartPrompt", "Prevents a restart prompt appearing"); + internal static PluginCheckbox IsEnabledBox = new PluginCheckbox( + "Enabled", + "Enables the auto updater", + true, + "Enables or disables automatic updates for PathfinderAPI" + ); + internal static PluginCheckbox IncludePrerelease = new PluginCheckbox( + "IncludePreReleases", + "Autoupdate to pre-releases", + configDesc: "Whether or not to automatically update to beta versions" + ); + internal static PluginCheckbox NoRestartPrompt = new PluginCheckbox( + "NoRestartPrompt", + "Prevents a restart prompt appearing", + configDesc: "Whether ot not the restart prompt will automatically appear when the update is finished" + ); + private static PFButton CheckForUpdate = new PFButton(10, 10, 160, 30, "Check For Update", new Color(255, 255, 87)); private const string UPDATE_STRING = "Update"; private static PFButton PerformUpdate = new PFButton(180, 10, 160, 30, UPDATE_STRING); @@ -25,10 +39,10 @@ internal static class MainMenuOverride [HarmonyPatch(typeof(Program), nameof(Program.Main))] internal static void PFAPILoaded() { - OptionsManager.AddOption("Updater", IsEnabledBox); - OptionsManager.AddOption("Updater", IncludePrerelease); - OptionsManager.AddOption("Updater", NoRestartPrompt); - EventManager.AddHandler(OptionsSaved); + OptionsManager.GetOrRegisterTab("Updater", "AutoUpdater") + .AddOption(IsEnabledBox) + .AddOption(IncludePrerelease) + .AddOption(NoRestartPrompt); EventManager.AddHandler(OnDrawMainMenu); CanPerformUpdate = PathfinderUpdaterPlugin.NeedsUpdate; } From 84381e2fcc62da2f54215ec85c763be857ba7540 Mon Sep 17 00:00:00 2001 From: "George L. Albany" Date: Mon, 25 Apr 2022 05:29:00 -0400 Subject: [PATCH 03/14] Update BasePluginOption, PluginCheckbox, and PluginOptionTab to C# 10 MainMenuOverride: Changed IsEnabledBox, IncludePrerelease, and NotRestartPrompt to PluginCheckbox Modify PFAPILoaded to use OptionsManager Tab PluginOptionTab registry Removed OptionsSaved method as superflous Removed PathfinderUpdaterPlugin's superflous ConfigEntry variables, relies on MainMenuOverride option values Changed RestartPopupScreen.ExitScreen to only update MainMenuOverride.RestartPrompt.Value --- PathfinderAPI/Options/BasePluginOption.cs | 2 +- PathfinderAPI/Options/PluginCheckbox.cs | 2 +- PathfinderUpdater/MainMenuOverride.cs | 9 --------- PathfinderUpdater/PathfinderUpdaterPlugin.cs | 3 --- PathfinderUpdater/RestartPopupScreen.cs | 3 +-- 5 files changed, 3 insertions(+), 16 deletions(-) diff --git a/PathfinderAPI/Options/BasePluginOption.cs b/PathfinderAPI/Options/BasePluginOption.cs index 2a9d2ef4..e7d6800f 100644 --- a/PathfinderAPI/Options/BasePluginOption.cs +++ b/PathfinderAPI/Options/BasePluginOption.cs @@ -57,7 +57,7 @@ public virtual void OnRegistered() public abstract void OnDraw(GameTime gameTime); public virtual void OnSave(ConfigFile config) { - config.Bind(Tab.Id, Id, DefaultValue); + config.Bind(Tab.Id, Id, DefaultValue, ConfigDescription); } public virtual void OnLoad(ConfigFile config) { diff --git a/PathfinderAPI/Options/PluginCheckbox.cs b/PathfinderAPI/Options/PluginCheckbox.cs index db1d3208..ff465c86 100644 --- a/PathfinderAPI/Options/PluginCheckbox.cs +++ b/PathfinderAPI/Options/PluginCheckbox.cs @@ -21,4 +21,4 @@ public override void OnDraw(GameTime gameTime) TextItem.doSmallLabel(DrawDataField.Add(32, 30), DescriptionText, null); } -} \ No newline at end of file +} diff --git a/PathfinderUpdater/MainMenuOverride.cs b/PathfinderUpdater/MainMenuOverride.cs index b3e274b5..a6913124 100644 --- a/PathfinderUpdater/MainMenuOverride.cs +++ b/PathfinderUpdater/MainMenuOverride.cs @@ -47,15 +47,6 @@ internal static void PFAPILoaded() CanPerformUpdate = PathfinderUpdaterPlugin.NeedsUpdate; } - internal static void OptionsSaved(CustomOptionsSaveEvent args) - { - PathfinderUpdaterPlugin.IsEnabled.Value = IsEnabledBox.Value; - PathfinderUpdaterPlugin.EnablePreReleases.Value = IncludePrerelease.Value; - PathfinderUpdaterPlugin.NoRestartPrompt.Value = NoRestartPrompt.Value; - if(popupScreen != null) - popupScreen.NoRestartPrompt.Value = PathfinderUpdaterPlugin.NoRestartPrompt.Value; - } - internal static async Task PerformCheckAndUpdateButtonAsync() { PerformUpdate.Text = UPDATE_STRING; diff --git a/PathfinderUpdater/PathfinderUpdaterPlugin.cs b/PathfinderUpdater/PathfinderUpdaterPlugin.cs index 71eb798e..139a12ce 100644 --- a/PathfinderUpdater/PathfinderUpdaterPlugin.cs +++ b/PathfinderUpdater/PathfinderUpdaterPlugin.cs @@ -47,12 +47,9 @@ public override bool Load() { Config = base.Config; - IsEnabled = Config.Bind("AutoUpdater", "EnableAutoUpdates", true, "Enables or disables automatic updates for PathfinderAPI"); - EnablePreReleases = Config.Bind("AutoUpdater", "UsePreReleases", false, "Whether or not to automatically update to beta versions"); AcceptedUpdate = Config.Bind("AutoUpdater", "LatestAcceptedUpdate", "", "Used internally to keep track of whether you accepted the update or not"); CurrentVersion = Config.Bind("AutoUpdater", "CurrentVersion", HacknetChainloader.VERSION, "Used internally to keep track of version.\nIf you want to skip updating to a version but keep the updater on, set this manually to the latest verison."); - NoRestartPrompt = Config.Bind("AutoUpdater", "NoRestartPrompt", false, "Whether ot not the restart prompt will automatically appear when the update is finished"); HarmonyInstance.PatchAll(Assembly.GetExecutingAssembly()); if (Type.GetType("Mono.Runtime") != null) diff --git a/PathfinderUpdater/RestartPopupScreen.cs b/PathfinderUpdater/RestartPopupScreen.cs index dde4ed16..d35c9ec5 100644 --- a/PathfinderUpdater/RestartPopupScreen.cs +++ b/PathfinderUpdater/RestartPopupScreen.cs @@ -43,7 +43,6 @@ public override void Draw(GameTime gameTime) public new void ExitScreen() { base.ExitScreen(); - PathfinderUpdaterPlugin.NoRestartPrompt.Value = NoRestartPrompt.Value; - MainMenuOverride.NoRestartPrompt.Value = PathfinderUpdaterPlugin.NoRestartPrompt.Value; + MainMenuOverride.NoRestartPrompt.Value = NoRestartPrompt.Value; } } \ No newline at end of file From 0fed205a99604664eb97a0414bdb2415ee5eff4e Mon Sep 17 00:00:00 2001 From: "George L. Albany" Date: Mon, 25 Apr 2022 05:59:51 -0400 Subject: [PATCH 04/14] OptionsManager tab methods now use generics Eases the construction of custom tabs GUI.DrawData updated to C#10 --- PathfinderAPI/GUI/DrawData.cs | 91 ++++++++++++------------- PathfinderAPI/Options/OptionsManager.cs | 35 +++++++--- 2 files changed, 71 insertions(+), 55 deletions(-) diff --git a/PathfinderAPI/GUI/DrawData.cs b/PathfinderAPI/GUI/DrawData.cs index 1e915f80..2477c38d 100644 --- a/PathfinderAPI/GUI/DrawData.cs +++ b/PathfinderAPI/GUI/DrawData.cs @@ -1,57 +1,56 @@ using Microsoft.Xna.Framework; -namespace Pathfinder.GUI +namespace Pathfinder.GUI; + +public struct DrawData { - public struct DrawData + public Rectangle _rectangle; + public Rectangle Rectangle { get => _rectangle; set => _rectangle = value; } + public int X { get => _rectangle.X; set => _rectangle.X = value; } + public int Y { get => _rectangle.Y; set => _rectangle.Y = value; } + public int Width { get => _rectangle.Width; set => _rectangle.Width = value; } + public int Height { get => _rectangle.Height; set => _rectangle.Height = value; } + public Point Position { - public Rectangle _rectangle; - public Rectangle Rectangle { get => _rectangle; set => _rectangle = value; } - public int X { get => _rectangle.X; set => _rectangle.X = value; } - public int Y { get => _rectangle.Y; set => _rectangle.Y = value; } - public int Width { get => _rectangle.Width; set => _rectangle.Width = value; } - public int Height { get => _rectangle.Height; set => _rectangle.Height = value; } - public Point Position + get => new Point(X, Y); + set { - get => new Point(X, Y); - set - { - X = value.X; - Y = value.Y; - } + X = value.X; + Y = value.Y; } - public Point Size + } + public Point Size + { + get => new Point(Width, Height); + set { - get => new Point(Width, Height); - set - { - Width = value.X; - Height = value.Y; - } + Width = value.X; + Height = value.Y; } - public DrawData Add(int x, int? y = null, int width = 0, int? height = null) - { - if(!y.HasValue) y = x; - if(!height.HasValue) height = width; - X += x; - Y += y.Value; - Width += width; + } + public DrawData Add(int x, int? y = null, int width = 0, int? height = null) + { + if(!y.HasValue) y = x; + if(!height.HasValue) height = width; + X += x; + Y += y.Value; + Width += width; + Height = height.Value; + return this; + } + public DrawData Set(int? x = null, int? y = null, int? width = null, int? height = null) + { + if(x.HasValue) + X = x.Value; + if(y.HasValue) + Y = y.Value; + if(width.HasValue) + Width = width.Value; + if(height.HasValue) Height = height.Value; - return this; - } - public DrawData Set(int? x = null, int? y = null, int? width = null, int? height = null) - { - if(x.HasValue) - X = x.Value; - if(y.HasValue) - Y = y.Value; - if(width.HasValue) - Width = width.Value; - if(height.HasValue) - Height = height.Value; - return this; - } - public static implicit operator Vector2(DrawData d) => new Vector2(d.X, d.Y); - public static implicit operator Point(DrawData d) => d.Position; - public static implicit operator Rectangle(DrawData d) => d.Rectangle; + return this; } + public static implicit operator Vector2(DrawData d) => new Vector2(d.X, d.Y); + public static implicit operator Point(DrawData d) => d.Position; + public static implicit operator Rectangle(DrawData d) => d.Rectangle; } \ No newline at end of file diff --git a/PathfinderAPI/Options/OptionsManager.cs b/PathfinderAPI/Options/OptionsManager.cs index b663dd61..c967ccf6 100644 --- a/PathfinderAPI/Options/OptionsManager.cs +++ b/PathfinderAPI/Options/OptionsManager.cs @@ -19,36 +19,53 @@ public static void AddOption(string tag, Option opt) tab.Options.Add(opt); } - public static bool TryGetTab(string tabName, out PluginOptionTab tab) + public static bool TryGetTab(string tabId, out TabT tab) + where TabT : PluginOptionTab { - tab = PluginTabs.Find(t => t.Id == tabName); + tab = PluginTabs.Find(t => t.Id == tabId) as TabT; return tab != null; } - public static PluginOptionTab GetTab(string tabName) + public static PluginOptionTab GetTab(string tabId) + where TabT : PluginOptionTab { - if(!TryGetTab(tabName, out var tab)) + if(!TryGetTab(tabId, out TabT tab)) return tab; return null; } public static PluginOptionTab RegisterTab(string tabName, string tabId = null) { - if(GetTab(tabName) != null) - throw new InvalidOperationException("Can not deliberately register an existing tab"); - PluginOptionTab tab; - PluginTabs.Add(tab = new PluginOptionTab(tabName, tabId)); + if(GetTab(tabId ?? string.Concat(tabName.Where(c => !char.IsWhiteSpace(c) && c != '='))) != null) + throw new InvalidOperationException("Can not register tabs with a registered id"); + return RegisterTab(new PluginOptionTab(tabName, tabId)); + } + + public static TabT RegisterTab(TabT tab) + where TabT : PluginOptionTab + { + if(GetTab(tab.Id) != null) + throw new InvalidOperationException("Can not register tabs with a registered id"); + PluginTabs.Add(tab); tab.OnRegistered(); return tab; } public static PluginOptionTab GetOrRegisterTab(string tabName, string tabId = null) { - if(!TryGetTab(tabName, out var tab)) + if(!TryGetTab(tabId ?? string.Concat(tabName.Where(c => !char.IsWhiteSpace(c) && c != '=')), out PluginOptionTab tab)) tab = RegisterTab(tabName, tabId); return tab; } + public static TabT GetOrRegisterTab(string tabName, string tabId, Func generatorFunc) + where TabT : PluginOptionTab + { + if(!TryGetTab(tabId ?? string.Concat(tabName.Where(c => !char.IsWhiteSpace(c) && c != '=')), out TabT tab)) + tab = RegisterTab(generatorFunc()); + return tab; + } + public static T RegisterOption(string tabName, string tabId, T option) where T : IPluginOption { From 4700e4547d63a5d84726f2fe0c913f89ba169cac Mon Sep 17 00:00:00 2001 From: "George L. Albany" Date: Mon, 25 Apr 2022 19:23:12 -0400 Subject: [PATCH 05/14] DrawData: Renamed to PluginOptionDrawData X, Y, Width, and Height made as stored nullable ints instead Modified Add method for ease of convenience Added QuickAdd for simpler alternative to modified Add Corrected cases of PluginOptionDrawData's nullable ints usage Corrected ambiguous generic type references for GetTab in OptionsManager PathfinderOptionsMenu: Renamed currentTabName to public CurrentTabId { get; private set; } Added SetCurrentTab(string), SetCurrentTab(PlugionOptionTab), and SetCurrentTab(OptionsTab) for tab registry safety PlugionOptionTab: Move option draw data setting functionality to _SetDrawPositions method Correct options OnDraw calls to use gameTime --- PathfinderAPI/GUI/DrawData.cs | 56 --------- PathfinderAPI/GUI/PluginOptionDrawData.cs | 109 ++++++++++++++++++ PathfinderAPI/Options/BasePluginOption.cs | 6 +- PathfinderAPI/Options/OptionsManager.cs | 4 +- .../Options/PathfinderOptionsMenu.cs | 47 ++++++-- PathfinderAPI/Options/PluginCheckbox.cs | 4 +- PathfinderAPI/Options/PluginOptionTab.cs | 48 ++++---- 7 files changed, 182 insertions(+), 92 deletions(-) delete mode 100644 PathfinderAPI/GUI/DrawData.cs create mode 100644 PathfinderAPI/GUI/PluginOptionDrawData.cs diff --git a/PathfinderAPI/GUI/DrawData.cs b/PathfinderAPI/GUI/DrawData.cs deleted file mode 100644 index 2477c38d..00000000 --- a/PathfinderAPI/GUI/DrawData.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Microsoft.Xna.Framework; - -namespace Pathfinder.GUI; - -public struct DrawData -{ - public Rectangle _rectangle; - public Rectangle Rectangle { get => _rectangle; set => _rectangle = value; } - public int X { get => _rectangle.X; set => _rectangle.X = value; } - public int Y { get => _rectangle.Y; set => _rectangle.Y = value; } - public int Width { get => _rectangle.Width; set => _rectangle.Width = value; } - public int Height { get => _rectangle.Height; set => _rectangle.Height = value; } - public Point Position - { - get => new Point(X, Y); - set - { - X = value.X; - Y = value.Y; - } - } - public Point Size - { - get => new Point(Width, Height); - set - { - Width = value.X; - Height = value.Y; - } - } - public DrawData Add(int x, int? y = null, int width = 0, int? height = null) - { - if(!y.HasValue) y = x; - if(!height.HasValue) height = width; - X += x; - Y += y.Value; - Width += width; - Height = height.Value; - return this; - } - public DrawData Set(int? x = null, int? y = null, int? width = null, int? height = null) - { - if(x.HasValue) - X = x.Value; - if(y.HasValue) - Y = y.Value; - if(width.HasValue) - Width = width.Value; - if(height.HasValue) - Height = height.Value; - return this; - } - public static implicit operator Vector2(DrawData d) => new Vector2(d.X, d.Y); - public static implicit operator Point(DrawData d) => d.Position; - public static implicit operator Rectangle(DrawData d) => d.Rectangle; -} \ No newline at end of file diff --git a/PathfinderAPI/GUI/PluginOptionDrawData.cs b/PathfinderAPI/GUI/PluginOptionDrawData.cs new file mode 100644 index 00000000..f04fa203 --- /dev/null +++ b/PathfinderAPI/GUI/PluginOptionDrawData.cs @@ -0,0 +1,109 @@ +using Microsoft.Xna.Framework; + +namespace Pathfinder.GUI; + +public struct PluginOptionDrawData +{ + public int? X; + public int? Y; + public int? Width; + public int? Height; + public Rectangle Rectangle + { + get => new Rectangle(X.GetValueOrDefault(), Y.GetValueOrDefault(), Width.GetValueOrDefault(), Height.GetValueOrDefault()); + set + { + X = value.X; + Y = value.Y; + Width = value.Width; + Height = value.Height; + } + } + public Vector2 Vector2 + { + get => new Vector2(X.GetValueOrDefault(), Y.GetValueOrDefault()); + set + { + X = (int)value.X; + Y = (int)value.Y; + } + } + public Point Position + { + get => new Point(X.GetValueOrDefault(), Y.GetValueOrDefault()); + set + { + X = value.X; + Y = value.Y; + } + } + public Point Size + { + get => new Point(Width.GetValueOrDefault(), Height.GetValueOrDefault()); + set + { + Width = value.X; + Height = value.Y; + } + } + + public PluginOptionDrawData QuickAdd(int x = 0, int y = 0, int width = 0, int height = 0) + { + X = x + X.GetValueOrDefault(); + Y = y + Y.GetValueOrDefault(); + Width = width + Width.GetValueOrDefault(); + Height = height + Height.GetValueOrDefault(); + return this; + } + + public PluginOptionDrawData Add(int? x, int? y = null, int? width = null, int? height = null, bool xycombo = false, bool whcombo = false) + { + if(xycombo && (x == null || y == null) && x != y) + { + if(x.HasValue) y = x; + else x = y; + } + if(whcombo && (width == null || height == null) && width != height) + { + if(width.HasValue) height = width; + else width = height; + } + + if(x != null) + { + if(X == null) X = 0; + X += x; + } + if(y != null) + { + if(Y == null) Y = 0; + Y += y.Value; + } + if(width != null) + { + if(Width == null) Width = 0; + Width += width; + } + if(height != null) + { + if(Height == null) Height = 0; + Height = height.Value; + } + return this; + } + public PluginOptionDrawData Set(int? x = null, int? y = null, int? width = null, int? height = null) + { + if(x.HasValue) + X = x.Value; + if(y.HasValue) + Y = y.Value; + if(width.HasValue) + Width = width.Value; + if(height.HasValue) + Height = height.Value; + return this; + } + public static implicit operator Vector2(PluginOptionDrawData d) => d.Vector2; + public static implicit operator Point(PluginOptionDrawData d) => d.Position; + public static implicit operator Rectangle(PluginOptionDrawData d) => d.Rectangle; +} \ No newline at end of file diff --git a/PathfinderAPI/Options/BasePluginOption.cs b/PathfinderAPI/Options/BasePluginOption.cs index e7d6800f..36089a56 100644 --- a/PathfinderAPI/Options/BasePluginOption.cs +++ b/PathfinderAPI/Options/BasePluginOption.cs @@ -8,7 +8,7 @@ public interface IPluginOption { PluginOptionTab Tab { get; set; } string Id { get; } - DrawData DrawData { get; set; } + PluginOptionDrawData DrawData { get; set; } void OnRegistered(); void OnDraw(GameTime gameTime); void OnSave(ConfigFile config); @@ -19,8 +19,8 @@ public abstract class BasePluginOption : IPluginOption { public PluginOptionTab Tab { get; set; } - public DrawData DrawDataField; - public DrawData DrawData { get => DrawDataField; set => DrawDataField = value; } + public PluginOptionDrawData DrawDataField; + public PluginOptionDrawData DrawData { get => DrawDataField; set => DrawDataField = value; } public int HacknetGuiId { get; private set; } public virtual ValueT Value { get; set; } diff --git a/PathfinderAPI/Options/OptionsManager.cs b/PathfinderAPI/Options/OptionsManager.cs index c967ccf6..c0b2f1a4 100644 --- a/PathfinderAPI/Options/OptionsManager.cs +++ b/PathfinderAPI/Options/OptionsManager.cs @@ -36,7 +36,7 @@ public static PluginOptionTab GetTab(string tabId) public static PluginOptionTab RegisterTab(string tabName, string tabId = null) { - if(GetTab(tabId ?? string.Concat(tabName.Where(c => !char.IsWhiteSpace(c) && c != '='))) != null) + if(GetTab(tabId ?? string.Concat(tabName.Where(c => !char.IsWhiteSpace(c) && c != '='))) != null) throw new InvalidOperationException("Can not register tabs with a registered id"); return RegisterTab(new PluginOptionTab(tabName, tabId)); } @@ -44,7 +44,7 @@ public static PluginOptionTab RegisterTab(string tabName, string tabId = null) public static TabT RegisterTab(TabT tab) where TabT : PluginOptionTab { - if(GetTab(tab.Id) != null) + if(GetTab(tab.Id) != null) throw new InvalidOperationException("Can not register tabs with a registered id"); PluginTabs.Add(tab); tab.OnRegistered(); diff --git a/PathfinderAPI/Options/PathfinderOptionsMenu.cs b/PathfinderAPI/Options/PathfinderOptionsMenu.cs index de6da9dd..499aded8 100644 --- a/PathfinderAPI/Options/PathfinderOptionsMenu.cs +++ b/PathfinderAPI/Options/PathfinderOptionsMenu.cs @@ -13,7 +13,33 @@ namespace Pathfinder.Options; internal static class PathfinderOptionsMenu { private static bool isInPathfinderMenu = false; - internal static string currentTabName = null; + public static string CurrentTabId { get; private set; } = null; + + public static void SetCurrentTab(string tabId) + { + if(OptionsManager.PluginTabs.Any(pt => pt.Id == tabId) || OptionsManager.Tabs.ContainsKey(tabId)) + { + CurrentTabId = tabId; + return; + } + throw new InvalidOperationException($"Tab {tabId} not found in {typeof(OptionsManager).FullName}.{nameof(OptionsManager.PluginTabs)} or {typeof(OptionsManager).FullName}.{nameof(OptionsManager.Tabs)}"); + } + + public static PluginOptionTab SetCurrentTab(PluginOptionTab tab) + { + if(!OptionsManager.PluginTabs.Contains(tab)) + throw new InvalidOperationException($"Tab {tab.Id} not found in {typeof(OptionsManager).FullName}.{nameof(OptionsManager.PluginTabs)}"); + CurrentTabId = tab.Id; + return tab; + } + + public static OptionsTab SetCurrentTab(OptionsTab tab) + { + if(!OptionsManager.Tabs.TryGetValue(tab.Name, out var newTab) || tab != newTab) + throw new InvalidOperationException($"Tab {tab.Name} not found in {typeof(OptionsManager).FullName}.{nameof(OptionsManager.Tabs)}"); + CurrentTabId = tab.Name; + return tab; + } private static PFButton ReturnButton = new PFButton(10, 10, 220, 54, "Back to Options", Color.Yellow); @@ -30,7 +56,7 @@ internal static bool Draw(ref OptionsMenu __instance, GameTime gameTime) if (ReturnButton.Do()) { - currentTabName = null; + CurrentTabId = null; isInPathfinderMenu = false; GuiData.endDraw(); PostProcessor.end(); @@ -47,25 +73,28 @@ internal static bool Draw(ref OptionsMenu __instance, GameTime gameTime) foreach (var tab in OptionsManager.PluginTabs) { - tab.ButtonData = tab.ButtonData.Set(tabX); + if(tab.ButtonData.X == null) + { + tab.ButtonData.Set(tabX); + tabX += 10 + tab.ButtonData.Width.GetValueOrDefault(); + } tab.OnDraw(gameTime); - tabX += 10 + tab.ButtonData.Width; } foreach (var tab in tabs.Values) { - if (currentTabName == null) - currentTabName = tab.Name; - var active = currentTabName == tab.Name; + if (CurrentTabId == null) + CurrentTabId = tab.Name; + var active = CurrentTabId == tab.Name; // Display tab button if (Button.doButton(tab.ButtonID, tabX, 70, 128, 20, tab.Name, active ? Color.Green : Color.Gray)) { - currentTabName = tab.Name; + CurrentTabId = tab.Name; break; } tabX += 128 + 10; - if (currentTabName != tab.Name) + if (CurrentTabId != tab.Name) continue; // Display options diff --git a/PathfinderAPI/Options/PluginCheckbox.cs b/PathfinderAPI/Options/PluginCheckbox.cs index ff465c86..36ec1b7c 100644 --- a/PathfinderAPI/Options/PluginCheckbox.cs +++ b/PathfinderAPI/Options/PluginCheckbox.cs @@ -17,8 +17,8 @@ public PluginCheckbox(string headerText, string descriptionText = null, bool def public override void OnDraw(GameTime gameTime) { TextItem.doLabel(DrawDataField, HeaderText, null, 200); - Value = CheckBox.doCheckBox(HacknetGuiId, DrawDataField.X, DrawDataField.Y + 34, Value, null); + Value = CheckBox.doCheckBox(HacknetGuiId, DrawDataField.X.Value, DrawDataField.Y.Value + 34, Value, null); - TextItem.doSmallLabel(DrawDataField.Add(32, 30), DescriptionText, null); + TextItem.doSmallLabel(DrawDataField.QuickAdd(32, 30), DescriptionText, null); } } diff --git a/PathfinderAPI/Options/PluginOptionTab.cs b/PathfinderAPI/Options/PluginOptionTab.cs index ef13728d..031afed1 100644 --- a/PathfinderAPI/Options/PluginOptionTab.cs +++ b/PathfinderAPI/Options/PluginOptionTab.cs @@ -16,7 +16,7 @@ public class PluginOptionTab : IReadOnlyList public string TabName { get; private set; } public int HacknetGuiId { get; } public bool IsRegistered { get; internal set; } - public DrawData ButtonData { get; set; } = new DrawData + public PluginOptionDrawData ButtonData { get; set; } = new PluginOptionDrawData { X = 0, Y = 70, @@ -44,12 +44,7 @@ public PluginOptionTab AddOption(IPluginOption option) if(IsRegistered) { option.OnRegistered(); - int optX = ButtonData.Rectangle.Bottom, optY = 110; - foreach (var opt in this) - { - option.DrawData = option.DrawData.Set(optX, optY); - optY += 10 + option.DrawData.Height; - } + _SetDrawPositions(); } return this; } @@ -79,13 +74,7 @@ public virtual void OnRegistered() { foreach(var opt in options) opt.OnRegistered(); - int optX = ButtonData.Rectangle.Bottom, optY = 110; - foreach (var option in this) - { - option.DrawData = option.DrawData.Set(optX, optY); - optY += 10 + option.DrawData.Height; - } - + _SetDrawPositions(); } public virtual void OnSave(ConfigFile config) @@ -102,17 +91,23 @@ public virtual void OnLoad(ConfigFile config) public virtual void OnDraw(GameTime gameTime) { - if (PathfinderOptionsMenu.currentTabName == null) - PathfinderOptionsMenu.currentTabName = TabName; - var active = PathfinderOptionsMenu.currentTabName == TabName; + if (PathfinderOptionsMenu.CurrentTabId == null) + PathfinderOptionsMenu.SetCurrentTab(this); + var active = PathfinderOptionsMenu.CurrentTabId == Id; // Display tab button - if (Button.doButton(HacknetGuiId, ButtonData.X, ButtonData.Y, ButtonData.Width, ButtonData.Height, TabName, active ? Color.Green : Color.Gray)) + if (Button.doButton(HacknetGuiId, + ButtonData.X.GetValueOrDefault(), + ButtonData.Y.GetValueOrDefault(), + ButtonData.Width.GetValueOrDefault(), + ButtonData.Height.GetValueOrDefault(), + TabName, + active ? Color.Green : Color.Gray)) { - PathfinderOptionsMenu.currentTabName = TabName; + PathfinderOptionsMenu.SetCurrentTab(this); return; } - if (PathfinderOptionsMenu.currentTabName != TabName) + if (PathfinderOptionsMenu.CurrentTabId != Id) return; // Display options @@ -124,4 +119,17 @@ public IEnumerator GetEnumerator() => options.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + private void _SetDrawPositions() + { + int defOptionXPos = ButtonData.Rectangle.Bottom, defOptionYPos = 110; + foreach (var option in this) + { + if(option.DrawData.X == null) + option.DrawData = option.DrawData.Set(defOptionXPos); + if(option.DrawData.Y == null) + option.DrawData = option.DrawData.Set(y: defOptionYPos); + defOptionYPos += 10 + option.DrawData.Height.GetValueOrDefault(); + } + } } \ No newline at end of file From 5f4a7588c775d7461742d20f3e9d485f6d273489 Mon Sep 17 00:00:00 2001 From: "George L. Albany" Date: Mon, 25 Apr 2022 19:38:05 -0400 Subject: [PATCH 06/14] Meta.Load.OptionAttribute uses IPluginOption instead of Option Disabled obsolete warnings in Options and PathfinderOptionsMenu --- PathfinderAPI/Meta/Load/OptionAttribute.cs | 12 ++++++------ PathfinderAPI/Options/Options.cs | 2 ++ PathfinderAPI/Options/PathfinderOptionsMenu.cs | 4 ++++ 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/PathfinderAPI/Meta/Load/OptionAttribute.cs b/PathfinderAPI/Meta/Load/OptionAttribute.cs index 3c594577..6084e5e9 100644 --- a/PathfinderAPI/Meta/Load/OptionAttribute.cs +++ b/PathfinderAPI/Meta/Load/OptionAttribute.cs @@ -31,24 +31,24 @@ protected internal override void CallOn(HacknetPlugin plugin, MemberInfo targett if(targettedInfo.DeclaringType != plugin.GetType()) throw new InvalidOperationException($"Pathfinder.Meta.Load.OptionAttribute is only valid in a class derived from BepInEx.Hacknet.HacknetPlugin"); - Option option = null; + IPluginOption option = null; switch(targettedInfo) { case PropertyInfo propertyInfo: - if(!propertyInfo.PropertyType.IsSubclassOf(typeof(Option))) + if(!typeof(IPluginOption).IsAssignableFrom(propertyInfo.PropertyType)) throw new InvalidOperationException($"Property {propertyInfo.Name}'s type does not derive from Pathfinder.Options.Option"); - option = (Option)(propertyInfo.GetGetMethod()?.Invoke(plugin, null)); + option = (IPluginOption)(propertyInfo.GetGetMethod()?.Invoke(plugin, null)); break; case FieldInfo fieldInfo: - if(!fieldInfo.FieldType.IsSubclassOf(typeof(Option))) + if(!typeof(IPluginOption).IsAssignableFrom(fieldInfo.FieldType)) throw new InvalidOperationException($"Field {fieldInfo.Name}'s type does not derive from Pathfinder.Options.Option"); - option = (Option)fieldInfo.GetValue(plugin); + option = (IPluginOption)fieldInfo.GetValue(plugin); break; } if(option == null) throw new InvalidOperationException($"Option not set to a default value, Option members should be set before HacknetPlugin.Load() is called"); - OptionsManager.AddOption(Tag, option); + OptionsManager.GetOrRegisterTab(Tag).AddOption(option); } } \ No newline at end of file diff --git a/PathfinderAPI/Options/Options.cs b/PathfinderAPI/Options/Options.cs index eb671e07..68b3ce88 100644 --- a/PathfinderAPI/Options/Options.cs +++ b/PathfinderAPI/Options/Options.cs @@ -1,3 +1,5 @@ +#pragma warning disable 618 + using Hacknet.Gui; using Microsoft.Xna.Framework; using Pathfinder.GUI; diff --git a/PathfinderAPI/Options/PathfinderOptionsMenu.cs b/PathfinderAPI/Options/PathfinderOptionsMenu.cs index 499aded8..c291e7a1 100644 --- a/PathfinderAPI/Options/PathfinderOptionsMenu.cs +++ b/PathfinderAPI/Options/PathfinderOptionsMenu.cs @@ -17,12 +17,14 @@ internal static class PathfinderOptionsMenu public static void SetCurrentTab(string tabId) { + #pragma warning disable 618 if(OptionsManager.PluginTabs.Any(pt => pt.Id == tabId) || OptionsManager.Tabs.ContainsKey(tabId)) { CurrentTabId = tabId; return; } throw new InvalidOperationException($"Tab {tabId} not found in {typeof(OptionsManager).FullName}.{nameof(OptionsManager.PluginTabs)} or {typeof(OptionsManager).FullName}.{nameof(OptionsManager.Tabs)}"); + #pragma warning restore 618 } public static PluginOptionTab SetCurrentTab(PluginOptionTab tab) @@ -33,6 +35,7 @@ public static PluginOptionTab SetCurrentTab(PluginOptionTab tab) return tab; } + #pragma warning disable 618 public static OptionsTab SetCurrentTab(OptionsTab tab) { if(!OptionsManager.Tabs.TryGetValue(tab.Name, out var newTab) || tab != newTab) @@ -40,6 +43,7 @@ public static OptionsTab SetCurrentTab(OptionsTab tab) CurrentTabId = tab.Name; return tab; } + #pragma warning restore 618 private static PFButton ReturnButton = new PFButton(10, 10, 220, 54, "Back to Options", Color.Yellow); From 65afecba379f0faf3781522e0bb9585630980732 Mon Sep 17 00:00:00 2001 From: "George L. Albany" Date: Mon, 25 Apr 2022 20:52:48 -0400 Subject: [PATCH 07/14] Correct Exception messages for OptionAttribute --- PathfinderAPI/Meta/Load/OptionAttribute.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/PathfinderAPI/Meta/Load/OptionAttribute.cs b/PathfinderAPI/Meta/Load/OptionAttribute.cs index 6084e5e9..4372e5f5 100644 --- a/PathfinderAPI/Meta/Load/OptionAttribute.cs +++ b/PathfinderAPI/Meta/Load/OptionAttribute.cs @@ -36,18 +36,18 @@ protected internal override void CallOn(HacknetPlugin plugin, MemberInfo targett { case PropertyInfo propertyInfo: if(!typeof(IPluginOption).IsAssignableFrom(propertyInfo.PropertyType)) - throw new InvalidOperationException($"Property {propertyInfo.Name}'s type does not derive from Pathfinder.Options.Option"); + throw new InvalidOperationException($"Property {propertyInfo.Name}'s type does not derive from Pathfinder.Options.IPluginOption"); option = (IPluginOption)(propertyInfo.GetGetMethod()?.Invoke(plugin, null)); break; case FieldInfo fieldInfo: if(!typeof(IPluginOption).IsAssignableFrom(fieldInfo.FieldType)) - throw new InvalidOperationException($"Field {fieldInfo.Name}'s type does not derive from Pathfinder.Options.Option"); + throw new InvalidOperationException($"Field {fieldInfo.Name}'s type does not derive from Pathfinder.Options.IPluginOption"); option = (IPluginOption)fieldInfo.GetValue(plugin); break; } if(option == null) - throw new InvalidOperationException($"Option not set to a default value, Option members should be set before HacknetPlugin.Load() is called"); + throw new InvalidOperationException($"IPluginOption not set to a default value, IPluginOption members should be set before HacknetPlugin.Load() is called"); OptionsManager.GetOrRegisterTab(Tag).AddOption(option); } From 3a9bf0d11ddafc42cd2c18147e2e315d8040a179 Mon Sep 17 00:00:00 2001 From: "George L. Albany" Date: Mon, 25 Apr 2022 21:21:53 -0400 Subject: [PATCH 08/14] OptionsTabAttribute: pluginToOptionsTag renamed to pluginToOptTabAttribute that stores OptionsTabAttribute as value Added TabName and TabId Made Tag obsolete and redirects to TabName Property setters are made public Added OptionsManager.GetIdFrom(string, string) for PluginOptionTab id resolution OptionsAttribute: Added TabName and TabId Made Tag obsolete redirects to TabName Can register tabs with ID --- .../Meta/Load/HacknetPluginExtensions.cs | 6 ++--- PathfinderAPI/Meta/Load/OptionAttribute.cs | 23 ++++++++++++------- .../Meta/Load/OptionsTabAttribute.cs | 15 ++++++++---- PathfinderAPI/Options/OptionsManager.cs | 9 +++++--- PathfinderAPI/Options/PluginOptionTab.cs | 2 +- 5 files changed, 35 insertions(+), 20 deletions(-) diff --git a/PathfinderAPI/Meta/Load/HacknetPluginExtensions.cs b/PathfinderAPI/Meta/Load/HacknetPluginExtensions.cs index ff5949ca..4dba1d90 100644 --- a/PathfinderAPI/Meta/Load/HacknetPluginExtensions.cs +++ b/PathfinderAPI/Meta/Load/HacknetPluginExtensions.cs @@ -6,13 +6,13 @@ public static class HacknetPluginExtensions { public static string GetOptionsTag(this HacknetPlugin plugin) { - if(!OptionsTabAttribute.pluginToOptionsTag.TryGetValue(plugin, out var tag)) + if(!OptionsTabAttribute.pluginToOptTabAttribute.TryGetValue(plugin, out var attr)) return null; - return tag; + return attr.TabName; } public static bool HasOptionsTag(this HacknetPlugin plugin) { - return OptionsTabAttribute.pluginToOptionsTag.ContainsKey(plugin); + return OptionsTabAttribute.pluginToOptTabAttribute.ContainsKey(plugin); } } \ No newline at end of file diff --git a/PathfinderAPI/Meta/Load/OptionAttribute.cs b/PathfinderAPI/Meta/Load/OptionAttribute.cs index 4372e5f5..7cb303c8 100644 --- a/PathfinderAPI/Meta/Load/OptionAttribute.cs +++ b/PathfinderAPI/Meta/Load/OptionAttribute.cs @@ -7,25 +7,32 @@ namespace Pathfinder.Meta.Load; [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] public class OptionAttribute : BaseAttribute { - public string Tag { get; set; } + [Obsolete("Use TabName")] + public string Tag { get => TabName; set => TabName = value; } + public string TabName { get; set; } + public string TabId { get; set; } - public OptionAttribute(string tag = null) + public OptionAttribute(string tag = null, string tabId = null) { - this.Tag = tag; + TabName = tag; + TabId = tabId; } public OptionAttribute(Type pluginType) { - this.Tag = pluginType.GetCustomAttribute()?.Tag; + var tabAttr = pluginType.GetCustomAttribute(); + TabName = tabAttr.TabName; + TabId = tabAttr.TabId; } protected internal override void CallOn(HacknetPlugin plugin, MemberInfo targettedInfo) { - if(Tag == null) + if(TabName == null) { - Tag = plugin.GetOptionsTag(); - if(Tag == null) + if(!OptionsTabAttribute.pluginToOptionsTag.TryGetValue(plugin, out var tab)) throw new InvalidOperationException($"Could not find Pathfinder.Meta.Load.OptionsTabAttribute for {targettedInfo.DeclaringType.FullName}"); + TabName = tab.TabName; + TabId = tab.TabId; } if(targettedInfo.DeclaringType != plugin.GetType()) @@ -49,6 +56,6 @@ protected internal override void CallOn(HacknetPlugin plugin, MemberInfo targett if(option == null) throw new InvalidOperationException($"IPluginOption not set to a default value, IPluginOption members should be set before HacknetPlugin.Load() is called"); - OptionsManager.GetOrRegisterTab(Tag).AddOption(option); + OptionsManager.GetOrRegisterTab(TabName, TabId).AddOption(option); } } \ No newline at end of file diff --git a/PathfinderAPI/Meta/Load/OptionsTabAttribute.cs b/PathfinderAPI/Meta/Load/OptionsTabAttribute.cs index e941e0e9..61fd5272 100644 --- a/PathfinderAPI/Meta/Load/OptionsTabAttribute.cs +++ b/PathfinderAPI/Meta/Load/OptionsTabAttribute.cs @@ -1,22 +1,27 @@ using System.Reflection; using BepInEx.Hacknet; +using Pathfinder.Options; namespace Pathfinder.Meta.Load; [AttributeUsage(AttributeTargets.Class)] public class OptionsTabAttribute : BaseAttribute { - internal static readonly Dictionary pluginToOptionsTag = new Dictionary(); + internal static readonly Dictionary pluginToOptTabAttribute = new Dictionary(); - public string Tag { get; } + [Obsolete("Use TabName")] + public string Tag { get => TabName; set => TabName = value; } + public string TabName { get; set; } + public string TabId { get; set; } - public OptionsTabAttribute(string tag) + public OptionsTabAttribute(string tag, string tabId = null) { - this.Tag = tag; + TabName = tag; + TabId = tabId; } protected internal override void CallOn(HacknetPlugin plugin, MemberInfo targettedInfo) { - pluginToOptionsTag.Add(plugin, Tag); + pluginToOptTabAttribute.Add(plugin, this); } } \ No newline at end of file diff --git a/PathfinderAPI/Options/OptionsManager.cs b/PathfinderAPI/Options/OptionsManager.cs index c0b2f1a4..7abf8f41 100644 --- a/PathfinderAPI/Options/OptionsManager.cs +++ b/PathfinderAPI/Options/OptionsManager.cs @@ -36,7 +36,7 @@ public static PluginOptionTab GetTab(string tabId) public static PluginOptionTab RegisterTab(string tabName, string tabId = null) { - if(GetTab(tabId ?? string.Concat(tabName.Where(c => !char.IsWhiteSpace(c) && c != '='))) != null) + if(GetTab(GetIdFrom(tabName, tabId)) != null) throw new InvalidOperationException("Can not register tabs with a registered id"); return RegisterTab(new PluginOptionTab(tabName, tabId)); } @@ -53,7 +53,7 @@ public static TabT RegisterTab(TabT tab) public static PluginOptionTab GetOrRegisterTab(string tabName, string tabId = null) { - if(!TryGetTab(tabId ?? string.Concat(tabName.Where(c => !char.IsWhiteSpace(c) && c != '=')), out PluginOptionTab tab)) + if(!TryGetTab(GetIdFrom(tabName, tabId), out PluginOptionTab tab)) tab = RegisterTab(tabName, tabId); return tab; } @@ -61,7 +61,7 @@ public static PluginOptionTab GetOrRegisterTab(string tabName, string tabId = nu public static TabT GetOrRegisterTab(string tabName, string tabId, Func generatorFunc) where TabT : PluginOptionTab { - if(!TryGetTab(tabId ?? string.Concat(tabName.Where(c => !char.IsWhiteSpace(c) && c != '=')), out TabT tab)) + if(!TryGetTab(GetIdFrom(tabName, tabId), out TabT tab)) tab = RegisterTab(generatorFunc()); return tab; } @@ -96,6 +96,9 @@ public static void OnConfigLoad(ConfigFile config) foreach(var tab in PluginTabs) tab.OnLoad(config); } + + internal static string GetIdFrom(string name, string id = null) + => id ?? string.Concat(name.Where(c => !char.IsWhiteSpace(c) && c != '=')); } [Obsolete("Use PluginOptionTab")] diff --git a/PathfinderAPI/Options/PluginOptionTab.cs b/PathfinderAPI/Options/PluginOptionTab.cs index 031afed1..db584eab 100644 --- a/PathfinderAPI/Options/PluginOptionTab.cs +++ b/PathfinderAPI/Options/PluginOptionTab.cs @@ -27,7 +27,7 @@ public class PluginOptionTab : IReadOnlyList public PluginOptionTab(string tabName, string tabId = null) { TabName = tabName; - Id = tabId ?? string.Concat(TabName.Where(c => !char.IsWhiteSpace(c) && c != '=')); + Id = OptionsManager.GetIdFrom(tabName, tabId); HacknetGuiId = PFButton.GetNextID(); } From 1a9deadad15b8003c7aaa6c5b11f5d9b86e8bb57 Mon Sep 17 00:00:00 2001 From: "George L. Albany" Date: Mon, 25 Apr 2022 23:57:43 -0400 Subject: [PATCH 09/14] Correct missing rename in OptionAttribute --- PathfinderAPI/Meta/Load/OptionAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PathfinderAPI/Meta/Load/OptionAttribute.cs b/PathfinderAPI/Meta/Load/OptionAttribute.cs index 7cb303c8..a64af934 100644 --- a/PathfinderAPI/Meta/Load/OptionAttribute.cs +++ b/PathfinderAPI/Meta/Load/OptionAttribute.cs @@ -29,7 +29,7 @@ protected internal override void CallOn(HacknetPlugin plugin, MemberInfo targett { if(TabName == null) { - if(!OptionsTabAttribute.pluginToOptionsTag.TryGetValue(plugin, out var tab)) + if(!OptionsTabAttribute.pluginToOptTabAttribute.TryGetValue(plugin, out var tab)) throw new InvalidOperationException($"Could not find Pathfinder.Meta.Load.OptionsTabAttribute for {targettedInfo.DeclaringType.FullName}"); TabName = tab.TabName; TabId = tab.TabId; From 8d19f545083488d00e3310cd08f3627895028841 Mon Sep 17 00:00:00 2001 From: "George L. Albany" Date: Wed, 27 Apr 2022 02:05:07 -0400 Subject: [PATCH 10/14] PluginOption OnRegistered renamed to LoadContent, runs after Game1.LoadContent BasePluginOption: Removed DrawDataField, use DrawData Removed Id.set Added HeaderColor, DescriptionColor, HeaderFont, DescriptionFont, HeaderTextSize, and DescritptionTextSize Added protected constructor for needed assignment Assign fonts and size in LoadContent Fix config.Bind throwing for ConfigDescription being null Added protected DrawString method OptionManager: Made GetIdFrom public Added Postfix Patch to Game1.LoadContent for calling LoadContent on all tabs PathfinderOptions will add to an existing Pathfinder option tab if it exists PluginCheckbox has been cleaned up and given SelectedColor PluginOptionTab: Changed options to IDictionary Added SpriteBatch Batch Renamed IsRegistered to IsLoaded Implements IReadOnlyDictionary for options instead Renamed OnRegistered to LoadContent, runs on Game1.LoadContent Properly set IsLoaded to true in LoadContent Properly set draw data positions Added PluginSlider for slider option Added incomplete and unpolished PluginTextbox Partly addresses #72 --- PathfinderAPI/GUI/PluginOptionDrawData.cs | 30 +++++---- PathfinderAPI/Options/BasePluginOption.cs | 50 +++++++++++++-- PathfinderAPI/Options/OptionsManager.cs | 14 +++- PathfinderAPI/Options/PathfinderOptions.cs | 2 +- PathfinderAPI/Options/PluginCheckbox.cs | 19 +++--- PathfinderAPI/Options/PluginOptionTab.cs | 66 +++++++++++-------- PathfinderAPI/Options/PluginSlider.cs | 48 ++++++++++++++ PathfinderAPI/Options/PluginTextbox.cs | 74 ++++++++++++++++++++++ 8 files changed, 243 insertions(+), 60 deletions(-) create mode 100644 PathfinderAPI/Options/PluginSlider.cs create mode 100644 PathfinderAPI/Options/PluginTextbox.cs diff --git a/PathfinderAPI/GUI/PluginOptionDrawData.cs b/PathfinderAPI/GUI/PluginOptionDrawData.cs index f04fa203..1ea649ff 100644 --- a/PathfinderAPI/GUI/PluginOptionDrawData.cs +++ b/PathfinderAPI/GUI/PluginOptionDrawData.cs @@ -49,15 +49,17 @@ public Point Size public PluginOptionDrawData QuickAdd(int x = 0, int y = 0, int width = 0, int height = 0) { - X = x + X.GetValueOrDefault(); - Y = y + Y.GetValueOrDefault(); - Width = width + Width.GetValueOrDefault(); - Height = height + Height.GetValueOrDefault(); - return this; + var result = this; + result.X = x + X.GetValueOrDefault(); + result.Y = y + Y.GetValueOrDefault(); + result.Width = width + Width.GetValueOrDefault(); + result.Height = height + Height.GetValueOrDefault(); + return result; } public PluginOptionDrawData Add(int? x, int? y = null, int? width = null, int? height = null, bool xycombo = false, bool whcombo = false) { + var result = this; if(xycombo && (x == null || y == null) && x != y) { if(x.HasValue) y = x; @@ -71,25 +73,25 @@ public PluginOptionDrawData Add(int? x, int? y = null, int? width = null, int? h if(x != null) { - if(X == null) X = 0; - X += x; + if(result.X == null) result.X = 0; + result.X += x; } if(y != null) { - if(Y == null) Y = 0; - Y += y.Value; + if(result.Y == null) result.Y = 0; + result.Y += y.Value; } if(width != null) { - if(Width == null) Width = 0; - Width += width; + if(result.Width == null) result.Width = 0; + result.Width += width; } if(height != null) { - if(Height == null) Height = 0; - Height = height.Value; + if(result.Height == null) result.Height = 0; + result.Height = height.Value; } - return this; + return result; } public PluginOptionDrawData Set(int? x = null, int? y = null, int? width = null, int? height = null) { diff --git a/PathfinderAPI/Options/BasePluginOption.cs b/PathfinderAPI/Options/BasePluginOption.cs index 36089a56..346f2d98 100644 --- a/PathfinderAPI/Options/BasePluginOption.cs +++ b/PathfinderAPI/Options/BasePluginOption.cs @@ -1,6 +1,8 @@ using BepInEx.Configuration; using Pathfinder.GUI; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Hacknet; namespace Pathfinder.Options; @@ -9,7 +11,7 @@ public interface IPluginOption PluginOptionTab Tab { get; set; } string Id { get; } PluginOptionDrawData DrawData { get; set; } - void OnRegistered(); + void LoadContent(); void OnDraw(GameTime gameTime); void OnSave(ConfigFile config); void OnLoad(ConfigFile config); @@ -19,8 +21,7 @@ public abstract class BasePluginOption : IPluginOption { public PluginOptionTab Tab { get; set; } - public PluginOptionDrawData DrawDataField; - public PluginOptionDrawData DrawData { get => DrawDataField; set => DrawDataField = value; } + public PluginOptionDrawData DrawData { get; set; } public int HacknetGuiId { get; private set; } public virtual ValueT Value { get; set; } @@ -28,7 +29,25 @@ public abstract class BasePluginOption : IPluginOption public virtual string HeaderText { get; protected set; } public virtual string DescriptionText { get; protected set; } public virtual string ConfigDescription { get; protected set; } - public virtual string Id { get; private set; } + public virtual string Id { get; } + + public Color HeaderColor { get; set; } = Color.White; + public Color DescriptionColor { get; set; } = Color.White; + + public SpriteFont HeaderFont { get; set; } + public SpriteFont DescriptionFont { get; set; } + + public Vector2 HeaderTextSize => HeaderFont.MeasureString(HeaderText); + public Vector2 DescriptionTextSize => DescriptionFont.MeasureString(DescriptionText); + + protected BasePluginOption(string headerText, string descriptionText = null, ValueT defaultValue = default, string configDesc = null, string id = null) + { + HeaderText = headerText; + DescriptionText = descriptionText; + DefaultValue = defaultValue; + ConfigDescription = configDesc; + Id = OptionsManager.GetIdFrom(HeaderText, id); + } public bool TrySetHeaderText(string text) { @@ -48,16 +67,28 @@ public bool TrySetConfigDescription(string desc) return ConfigDescription == desc; } - public virtual void OnRegistered() + public virtual void LoadContent() { HacknetGuiId = PFButton.GetNextID(); - Id = string.Concat(HeaderText.Where(c => !char.IsWhiteSpace(c) && c != '=')); + HeaderFont ??= GuiData.font; + DescriptionFont ??= GuiData.smallfont; + SetSize(); + } + + public virtual void SetSize() + { + var headerSize = HeaderTextSize; + var descSize = DescriptionTextSize; + if(DrawData.Width == null) + DrawData = DrawData.Set(width: (int)(Math.Max(headerSize.X, descSize.X + 32))); + if(DrawData.Height == null) + DrawData = DrawData.Set(height: (int)(headerSize.Y + descSize.Y)); } public abstract void OnDraw(GameTime gameTime); public virtual void OnSave(ConfigFile config) { - config.Bind(Tab.Id, Id, DefaultValue, ConfigDescription); + config.Bind(Tab.Id, Id, DefaultValue, ConfigDescription ?? ""); } public virtual void OnLoad(ConfigFile config) { @@ -65,4 +96,9 @@ public virtual void OnLoad(ConfigFile config) Value = entry.Value; else Value = DefaultValue; } + + protected void DrawString(Vector2 pos, string text, Color? color = null, SpriteFont font = null) + { + Tab.Batch.DrawString(font ?? GuiData.font, text, pos, color ?? Color.White); + } } \ No newline at end of file diff --git a/PathfinderAPI/Options/OptionsManager.cs b/PathfinderAPI/Options/OptionsManager.cs index 7abf8f41..9fa8cf70 100644 --- a/PathfinderAPI/Options/OptionsManager.cs +++ b/PathfinderAPI/Options/OptionsManager.cs @@ -3,6 +3,7 @@ namespace Pathfinder.Options; +[HarmonyPatch] public static class OptionsManager { [Obsolete("Use PluginTabs")] @@ -45,9 +46,8 @@ public static TabT RegisterTab(TabT tab) where TabT : PluginOptionTab { if(GetTab(tab.Id) != null) - throw new InvalidOperationException("Can not register tabs with a registered id"); + throw new InvalidOperationException("Can not register new tabs with an already registered id"); PluginTabs.Add(tab); - tab.OnRegistered(); return tab; } @@ -97,8 +97,16 @@ public static void OnConfigLoad(ConfigFile config) tab.OnLoad(config); } - internal static string GetIdFrom(string name, string id = null) + public static string GetIdFrom(string name, string id = null) => id ?? string.Concat(name.Where(c => !char.IsWhiteSpace(c) && c != '=')); + + [HarmonyPostfix] + [HarmonyPatch(typeof(Game1), nameof(Game1.LoadContent))] + private static void OnPostGame1LoadContent(Game1 __instance) + { + foreach(var tab in PluginTabs) + tab.LoadContent(); + } } [Obsolete("Use PluginOptionTab")] diff --git a/PathfinderAPI/Options/PathfinderOptions.cs b/PathfinderAPI/Options/PathfinderOptions.cs index f4969b8e..be7bbae2 100644 --- a/PathfinderAPI/Options/PathfinderOptions.cs +++ b/PathfinderAPI/Options/PathfinderOptions.cs @@ -13,7 +13,7 @@ internal static class PathfinderOptions [Util.Initialize] static void Initialize() { - OptionsManager.RegisterTab(OPTION_TAG) + OptionsManager.GetOrRegisterTab(OPTION_TAG) .AddOption(PreloadAllThemes) .AddOption(DisableSteamCloudError); } diff --git a/PathfinderAPI/Options/PluginCheckbox.cs b/PathfinderAPI/Options/PluginCheckbox.cs index 36ec1b7c..8efb01b0 100644 --- a/PathfinderAPI/Options/PluginCheckbox.cs +++ b/PathfinderAPI/Options/PluginCheckbox.cs @@ -1,24 +1,25 @@ using BepInEx.Configuration; +using Hacknet; using Hacknet.Gui; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; namespace Pathfinder.Options; public class PluginCheckbox : BasePluginOption { - public PluginCheckbox(string headerText, string descriptionText = null, bool defaultValue = false, string configDesc = null) + public Color SelectedColor { get; set; } = Color.White; + + public PluginCheckbox(string headerText, string descriptionText = null, bool defaultValue = false, string configDesc = null, string id = null) + : base(headerText, descriptionText, defaultValue, configDesc, id) { - HeaderText = headerText; - DescriptionText = descriptionText; - DefaultValue = defaultValue; - ConfigDescription = configDesc; } public override void OnDraw(GameTime gameTime) { - TextItem.doLabel(DrawDataField, HeaderText, null, 200); - Value = CheckBox.doCheckBox(HacknetGuiId, DrawDataField.X.Value, DrawDataField.Y.Value + 34, Value, null); - - TextItem.doSmallLabel(DrawDataField.QuickAdd(32, 30), DescriptionText, null); + DrawString(DrawData, HeaderText, HeaderColor, HeaderFont); + Value = CheckBox.doCheckBox(HacknetGuiId, DrawData.X.Value, DrawData.Y.Value + 34, Value, SelectedColor); + DrawString(DrawData.QuickAdd(32, 30), DescriptionText, DescriptionColor, DescriptionFont); + RenderedRectangle.doRectangleOutline(DrawData.X.Value, DrawData.Y.Value, DrawData.Width.Value, DrawData.Height.Value, 2, null); } } diff --git a/PathfinderAPI/Options/PluginOptionTab.cs b/PathfinderAPI/Options/PluginOptionTab.cs index db584eab..d82f24fd 100644 --- a/PathfinderAPI/Options/PluginOptionTab.cs +++ b/PathfinderAPI/Options/PluginOptionTab.cs @@ -3,19 +3,23 @@ using System.Collections.Generic; using System.Linq; using BepInEx.Configuration; +using Hacknet; using Hacknet.Gui; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; using Pathfinder.GUI; namespace Pathfinder.Options; -public class PluginOptionTab : IReadOnlyList +public class PluginOptionTab : IReadOnlyDictionary { - private List options = new List(); + private IDictionary options = new Dictionary(); public string Id { get; private set; } public string TabName { get; private set; } + + public SpriteBatch Batch { get; internal set; } public int HacknetGuiId { get; } - public bool IsRegistered { get; internal set; } + public bool IsLoaded { get; internal set; } public PluginOptionDrawData ButtonData { get; set; } = new PluginOptionDrawData { X = 0, @@ -32,18 +36,17 @@ public PluginOptionTab(string tabName, string tabId = null) } public int Count => options.Count; - - public IPluginOption this[int index] => options[index]; + public IEnumerable Keys => options.Keys; + public IEnumerable Values => options.Values; + public IPluginOption this[string key] => options[key]; public PluginOptionTab AddOption(IPluginOption option) { - if(options.Any(p => p.Id == option.Id)) - throw new InvalidOperationException("Option tabs may not have two options with the same id"); - options.Add(option); + options.Add(option.Id, option); option.Tab = this; - if(IsRegistered) + if(IsLoaded) { - option.OnRegistered(); + option.LoadContent(); _SetDrawPositions(); } return this; @@ -51,7 +54,9 @@ public PluginOptionTab AddOption(IPluginOption option) public IPluginOption GetOption(string id) { - return options.Find(p => p.Id == id); + if(TryGetValue(id, out var result)) + return result; + return null; } public OptionT GetOption(string id) where OptionT : IPluginOption @@ -62,31 +67,33 @@ public OptionT GetOptionAs(string id) where OptionT : class, IPluginOpt public bool RemoveOption(IPluginOption option) { - return options.Remove(option); + return RemoveOption(option.Id); } public bool RemoveOption(string id) { - return RemoveOption(options.Find(p => p.Id == id)); + return options.Remove(id); } - public virtual void OnRegistered() + public virtual void LoadContent() { + Batch = GuiData.spriteBatch; foreach(var opt in options) - opt.OnRegistered(); + opt.Value.LoadContent(); _SetDrawPositions(); + IsLoaded = true; } public virtual void OnSave(ConfigFile config) { foreach(var opt in options) - opt.OnSave(config); + opt.Value.OnSave(config); } public virtual void OnLoad(ConfigFile config) { foreach(var opt in options) - opt.OnLoad(config); + opt.Value.OnLoad(config); } public virtual void OnDraw(GameTime gameTime) @@ -112,24 +119,31 @@ public virtual void OnDraw(GameTime gameTime) // Display options foreach (var option in this) - option.OnDraw(gameTime); + option.Value.OnDraw(gameTime); } - public IEnumerator GetEnumerator() + public IEnumerator> GetEnumerator() => options.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); private void _SetDrawPositions() { - int defOptionXPos = ButtonData.Rectangle.Bottom, defOptionYPos = 110; - foreach (var option in this) + int defOptionXPos = 80, defOptionYPos = 110; + foreach (var pair in options) { - if(option.DrawData.X == null) - option.DrawData = option.DrawData.Set(defOptionXPos); - if(option.DrawData.Y == null) - option.DrawData = option.DrawData.Set(y: defOptionYPos); - defOptionYPos += 10 + option.DrawData.Height.GetValueOrDefault(); + var opt = pair.Value; + if(!opt.DrawData.X.HasValue) + opt.DrawData = opt.DrawData.Set(defOptionXPos); + if(!opt.DrawData.Y.HasValue) + opt.DrawData = opt.DrawData.Set(y: defOptionYPos); + defOptionYPos += 10 + opt.DrawData.Height.GetValueOrDefault(); } } + + public bool ContainsKey(string key) + => options.ContainsKey(key); + + public bool TryGetValue(string key, out IPluginOption value) + => options.TryGetValue(key, out value); } \ No newline at end of file diff --git a/PathfinderAPI/Options/PluginSlider.cs b/PathfinderAPI/Options/PluginSlider.cs new file mode 100644 index 00000000..3cb07413 --- /dev/null +++ b/PathfinderAPI/Options/PluginSlider.cs @@ -0,0 +1,48 @@ +using Hacknet.Gui; +using Microsoft.Xna.Framework; + +namespace Pathfinder.Options; + +public class PluginSlider : BasePluginOption +{ + public int SliderRangeWidth { get; set; } = 50; + public int SliderHeight { get; set; } = 20; + public float MinimumValue { get; set; } = 0f; + public float MaximumValue { get; set; } = 1f; + public float StepSize { get; set; } + + public PluginSlider(string headerText, string descriptionText = null, float defaultValue = 0f, float stepSize = 0f, string configDesc = null, string id = null) + : base(headerText, descriptionText, defaultValue, configDesc, id) + { + StepSize = stepSize; + } + + public override void SetSize() + { + var headerSize = HeaderTextSize; + var descSize = DescriptionTextSize; + if(DrawData.Width == null) + DrawData = DrawData.Set(width: (int)(Math.Max(headerSize.X, descSize.X + SliderRangeWidth + 10))); + if(DrawData.Height == null) + DrawData = DrawData.Set(height: (int)(headerSize.Y + Math.Max(descSize.Y, SliderHeight + 9))); + } + + public override void OnDraw(GameTime gameTime) + { + DrawString(DrawData, HeaderText, HeaderColor, HeaderFont); + Value = SliderBar.doSliderBar( + HacknetGuiId, + DrawData.X.Value + 5, + DrawData.Y.Value + 45, + SliderRangeWidth, + SliderHeight, + MaximumValue, + MinimumValue, + Value, + StepSize + ); + + DrawString(DrawData.QuickAdd(SliderRangeWidth + 10, 45), DescriptionText, DescriptionColor, DescriptionFont); + RenderedRectangle.doRectangleOutline(DrawData.X.Value, DrawData.Y.Value, DrawData.Width.Value, DrawData.Height.Value, 2, null); + } +} \ No newline at end of file diff --git a/PathfinderAPI/Options/PluginTextbox.cs b/PathfinderAPI/Options/PluginTextbox.cs new file mode 100644 index 00000000..d04dbba3 --- /dev/null +++ b/PathfinderAPI/Options/PluginTextbox.cs @@ -0,0 +1,74 @@ +using Hacknet; +using Hacknet.Gui; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Pathfinder.Options; + +public class PluginTextbox : BasePluginOption +{ + private int _textBoxWidthInPixels; + private int _textBoxWidth; + public int TextBoxWidth + { + get => _textBoxWidth; + set + { + _textBoxWidth = value; + if(TextBoxFont != null) + _textBoxWidthInPixels = ((int)TextBoxFont.MeasureString("o").X) * (_textBoxWidth - 1); + } + } + public int TextBoxLines { get; set; } = 1; + private SpriteFont _textBoxFont; + public SpriteFont TextBoxFont + { + get => _textBoxFont; + set + { + _textBoxFont = value; + _textBoxWidthInPixels = ((int)TextBoxFont.MeasureString("o").X) * (_textBoxWidth - 1); + } + } + + public PluginTextbox(string headerText, string descriptionText = null, string defaultValue = null, string configDesc = null, string id = null) + : base(headerText, descriptionText, defaultValue ?? "", configDesc, id) + { + TextBoxWidth = 20; + } + + public override void LoadContent() + { + TextBoxFont ??= GuiData.smallfont; + base.LoadContent(); + } + + public override void SetSize() + { + var characterSize = TextBoxFont.MeasureString("O"); + var headerSize = HeaderTextSize; + var descSize = DescriptionTextSize; + if(DrawData.Width == null) + DrawData = DrawData.Set(width: (int)(Math.Max(headerSize.X, descSize.X + _textBoxWidthInPixels + 10))); + if(DrawData.Height == null) + DrawData = DrawData.Set(height: (int)(headerSize.Y + Math.Max(descSize.Y, (TextBoxLines * characterSize.Y) + (Math.Max(0, TextBoxLines - 1)) * 25))); + } + + public override void OnDraw(GameTime gameTime) + { + DrawString(DrawData, HeaderText, HeaderColor, HeaderFont); + // Textbox doesn't support cliping the value to the width and doesn't take into account held down keys or special cases, like ctrl+backspace + Value = TextBox.doTextBox( + HacknetGuiId, + DrawData.X.Value, + DrawData.Y.Value + 34, + _textBoxWidthInPixels, + TextBoxLines, + Value, + TextBoxFont + ); + + DrawString(DrawData.QuickAdd(_textBoxWidthInPixels + 10, 32), DescriptionText, DescriptionColor, DescriptionFont); + RenderedRectangle.doRectangleOutline(DrawData.X.Value, DrawData.Y.Value, DrawData.Width.Value, DrawData.Height.Value, 2, null); + } +} \ No newline at end of file From 187829b5d997d9751d76443d6f046ae5109f2bc5 Mon Sep 17 00:00:00 2001 From: "George L. Albany" Date: Thu, 28 Apr 2022 10:20:40 -0400 Subject: [PATCH 11/14] Reworked PluginOptionTab OnDraw behavior Deleted PluginOptionDrawData IPluginOption: Removed DrawData Added Rectangle and Size Added TrySetOffset(Vector2) BasePluginOption: Added Position, Offset, and MinSize BasePluginOption derived classes use Position and Size for drawing and MinSize for configuring the Size PluginOptionTab corrects option positions on every draw call using IPluginOption.TrySetOffset Added expanding behavior for PluginTextbox --- PathfinderAPI/GUI/PluginOptionDrawData.cs | 111 ---------------------- PathfinderAPI/Options/BasePluginOption.cs | 44 ++++++--- PathfinderAPI/Options/PluginCheckbox.cs | 18 +++- PathfinderAPI/Options/PluginOptionTab.cs | 21 +--- PathfinderAPI/Options/PluginSlider.cs | 24 ++--- PathfinderAPI/Options/PluginTextbox.cs | 64 ++++++------- 6 files changed, 90 insertions(+), 192 deletions(-) delete mode 100644 PathfinderAPI/GUI/PluginOptionDrawData.cs diff --git a/PathfinderAPI/GUI/PluginOptionDrawData.cs b/PathfinderAPI/GUI/PluginOptionDrawData.cs deleted file mode 100644 index 1ea649ff..00000000 --- a/PathfinderAPI/GUI/PluginOptionDrawData.cs +++ /dev/null @@ -1,111 +0,0 @@ -using Microsoft.Xna.Framework; - -namespace Pathfinder.GUI; - -public struct PluginOptionDrawData -{ - public int? X; - public int? Y; - public int? Width; - public int? Height; - public Rectangle Rectangle - { - get => new Rectangle(X.GetValueOrDefault(), Y.GetValueOrDefault(), Width.GetValueOrDefault(), Height.GetValueOrDefault()); - set - { - X = value.X; - Y = value.Y; - Width = value.Width; - Height = value.Height; - } - } - public Vector2 Vector2 - { - get => new Vector2(X.GetValueOrDefault(), Y.GetValueOrDefault()); - set - { - X = (int)value.X; - Y = (int)value.Y; - } - } - public Point Position - { - get => new Point(X.GetValueOrDefault(), Y.GetValueOrDefault()); - set - { - X = value.X; - Y = value.Y; - } - } - public Point Size - { - get => new Point(Width.GetValueOrDefault(), Height.GetValueOrDefault()); - set - { - Width = value.X; - Height = value.Y; - } - } - - public PluginOptionDrawData QuickAdd(int x = 0, int y = 0, int width = 0, int height = 0) - { - var result = this; - result.X = x + X.GetValueOrDefault(); - result.Y = y + Y.GetValueOrDefault(); - result.Width = width + Width.GetValueOrDefault(); - result.Height = height + Height.GetValueOrDefault(); - return result; - } - - public PluginOptionDrawData Add(int? x, int? y = null, int? width = null, int? height = null, bool xycombo = false, bool whcombo = false) - { - var result = this; - if(xycombo && (x == null || y == null) && x != y) - { - if(x.HasValue) y = x; - else x = y; - } - if(whcombo && (width == null || height == null) && width != height) - { - if(width.HasValue) height = width; - else width = height; - } - - if(x != null) - { - if(result.X == null) result.X = 0; - result.X += x; - } - if(y != null) - { - if(result.Y == null) result.Y = 0; - result.Y += y.Value; - } - if(width != null) - { - if(result.Width == null) result.Width = 0; - result.Width += width; - } - if(height != null) - { - if(result.Height == null) result.Height = 0; - result.Height = height.Value; - } - return result; - } - public PluginOptionDrawData Set(int? x = null, int? y = null, int? width = null, int? height = null) - { - if(x.HasValue) - X = x.Value; - if(y.HasValue) - Y = y.Value; - if(width.HasValue) - Width = width.Value; - if(height.HasValue) - Height = height.Value; - return this; - } - public static implicit operator Vector2(PluginOptionDrawData d) => d.Vector2; - public static implicit operator Point(PluginOptionDrawData d) => d.Position; - public static implicit operator Rectangle(PluginOptionDrawData d) => d.Rectangle; -} \ No newline at end of file diff --git a/PathfinderAPI/Options/BasePluginOption.cs b/PathfinderAPI/Options/BasePluginOption.cs index 346f2d98..694956ca 100644 --- a/PathfinderAPI/Options/BasePluginOption.cs +++ b/PathfinderAPI/Options/BasePluginOption.cs @@ -10,7 +10,9 @@ public interface IPluginOption { PluginOptionTab Tab { get; set; } string Id { get; } - PluginOptionDrawData DrawData { get; set; } + Rectangle Rectangle { get; set; } + Vector2 Size { get; } + bool TrySetOffset(Vector2 offset); void LoadContent(); void OnDraw(GameTime gameTime); void OnSave(ConfigFile config); @@ -21,7 +23,7 @@ public abstract class BasePluginOption : IPluginOption { public PluginOptionTab Tab { get; set; } - public PluginOptionDrawData DrawData { get; set; } + public Rectangle Rectangle { get; set; } public int HacknetGuiId { get; private set; } public virtual ValueT Value { get; set; } @@ -40,6 +42,27 @@ public abstract class BasePluginOption : IPluginOption public Vector2 HeaderTextSize => HeaderFont.MeasureString(HeaderText); public Vector2 DescriptionTextSize => DescriptionFont.MeasureString(DescriptionText); + public virtual Vector2 Position => new Vector2(Rectangle.X + Offset.X, Rectangle.Y + Offset.Y); + public virtual Vector2 Offset { get; set; } + + public Vector2 Size + { + get + { + var minsize = MinSize; + return new Vector2(Math.Max(Rectangle.Width, minsize.X), Math.Max(Rectangle.Height, minsize.Y)); + } + } + public virtual Vector2 MinSize + { + get + { + var headerSize = HeaderTextSize; + var descSize = DescriptionTextSize; + return new Vector2(Math.Max(headerSize.X, descSize.X), headerSize.Y + descSize.Y); + } + } + protected BasePluginOption(string headerText, string descriptionText = null, ValueT defaultValue = default, string configDesc = null, string id = null) { HeaderText = headerText; @@ -49,6 +72,12 @@ protected BasePluginOption(string headerText, string descriptionText = null, Val Id = OptionsManager.GetIdFrom(HeaderText, id); } + public bool TrySetOffset(Vector2 offset) + { + Offset = offset; + return Offset == offset; + } + public bool TrySetHeaderText(string text) { HeaderText = text; @@ -72,17 +101,6 @@ public virtual void LoadContent() HacknetGuiId = PFButton.GetNextID(); HeaderFont ??= GuiData.font; DescriptionFont ??= GuiData.smallfont; - SetSize(); - } - - public virtual void SetSize() - { - var headerSize = HeaderTextSize; - var descSize = DescriptionTextSize; - if(DrawData.Width == null) - DrawData = DrawData.Set(width: (int)(Math.Max(headerSize.X, descSize.X + 32))); - if(DrawData.Height == null) - DrawData = DrawData.Set(height: (int)(headerSize.Y + descSize.Y)); } public abstract void OnDraw(GameTime gameTime); diff --git a/PathfinderAPI/Options/PluginCheckbox.cs b/PathfinderAPI/Options/PluginCheckbox.cs index 8efb01b0..f33ac90b 100644 --- a/PathfinderAPI/Options/PluginCheckbox.cs +++ b/PathfinderAPI/Options/PluginCheckbox.cs @@ -15,11 +15,21 @@ public PluginCheckbox(string headerText, string descriptionText = null, bool def { } + public override Vector2 MinSize + { + get + { + var minsize = base.MinSize; + minsize.X = Math.Max(HeaderTextSize.X, DescriptionTextSize.X + 32); + return minsize; + } + } + public override void OnDraw(GameTime gameTime) { - DrawString(DrawData, HeaderText, HeaderColor, HeaderFont); - Value = CheckBox.doCheckBox(HacknetGuiId, DrawData.X.Value, DrawData.Y.Value + 34, Value, SelectedColor); - DrawString(DrawData.QuickAdd(32, 30), DescriptionText, DescriptionColor, DescriptionFont); - RenderedRectangle.doRectangleOutline(DrawData.X.Value, DrawData.Y.Value, DrawData.Width.Value, DrawData.Height.Value, 2, null); + DrawString(Position, HeaderText, HeaderColor, HeaderFont); + Value = CheckBox.doCheckBox(HacknetGuiId, (int)Position.X, (int)Position.Y + 34, Value, SelectedColor); + DrawString(Position + new Vector2(32, 30), DescriptionText, DescriptionColor, DescriptionFont); + RenderedRectangle.doRectangleOutline((int)Position.X, (int)Position.Y, (int)Size.X, (int)Size.Y, 2, null); } } diff --git a/PathfinderAPI/Options/PluginOptionTab.cs b/PathfinderAPI/Options/PluginOptionTab.cs index d82f24fd..33abadac 100644 --- a/PathfinderAPI/Options/PluginOptionTab.cs +++ b/PathfinderAPI/Options/PluginOptionTab.cs @@ -47,7 +47,6 @@ public PluginOptionTab AddOption(IPluginOption option) if(IsLoaded) { option.LoadContent(); - _SetDrawPositions(); } return this; } @@ -80,7 +79,6 @@ public virtual void LoadContent() Batch = GuiData.spriteBatch; foreach(var opt in options) opt.Value.LoadContent(); - _SetDrawPositions(); IsLoaded = true; } @@ -118,8 +116,13 @@ public virtual void OnDraw(GameTime gameTime) return; // Display options + int defOptionXPos = 80, defOptionYPos = 110; foreach (var option in this) + { + if(option.Value.TrySetOffset(new Vector2(defOptionXPos, defOptionYPos))) + defOptionYPos += 10 + (int)option.Value.Size.Y; option.Value.OnDraw(gameTime); + } } public IEnumerator> GetEnumerator() @@ -127,20 +130,6 @@ public IEnumerator> GetEnumerator() IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - private void _SetDrawPositions() - { - int defOptionXPos = 80, defOptionYPos = 110; - foreach (var pair in options) - { - var opt = pair.Value; - if(!opt.DrawData.X.HasValue) - opt.DrawData = opt.DrawData.Set(defOptionXPos); - if(!opt.DrawData.Y.HasValue) - opt.DrawData = opt.DrawData.Set(y: defOptionYPos); - defOptionYPos += 10 + opt.DrawData.Height.GetValueOrDefault(); - } - } - public bool ContainsKey(string key) => options.ContainsKey(key); diff --git a/PathfinderAPI/Options/PluginSlider.cs b/PathfinderAPI/Options/PluginSlider.cs index 3cb07413..d132bf51 100644 --- a/PathfinderAPI/Options/PluginSlider.cs +++ b/PathfinderAPI/Options/PluginSlider.cs @@ -17,23 +17,23 @@ public PluginSlider(string headerText, string descriptionText = null, float defa StepSize = stepSize; } - public override void SetSize() + public override Vector2 MinSize { - var headerSize = HeaderTextSize; - var descSize = DescriptionTextSize; - if(DrawData.Width == null) - DrawData = DrawData.Set(width: (int)(Math.Max(headerSize.X, descSize.X + SliderRangeWidth + 10))); - if(DrawData.Height == null) - DrawData = DrawData.Set(height: (int)(headerSize.Y + Math.Max(descSize.Y, SliderHeight + 9))); + get + { + var headerSize = HeaderTextSize; + var descSize = DescriptionTextSize; + return new Vector2(Math.Max(headerSize.X, descSize.X + SliderRangeWidth + 10), headerSize.Y + Math.Max(descSize.Y, SliderHeight + 9)); + } } public override void OnDraw(GameTime gameTime) { - DrawString(DrawData, HeaderText, HeaderColor, HeaderFont); + DrawString(Position, HeaderText, HeaderColor, HeaderFont); Value = SliderBar.doSliderBar( HacknetGuiId, - DrawData.X.Value + 5, - DrawData.Y.Value + 45, + (int)Position.X + 5, + (int)Position.Y + 45, SliderRangeWidth, SliderHeight, MaximumValue, @@ -42,7 +42,7 @@ public override void OnDraw(GameTime gameTime) StepSize ); - DrawString(DrawData.QuickAdd(SliderRangeWidth + 10, 45), DescriptionText, DescriptionColor, DescriptionFont); - RenderedRectangle.doRectangleOutline(DrawData.X.Value, DrawData.Y.Value, DrawData.Width.Value, DrawData.Height.Value, 2, null); + DrawString(Position + new Vector2(SliderRangeWidth + 10, 43), DescriptionText, DescriptionColor, DescriptionFont); + RenderedRectangle.doRectangleOutline((int)Position.X, (int)Position.Y, (int)Size.X, (int)Size.Y, 2, null); } } \ No newline at end of file diff --git a/PathfinderAPI/Options/PluginTextbox.cs b/PathfinderAPI/Options/PluginTextbox.cs index d04dbba3..d6b533b9 100644 --- a/PathfinderAPI/Options/PluginTextbox.cs +++ b/PathfinderAPI/Options/PluginTextbox.cs @@ -7,29 +7,9 @@ namespace Pathfinder.Options; public class PluginTextbox : BasePluginOption { - private int _textBoxWidthInPixels; - private int _textBoxWidth; - public int TextBoxWidth - { - get => _textBoxWidth; - set - { - _textBoxWidth = value; - if(TextBoxFont != null) - _textBoxWidthInPixels = ((int)TextBoxFont.MeasureString("o").X) * (_textBoxWidth - 1); - } - } + public int TextBoxWidth { get; set; } = 20; public int TextBoxLines { get; set; } = 1; - private SpriteFont _textBoxFont; - public SpriteFont TextBoxFont - { - get => _textBoxFont; - set - { - _textBoxFont = value; - _textBoxWidthInPixels = ((int)TextBoxFont.MeasureString("o").X) * (_textBoxWidth - 1); - } - } + public SpriteFont TextBoxFont { get; set; } public PluginTextbox(string headerText, string descriptionText = null, string defaultValue = null, string configDesc = null, string id = null) : base(headerText, descriptionText, defaultValue ?? "", configDesc, id) @@ -43,32 +23,44 @@ public override void LoadContent() base.LoadContent(); } - public override void SetSize() + public Vector2 TextBoxSize => + TextBoxWidth > 0 + ? TextBoxFont.MeasureString(new string('o', TextBoxWidth+1)) + : (TextBoxWidth == 0 + ? new Vector2(2, TextBoxFont.MeasureString("o").Y) + : TextBoxFont.MeasureString(Value) + new Vector2(5, 0) + ); + + public override Vector2 MinSize { - var characterSize = TextBoxFont.MeasureString("O"); - var headerSize = HeaderTextSize; - var descSize = DescriptionTextSize; - if(DrawData.Width == null) - DrawData = DrawData.Set(width: (int)(Math.Max(headerSize.X, descSize.X + _textBoxWidthInPixels + 10))); - if(DrawData.Height == null) - DrawData = DrawData.Set(height: (int)(headerSize.Y + Math.Max(descSize.Y, (TextBoxLines * characterSize.Y) + (Math.Max(0, TextBoxLines - 1)) * 25))); + get + { + var textBoxSize = TextBoxSize; + var headerSize = HeaderTextSize; + var descSize = DescriptionTextSize; + return new Vector2( + Math.Max(headerSize.X, descSize.X + textBoxSize.X + 10), + headerSize.Y + Math.Max(descSize.Y, textBoxSize.Y)// + (Math.Max(0, TextBoxLines - 1)) * 25) + ); + } } public override void OnDraw(GameTime gameTime) { - DrawString(DrawData, HeaderText, HeaderColor, HeaderFont); + var textBoxSize = TextBoxSize; + DrawString(Position, HeaderText, HeaderColor, HeaderFont); // Textbox doesn't support cliping the value to the width and doesn't take into account held down keys or special cases, like ctrl+backspace Value = TextBox.doTextBox( HacknetGuiId, - DrawData.X.Value, - DrawData.Y.Value + 34, - _textBoxWidthInPixels, + (int)Position.X, + (int)Position.Y + 34, + (int)textBoxSize.X, TextBoxLines, Value, TextBoxFont ); - DrawString(DrawData.QuickAdd(_textBoxWidthInPixels + 10, 32), DescriptionText, DescriptionColor, DescriptionFont); - RenderedRectangle.doRectangleOutline(DrawData.X.Value, DrawData.Y.Value, DrawData.Width.Value, DrawData.Height.Value, 2, null); + DrawString(Position + new Vector2(textBoxSize.X + 10, 32), DescriptionText, DescriptionColor, DescriptionFont); + RenderedRectangle.doRectangleOutline((int)Position.X, (int)Position.Y, (int)Size.X, (int)Size.Y, 2, null); } } \ No newline at end of file From 7bc7c627f77eb777cb4569b9ffbe5d5cec7fb1dc Mon Sep 17 00:00:00 2001 From: "George L. Albany" Date: Thu, 28 Apr 2022 10:31:49 -0400 Subject: [PATCH 12/14] Update PluginOptionTab for DrawData removal PluginOptionTab: Removed ButtonData Added ButtonRect, ButtonPosition, ButtonOffset Added TrySetButtonOffset(Point) Draws tab button using ButtonPosition and ButtonRect's Size values PathfinderOptionsMenu relies on tab button offset drawing --- PathfinderAPI/Options/OptionsManager.cs | 2 ++ .../Options/PathfinderOptionsMenu.cs | 13 +++++------- PathfinderAPI/Options/PluginOptionTab.cs | 20 +++++++++++++------ 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/PathfinderAPI/Options/OptionsManager.cs b/PathfinderAPI/Options/OptionsManager.cs index 9fa8cf70..43d37a71 100644 --- a/PathfinderAPI/Options/OptionsManager.cs +++ b/PathfinderAPI/Options/OptionsManager.cs @@ -1,5 +1,7 @@ using Pathfinder.GUI; using BepInEx.Configuration; +using HarmonyLib; +using Hacknet; namespace Pathfinder.Options; diff --git a/PathfinderAPI/Options/PathfinderOptionsMenu.cs b/PathfinderAPI/Options/PathfinderOptionsMenu.cs index c291e7a1..8c5fa59d 100644 --- a/PathfinderAPI/Options/PathfinderOptionsMenu.cs +++ b/PathfinderAPI/Options/PathfinderOptionsMenu.cs @@ -72,16 +72,13 @@ internal static bool Draw(ref OptionsMenu __instance, GameTime gameTime) #pragma warning disable 618 var tabs = OptionsManager.Tabs; #pragma warning restore 618 - - int tabX = 10; + + int tabX = 10, tabY = 70; foreach (var tab in OptionsManager.PluginTabs) { - if(tab.ButtonData.X == null) - { - tab.ButtonData.Set(tabX); - tabX += 10 + tab.ButtonData.Width.GetValueOrDefault(); - } + if(tab.TrySetButtonOffset(new Point(tabX, tabY))) + tabX += 10 + tab.ButtonRect.Width; tab.OnDraw(gameTime); } @@ -91,7 +88,7 @@ internal static bool Draw(ref OptionsMenu __instance, GameTime gameTime) CurrentTabId = tab.Name; var active = CurrentTabId == tab.Name; // Display tab button - if (Button.doButton(tab.ButtonID, tabX, 70, 128, 20, tab.Name, active ? Color.Green : Color.Gray)) + if (Button.doButton(tab.ButtonID, tabX, tabY, 128, 20, tab.Name, active ? Color.Green : Color.Gray)) { CurrentTabId = tab.Name; break; diff --git a/PathfinderAPI/Options/PluginOptionTab.cs b/PathfinderAPI/Options/PluginOptionTab.cs index 33abadac..c8760ab0 100644 --- a/PathfinderAPI/Options/PluginOptionTab.cs +++ b/PathfinderAPI/Options/PluginOptionTab.cs @@ -20,14 +20,22 @@ public class PluginOptionTab : IReadOnlyDictionary public SpriteBatch Batch { get; internal set; } public int HacknetGuiId { get; } public bool IsLoaded { get; internal set; } - public PluginOptionDrawData ButtonData { get; set; } = new PluginOptionDrawData + public Rectangle ButtonRect { get; set; } = new Rectangle { X = 0, - Y = 70, + Y = 0, Width = 128, Height = 20 }; + public Point ButtonPosition => ButtonRect.Location + ButtonOffset; + public Point ButtonOffset { get; protected set; } + public virtual bool TrySetButtonOffset(Point offset) + { + ButtonOffset = offset; + return ButtonOffset == offset; + } + public PluginOptionTab(string tabName, string tabId = null) { TabName = tabName; @@ -101,10 +109,10 @@ public virtual void OnDraw(GameTime gameTime) var active = PathfinderOptionsMenu.CurrentTabId == Id; // Display tab button if (Button.doButton(HacknetGuiId, - ButtonData.X.GetValueOrDefault(), - ButtonData.Y.GetValueOrDefault(), - ButtonData.Width.GetValueOrDefault(), - ButtonData.Height.GetValueOrDefault(), + ButtonPosition.X, + ButtonPosition.Y, + ButtonRect.Width, + ButtonRect.Height, TabName, active ? Color.Green : Color.Gray)) { From ba9f3619c27dc0d7b0dde50a5dcfe8ea04ec2c6c Mon Sep 17 00:00:00 2001 From: "George L. Albany" Date: Mon, 2 May 2022 03:56:33 -0400 Subject: [PATCH 13/14] Plugin Options handle config entries automatically Added CustomOptionsLoadEvent: Runs when opening the options screen Inverse of CustomOptionsSaveEvent Corrected MiscPatches.NoSteamErrorMessageIL for PathfinderOptions.DisableSteamCloudError's conversion to a PluginCheckbox Moved OptionsTab to a new file PluginOptionTab: Added HacknetPlugin Plugin Constructor relies on assigning a plugin to the tab Modified OnSave(ConfigFile) to Save(), by default forces Plugin.Config to Save to disk Modified OnLoad(ConfigFile) to Load(), by default does nothing IPluginOption: Removed OnSave and OnLoad Added ConfigEntryBase IPluginOption.ConfigEntry BasePluginOption: Added ConfigEntry TypedConfigEntry Automatically handles the setting and retrieving of its config value Added static string MakeIdFrom(string, string) to replace old OptionsManager.GetIdFrom OptionsManager: PluginTabs made into a Dictionary PluginTabs made private and renamed to _PluginTabs Public PluginTabs accessor has become a ReadOnlyDictionary of _PluginTabs Added const string PluginOptionTabIdPostfix Added MakeTabDataFrom(HacknetPlugin, string) Reworked GetIdFrom as GetIdFrom(HacknetPlugin, string, string) All RegisterTab methods require a HacknetPlugin argument now All RegisterOption methods also require a HacknetPlugin argument now Added ThrowDuplicateIdAttempt(string) for consistent exception response Removed OPTION_TAG from PathfinderOptions Removed Rectangle Outline from PluginCheckbox, PluginSlider, and PluginTextbox PathfinderAPIPlugin: Added PathfinderAPIPlugin Instance for singleton reference Postfix patched HacknetPlugin.PostLoad to automatically load and save configs (Is disabled if it does not call HacknetPlugin.PostLoad) MainMenuOverride: Call PFAPILoaded in PathfinderAPIPlugin.Load, removed patching Added UpdaterTab MainMenuOverride always refers to MainMenuOverride.NoRestartPrompt for NoRestartPrompt option MainMenuOverride always refers to MainMenuOverride.IsEnabledBox for Enabled option Implementation currently ignores the old config implmentation PathfinderUpdaterPlugin: Added PathfinderUpdaterPlugin Instance for singleton reference Replaced uses of IsEnabled with MainMenuOverride.IsEnabledBox Replaced uses of IncludePreReleases with MainMenuOverride.IncludePrerelease RestartPopupScreen: Made NoRestartPrompt PluginCheckbox Constructor requires a ConfigFile argument Moved buttons and checkbox for the sake of polish Forcibly save data if NoRestartPrompt's value changes Updated OptionAttribute --- .../Event/Options/CustomOptionsLoadEvent.cs | 6 + PathfinderAPI/Meta/Load/OptionAttribute.cs | 2 +- .../Meta/Load/OptionsTabAttribute.cs | 1 - PathfinderAPI/MiscPatches.cs | 2 +- PathfinderAPI/Options/BasePluginOption.cs | 43 ++++-- PathfinderAPI/Options/OptionsManager.cs | 136 +++++++++++------- PathfinderAPI/Options/OptionsTab.cs | 17 +++ PathfinderAPI/Options/PathfinderOptions.cs | 4 +- .../Options/PathfinderOptionsMenu.cs | 22 ++- PathfinderAPI/Options/PluginCheckbox.cs | 1 - PathfinderAPI/Options/PluginOptionTab.cs | 16 +-- PathfinderAPI/Options/PluginSlider.cs | 1 - PathfinderAPI/Options/PluginTextbox.cs | 1 - PathfinderAPI/PathfinderAPIPlugin.cs | 16 ++- PathfinderUpdater/MainMenuOverride.cs | 28 ++-- PathfinderUpdater/PathfinderUpdaterPlugin.cs | 15 +- PathfinderUpdater/RestartPopupScreen.cs | 29 ++-- 17 files changed, 211 insertions(+), 129 deletions(-) create mode 100644 PathfinderAPI/Event/Options/CustomOptionsLoadEvent.cs create mode 100644 PathfinderAPI/Options/OptionsTab.cs diff --git a/PathfinderAPI/Event/Options/CustomOptionsLoadEvent.cs b/PathfinderAPI/Event/Options/CustomOptionsLoadEvent.cs new file mode 100644 index 00000000..2def3a90 --- /dev/null +++ b/PathfinderAPI/Event/Options/CustomOptionsLoadEvent.cs @@ -0,0 +1,6 @@ +namespace Pathfinder.Event.Options; + +public class CustomOptionsLoadEvent : PathfinderEvent +{ + public CustomOptionsLoadEvent() { } +} \ No newline at end of file diff --git a/PathfinderAPI/Meta/Load/OptionAttribute.cs b/PathfinderAPI/Meta/Load/OptionAttribute.cs index a64af934..66a0d23a 100644 --- a/PathfinderAPI/Meta/Load/OptionAttribute.cs +++ b/PathfinderAPI/Meta/Load/OptionAttribute.cs @@ -56,6 +56,6 @@ protected internal override void CallOn(HacknetPlugin plugin, MemberInfo targett if(option == null) throw new InvalidOperationException($"IPluginOption not set to a default value, IPluginOption members should be set before HacknetPlugin.Load() is called"); - OptionsManager.GetOrRegisterTab(TabName, TabId).AddOption(option); + OptionsManager.GetOrRegisterTab(plugin, TabName, TabId).AddOption(option); } } \ No newline at end of file diff --git a/PathfinderAPI/Meta/Load/OptionsTabAttribute.cs b/PathfinderAPI/Meta/Load/OptionsTabAttribute.cs index 61fd5272..aac27212 100644 --- a/PathfinderAPI/Meta/Load/OptionsTabAttribute.cs +++ b/PathfinderAPI/Meta/Load/OptionsTabAttribute.cs @@ -1,6 +1,5 @@ using System.Reflection; using BepInEx.Hacknet; -using Pathfinder.Options; namespace Pathfinder.Meta.Load; diff --git a/PathfinderAPI/MiscPatches.cs b/PathfinderAPI/MiscPatches.cs index 1d58d7cc..b5df43e5 100644 --- a/PathfinderAPI/MiscPatches.cs +++ b/PathfinderAPI/MiscPatches.cs @@ -51,7 +51,7 @@ private static void NoSteamErrorMessageIL(ILContext il) c.RemoveRange(3); c.Emit(OpCodes.Ldsfld, AccessTools.Field(typeof(PathfinderOptions), nameof(PathfinderOptions.DisableSteamCloudError))); - c.Emit(OpCodes.Ldfld, AccessTools.Field(typeof(OptionCheckbox), nameof(OptionCheckbox.Value))); + c.Emit(OpCodes.Callvirt, AccessTools.Method(typeof(PluginCheckbox), $"get_{nameof(PluginCheckbox.Value)}")); c.GotoNext(MoveType.Before, x => x.MatchLdstr(out _)); c.Next.Operand = "Steam Cloud saving disabled by Pathfinder"; diff --git a/PathfinderAPI/Options/BasePluginOption.cs b/PathfinderAPI/Options/BasePluginOption.cs index 694956ca..8890c5ad 100644 --- a/PathfinderAPI/Options/BasePluginOption.cs +++ b/PathfinderAPI/Options/BasePluginOption.cs @@ -9,24 +9,44 @@ namespace Pathfinder.Options; public interface IPluginOption { PluginOptionTab Tab { get; set; } + public ConfigEntryBase ConfigEntry { get; } string Id { get; } Rectangle Rectangle { get; set; } Vector2 Size { get; } bool TrySetOffset(Vector2 offset); void LoadContent(); void OnDraw(GameTime gameTime); - void OnSave(ConfigFile config); - void OnLoad(ConfigFile config); } public abstract class BasePluginOption : IPluginOption { public PluginOptionTab Tab { get; set; } + public virtual ConfigEntryBase ConfigEntry => TypedConfigEntry; + public virtual ConfigEntry TypedConfigEntry { get; protected set; } public Rectangle Rectangle { get; set; } public int HacknetGuiId { get; private set; } - public virtual ValueT Value { get; set; } + public virtual ValueT Value + { + get + { + if(TypedConfigEntry == null) + { + if(Tab.Plugin.Config.TryGetEntry(Tab.Id, Id, out var entry)) + TypedConfigEntry = entry; + else + TypedConfigEntry = Tab.Plugin.Config.Bind(Tab.Id, Id, DefaultValue, ConfigDescription ?? ""); + } + return TypedConfigEntry.Value; + } + set + { + if(TypedConfigEntry == null) + TypedConfigEntry = Tab.Plugin.Config.Bind(Tab.Id, Id, DefaultValue, ConfigDescription ?? ""); + TypedConfigEntry.Value = value; + } + } public virtual ValueT DefaultValue { get; set; } = default; public virtual string HeaderText { get; protected set; } public virtual string DescriptionText { get; protected set; } @@ -50,7 +70,7 @@ public Vector2 Size get { var minsize = MinSize; - return new Vector2(Math.Max(Rectangle.Width, minsize.X), Math.Max(Rectangle.Height, minsize.Y)); + return new Vector2(Math.Max(Rectangle.Width, minsize.X), Math.Max(Rectangle.Height, minsize.Y)); } } public virtual Vector2 MinSize @@ -63,13 +83,16 @@ public virtual Vector2 MinSize } } + protected static string MakeIdFrom(string name, string id) + => id ?? string.Concat(name.Where(c => !char.IsWhiteSpace(c) && c != '=')); + protected BasePluginOption(string headerText, string descriptionText = null, ValueT defaultValue = default, string configDesc = null, string id = null) { HeaderText = headerText; DescriptionText = descriptionText; DefaultValue = defaultValue; ConfigDescription = configDesc; - Id = OptionsManager.GetIdFrom(HeaderText, id); + Id = MakeIdFrom(headerText, id); } public bool TrySetOffset(Vector2 offset) @@ -104,16 +127,6 @@ public virtual void LoadContent() } public abstract void OnDraw(GameTime gameTime); - public virtual void OnSave(ConfigFile config) - { - config.Bind(Tab.Id, Id, DefaultValue, ConfigDescription ?? ""); - } - public virtual void OnLoad(ConfigFile config) - { - if(config.TryGetEntry(Tab.Id, Id, out var entry)) - Value = entry.Value; - else Value = DefaultValue; - } protected void DrawString(Vector2 pos, string text, Color? color = null, SpriteFont font = null) { diff --git a/PathfinderAPI/Options/OptionsManager.cs b/PathfinderAPI/Options/OptionsManager.cs index 43d37a71..f57ac09c 100644 --- a/PathfinderAPI/Options/OptionsManager.cs +++ b/PathfinderAPI/Options/OptionsManager.cs @@ -1,7 +1,9 @@ -using Pathfinder.GUI; using BepInEx.Configuration; using HarmonyLib; using Hacknet; +using BepInEx.Hacknet; +using System.Collections.ObjectModel; +using BepInEx; namespace Pathfinder.Options; @@ -10,117 +12,143 @@ public static class OptionsManager { [Obsolete("Use PluginTabs")] public readonly static Dictionary Tabs = new Dictionary(); - public readonly static List PluginTabs = new List(); + private readonly static Dictionary _PluginTabs = new Dictionary(); + private static ReadOnlyDictionary _readonlyPluginTabs; + public static ReadOnlyDictionary PluginTabs => _readonlyPluginTabs ??= new ReadOnlyDictionary(_PluginTabs); - [Obsolete("Use RegisterOption or PluginOptionTab.AddOption")] - public static void AddOption(string tag, Option opt) + public const string PluginOptionTabIdPostfix = "Options"; + public static (string Name, string Id) MakeTabDataFrom(HacknetPlugin plugin, string tabName) { - if (!Tabs.TryGetValue(tag, out var tab)) { - tab = new OptionsTab(tag); - Tabs.Add(tag, tab); - } - tab.Options.Add(opt); + var metadata = MetadataHelper.GetMetadata(plugin); + var pair = HacknetChainloader.Instance.Plugins.First(dataPair => dataPair.Value.Metadata.GUID == metadata.GUID); + return (tabName ?? pair.Value.Metadata.Name, pair.Value.Metadata.GUID+PluginOptionTabIdPostfix); } + public static string GetIdFrom(HacknetPlugin plugin, string name, string id = null) + => id ?? (MakeTabDataFrom(plugin, name).Id); + public static bool TryGetTab(string tabId, out TabT tab) where TabT : PluginOptionTab { - tab = PluginTabs.Find(t => t.Id == tabId) as TabT; + tab = null; + if(_PluginTabs.TryGetValue(tabId, out var possibleTab)) + tab = possibleTab as TabT; return tab != null; } - public static PluginOptionTab GetTab(string tabId) + public static PluginOptionTab GetTab(string tabId, bool shouldThrow = false) where TabT : PluginOptionTab { if(!TryGetTab(tabId, out TabT tab)) return tab; + if(shouldThrow) + throw new KeyNotFoundException($"The given key '{tabId}' was not present in the dictionary."); return null; } - public static PluginOptionTab RegisterTab(string tabName, string tabId = null) + public static PluginOptionTab RegisterTab(HacknetPlugin plugin = null, string tabName = null, string tabId = null) { - if(GetTab(GetIdFrom(tabName, tabId)) != null) - throw new InvalidOperationException("Can not register tabs with a registered id"); - return RegisterTab(new PluginOptionTab(tabName, tabId)); + var pair = MakeTabDataFrom(plugin, tabName); + tabName ??= pair.Name; + tabId ??= pair.Id; + Console.WriteLine($"Registering tab {tabId} with name {tabName}"); + if(GetTab(GetIdFrom(plugin, tabName, tabId)) != null) + ThrowDuplicateIdAttempt(tabId); + return RegisterTab(plugin, new PluginOptionTab(plugin, tabName, tabId)); } - public static TabT RegisterTab(TabT tab) + public static TabT RegisterTab(HacknetPlugin plugin, TabT tab) where TabT : PluginOptionTab { if(GetTab(tab.Id) != null) - throw new InvalidOperationException("Can not register new tabs with an already registered id"); - PluginTabs.Add(tab); + ThrowDuplicateIdAttempt(tab.Id); + tab.Plugin = plugin; + _PluginTabs[tab.Id] = tab; + return tab; + } + + public static TabT RegisterTab(HacknetPlugin plugin) + where TabT : PluginOptionTab, new() + => RegisterTab(plugin, new TabT()); + + public static PluginOptionTab GetOrRegisterTab(HacknetPlugin plugin, string tabName = null) + { + var pair = MakeTabDataFrom(plugin, tabName); + if(!TryGetTab(GetIdFrom(plugin, pair.Name, pair.Id), out PluginOptionTab tab)) + tab = RegisterTab(plugin, pair.Name, pair.Id); return tab; } - public static PluginOptionTab GetOrRegisterTab(string tabName, string tabId = null) + public static PluginOptionTab GetOrRegisterTab(HacknetPlugin plugin, string tabName, string tabId = null) { - if(!TryGetTab(GetIdFrom(tabName, tabId), out PluginOptionTab tab)) - tab = RegisterTab(tabName, tabId); + if(!TryGetTab(GetIdFrom(plugin, tabName, tabId), out PluginOptionTab tab)) + tab = RegisterTab(plugin, tabName, tabId); return tab; } - public static TabT GetOrRegisterTab(string tabName, string tabId, Func generatorFunc) + public static TabT GetOrRegisterTab(HacknetPlugin plugin, string tabName, string tabId, Func genFunc) where TabT : PluginOptionTab { - if(!TryGetTab(GetIdFrom(tabName, tabId), out TabT tab)) - tab = RegisterTab(generatorFunc()); + if(!TryGetTab(GetIdFrom(plugin, tabName, tabId), out TabT tab)) + tab = RegisterTab(plugin, genFunc()); return tab; } - public static T RegisterOption(string tabName, string tabId, T option) + public static TabT GetOrRegisterTab(HacknetPlugin plugin, Func genFunc) + where TabT : PluginOptionTab + { + var pair = MakeTabDataFrom(plugin, null); + return GetOrRegisterTab(plugin, pair.Name, pair.Id, genFunc); + } + + public static T RegisterOption(HacknetPlugin plugin, string tabName, string tabId, T option) where T : IPluginOption { - GetOrRegisterTab(tabName, tabId).AddOption(option); + GetOrRegisterTab(plugin, tabName, tabId).AddOption(option); return option; } - public static T RegisterOption(string tabName, T option) + public static T RegisterOption(HacknetPlugin plugin, string tabName, T option) where T : IPluginOption { - return RegisterOption(tabName, null, option); + return RegisterOption(plugin, tabName, null, option); } - public static T RegisterOption(string tabName, string tabId = null) + public static T RegisterOption(HacknetPlugin plugin, string tabName, string tabId = null) where T : IPluginOption, new() { - return RegisterOption(tabName, tabId, (T)Activator.CreateInstance(typeof(T))); + return RegisterOption(plugin, tabName, tabId, (T)Activator.CreateInstance(typeof(T))); } - public static void OnConfigSave(ConfigFile config) + public static void OnConfigSave(string tabId, ConfigFile config) { - foreach(var tab in PluginTabs) - tab.OnSave(config); + _PluginTabs.GetValueSafe(tabId)?.Save(); } - public static void OnConfigLoad(ConfigFile config) + public static void OnConfigLoad(string tabId, ConfigFile config) { - foreach(var tab in PluginTabs) - tab.OnLoad(config); + _PluginTabs.GetValueSafe(tabId)?.Load(); } - public static string GetIdFrom(string name, string id = null) - => id ?? string.Concat(name.Where(c => !char.IsWhiteSpace(c) && c != '=')); - - [HarmonyPostfix] - [HarmonyPatch(typeof(Game1), nameof(Game1.LoadContent))] - private static void OnPostGame1LoadContent(Game1 __instance) + [Obsolete("Use RegisterOption or PluginOptionTab.AddOption")] + public static void AddOption(string tag, Option opt) { - foreach(var tab in PluginTabs) - tab.LoadContent(); + if (!Tabs.TryGetValue(tag, out var tab)) { + tab = new OptionsTab(tag); + Tabs.Add(tag, tab); + } + tab.Options.Add(opt); } -} - -[Obsolete("Use PluginOptionTab")] -public class OptionsTab -{ - public string Name; - public List