diff --git a/MiraAPI/MiraApiSettings.cs b/MiraAPI/MiraApiSettings.cs index 1047fcd..6d799b0 100644 --- a/MiraAPI/MiraApiSettings.cs +++ b/MiraAPI/MiraApiSettings.cs @@ -202,12 +202,6 @@ public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config) [LocalSliderSetting(min: 0.3f, max: 2f, suffixType: MiraNumberSuffixes.Multiplier, formatString: "0.00", displayValue: true)] public ConfigEntry TopRightButtonsFactorSlider { get; private set; } = config.Bind("MiraApi.VisualsUi", "MiraApi.TopRightButtonScaleFactor", 1f); - /// - /// Gets the scale of the match info guide. - /// - [LocalSliderSetting(min: 0.5f, max: 1f, suffixType: MiraNumberSuffixes.Multiplier, formatString: "0.00", displayValue: true)] - public ConfigEntry MatchInfoGuideScaleFactor { get; private set; } = config.Bind("MiraApi.VisualsUi", "MiraApi.MatchInfoGuideScaleFactor", 1f); - /// /// Gets the fps specified by the player. /// @@ -226,11 +220,24 @@ public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config) [LocalToggleSetting] public ConfigEntry ShowKeybinds { get; private set; } = config.Bind("MiraApi.VisualsUi", "MiraApi.ShowKeybindsOnButtons", true); + /// + /// Gets the scale of the match info guide. + /// + [LocalSliderSetting(min: 0.5f, max: 1f, suffixType: MiraNumberSuffixes.Multiplier, formatString: "0.00", displayValue: true)] + public ConfigEntry MatchInfoGuideScaleFactor { get; private set; } = config.Bind("MiraApi.MatchInfoGuide", "MiraApi.MatchInfoGuideScaleFactor", 1f); + /// /// Gets the flag indicating whether the wiki is on the bottom row. /// [LocalToggleSetting] - public ConfigEntry WikiOnBottomRow { get; private set; } = config.Bind("MiraApi.VisualsUi", "MiraApi.WikiOnBottomRow", true); + public ConfigEntry WikiOnBottomRow { get; private set; } = config.Bind("MiraApi.MatchInfoGuide", "MiraApi.WikiOnBottomRow", true); + + /// + /// Gets the flag indicating the position for the wiki's tabs. + /// + [LocalEnumSetting(names: ["MiraApi.WikiTabPosition.LeftSide", "MiraApi.WikiTabPosition.RightSide", "MiraApi.WikiTabPosition.Below"])] + public ConfigEntry WikiTabPosition { get; private set; } = + config.Bind("MiraApi.MatchInfoGuide", "MiraApi.WikiTabPosition", MiraApiPlugin.IsMobile ? MatchInfoTabPos.RightSide : MatchInfoTabPos.LeftSide); /// /// Gets whether to show the red flash from sabotages. @@ -250,3 +257,10 @@ public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config) [LocalToggleSetting] public ConfigEntry EnableVariableColors { get; private set; } = config.Bind("MiraApi.Accessibility", "MiraApi.EnableVariableColors", true); } + +public enum MatchInfoTabPos +{ + LeftSide, + RightSide, + Below, +} diff --git a/MiraAPI/Patches/LocalSettings/OptionsMenuPatches.cs b/MiraAPI/Patches/LocalSettings/OptionsMenuPatches.cs index af70764..6b64159 100644 --- a/MiraAPI/Patches/LocalSettings/OptionsMenuPatches.cs +++ b/MiraAPI/Patches/LocalSettings/OptionsMenuPatches.cs @@ -2,9 +2,9 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using HarmonyLib; +using Il2CppInterop.Runtime.InteropTypes.Arrays; using MiraAPI.LocalSettings; using MiraAPI.Utilities.Assets; -using Reactor.Utilities.Extensions; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; @@ -15,12 +15,18 @@ namespace MiraAPI.Patches.LocalSettings; public static class OptionsMenuPatches { private static readonly Dictionary> TabButtons = []; - private static int currentPage = 1; + private static int _currentPage = 1; internal static OptionsMenuBehaviour? Instance { get; private set; } - [SuppressMessage("Critical Code Smell", "S2223:Non-constant static fields should not be visible", Justification = "Internal behaviour that does not need property-level validation.")] - [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Read above.")] + [SuppressMessage( + "Critical Code Smell", + "S2223:Non-constant static fields should not be visible", + Justification = "Internal behaviour that does not need property-level validation.")] + [SuppressMessage( + "StyleCop.CSharp.MaintainabilityRules", + "SA1401:Fields should be private", + Justification = "Read above.")] internal static BoxCollider2D MaskCollider = null!; private static SpriteRenderer? background; @@ -69,7 +75,7 @@ public static class OptionsMenuPatches MaskCollider.isTrigger = true; MaskCollider.enabled = true; - currentPage = 1; + _currentPage = 1; TabButtons.Clear(); float yOffset = 0; var i = 0; @@ -83,7 +89,7 @@ public static class OptionsMenuPatches { var button = settings.CreateTabButton(__instance, ref tabIdx, ref yOffset); button.GetComponent().Content = tab; - button.SetActive(page == currentPage); + button.SetActive(page == _currentPage); if (!TabButtons.TryGetValue(page, out var list)) { @@ -105,20 +111,6 @@ public static class OptionsMenuPatches } } - static void ChangePage(int increase) - { - currentPage += increase; - if (currentPage > TabButtons.Count) - { - currentPage = 1; - } - - if (currentPage < 1) - { - currentPage = TabButtons.Count; - } - } - // Create page buttons // TODO: In-game menu has no close button. I tried making one from scratch but failed. Add one later plz if (TabButtons.Count <= 1) @@ -126,44 +118,56 @@ public static class OptionsMenuPatches return; } - var nextButton = Object.Instantiate(__instance.BackButton, __instance.transform).GetComponent(); - nextButton.transform.localPosition = new Vector3(3.2f, 2.6f, -10); - nextButton.transform.localScale = new Vector2(0.25f, 0.25f); - var nextRend = nextButton.GetComponent(); - nextRend.sprite = MiraAssets.NextButton.LoadAsset(); - nextButton.gameObject.GetComponent().DestroyImmediate(); + CreatePageButton(__instance, 3.2f, false, 1); + CreatePageButton(__instance, 2.9f, true, -1); + } - var nextRollover = nextButton.gameObject.AddComponent(); - nextRollover.Target = nextRend; - nextRollover.OverColor = Palette.AcceptedGreen; + private static void CreatePageButton(OptionsMenuBehaviour menu, float x, bool flip, int direction) + { + var obj = new GameObject("PageButton") { layer = 5 }; + obj.transform.SetParent(menu.transform); + obj.transform.localPosition = new Vector3(x, 2.6f, -10); + obj.transform.localScale = new Vector3(0.25f, 0.25f, 1); - nextButton.OnClick = new Button.ButtonClickedEvent(); - nextButton.OnClick.AddListener( + var rend = obj.AddComponent(); + rend.sprite = MiraAssets.NextButton.LoadAsset(); + rend.flipX = flip; + + var col = obj.AddComponent(); + col.size = rend.sprite.bounds.size; + + var rollover = obj.AddComponent(); + rollover.Target = rend; + rollover.OverColor = Palette.AcceptedGreen; + rollover.OutColor = Color.white; + + var button = obj.AddComponent(); + button.Colliders = new Il2CppReferenceArray([col]); + button.OnClick = new Button.ButtonClickedEvent(); + button.OnClick.AddListener( (UnityAction)(() => { - ChangePage(1); + ChangePage(direction); UpdatePages(); })); + button.OnMouseOver = new UnityEvent(); + button.OnMouseOver.AddListener((UnityAction)(() => rollover.DoMouseOver())); + button.OnMouseOut = new UnityEvent(); + button.OnMouseOut.AddListener((UnityAction)(() => rollover.DoMouseOut())); + } - var backButton = Object.Instantiate(__instance.BackButton, __instance.transform).GetComponent(); - backButton.transform.localPosition = new Vector3(2.9f, 2.6f, -10); - backButton.transform.localScale = new Vector2(0.25f, 0.25f); - var backRend = backButton.GetComponent(); - backRend.sprite = MiraAssets.NextButton.LoadAsset(); - backRend.flipX = true; - backButton.gameObject.GetComponent().DestroyImmediate(); + private static void ChangePage(int increase) + { + _currentPage += increase; + if (_currentPage > TabButtons.Count) + { + _currentPage = 1; + } - var backRollover = backButton.gameObject.AddComponent(); - backRollover.Target = backRend; - backRollover.OverColor = Palette.AcceptedGreen; - - backButton.OnClick = new Button.ButtonClickedEvent(); - backButton.OnClick.AddListener( - (UnityAction)(() => - { - ChangePage(-1); - UpdatePages(); - })); + if (_currentPage < 1) + { + _currentPage = TabButtons.Count; + } } /// @@ -194,7 +198,7 @@ public static class OptionsMenuPatches public static void OpenPostfix() { LocalSettingsManager.AvailableTabs.ForEach(CustomClose); - currentPage = 1; + _currentPage = 1; UpdatePages(); } @@ -204,9 +208,7 @@ public static class OptionsMenuPatches { pages.Value? .Where(x => x != null) - .Do( - x => x?.SetActive( - pages.Key == currentPage)); + .Do(x => x?.SetActive(pages.Key == _currentPage)); } } diff --git a/MiraAPI/Patches/RoleGuidePatches.cs b/MiraAPI/Patches/RoleGuidePatches.cs index bc86a9e..5354c04 100644 --- a/MiraAPI/Patches/RoleGuidePatches.cs +++ b/MiraAPI/Patches/RoleGuidePatches.cs @@ -40,6 +40,8 @@ public static class RoleGuidePatches private static readonly List ModifierEntries = []; private static readonly List UntrackedRolePanelDiagnostics = []; + private static GridLayoutGroup tabButtonContainer; + private static SpriteRenderer tabContainerRend; private static GameObject searchComponentContainer; private static SpriteRenderer _searchIconIdle; private static SpriteRenderer _searchIconHover; @@ -317,6 +319,8 @@ public static class RoleGuidePatches Initialize(__instance); } + SetUpTabGrids(LocalSettingsTabSingleton.Instance.WikiTabPosition.Value); + if (RoleEntries.Count == 0) { BuildEntries(); @@ -385,6 +389,40 @@ public static class RoleGuidePatches return false; } + private static void SetUpTabGrids(MatchInfoTabPos position) + { + tabButtonContainer.startCorner = GridLayoutGroup.Corner.UpperLeft; + tabButtonContainer.startAxis = GridLayoutGroup.Axis.Horizontal; + tabButtonContainer.constraint = GridLayoutGroup.Constraint.FixedColumnCount; + tabContainerRend.transform.localScale = new Vector3(0.4f, 0.4125f, 1); + tabContainerRend.transform.localRotation = Quaternion.Euler(new Vector3(0, 0, 0)); + switch (position) + { + case MatchInfoTabPos.LeftSide: + tabButtonContainer.childAlignment = TextAnchor.MiddleLeft; + tabButtonContainer.transform.localPosition = new Vector3(-3.95f, 0, -0.2f); + tabContainerRend.transform.localPosition = new Vector3(0.325f, 0, 0); + break; + case MatchInfoTabPos.RightSide: + tabButtonContainer.childAlignment = TextAnchor.MiddleRight; + tabButtonContainer.transform.localPosition = new Vector3(3.95f, 0, -0.2f); + tabContainerRend.transform.localPosition = new Vector3(-0.325f, 0, 0); + break; + default: + tabButtonContainer.startCorner = GridLayoutGroup.Corner.LowerLeft; + tabButtonContainer.childAlignment = TextAnchor.LowerCenter; + tabButtonContainer.startAxis = GridLayoutGroup.Axis.Vertical; + tabButtonContainer.constraint = GridLayoutGroup.Constraint.FixedRowCount; + tabButtonContainer.transform.localPosition = new Vector3(0, -2.15f, -0.2f); + tabContainerRend.transform.localPosition = new Vector3(-0.025f, 0.3f, 0); + tabContainerRend.transform.localRotation = Quaternion.Euler(new Vector3(0, 0, 270)); + tabContainerRend.transform.localScale = new Vector3(0.4f, 0.475f, 1); + break; + } + + tabButtonContainer.constraintCount = 1; + } + private static void Initialize(MatchInfoGuide instance) { if (_uiInitialized) @@ -411,6 +449,17 @@ public static class RoleGuidePatches searchComponentContainer.transform.localPosition = Vector3.zero; instance.MatchInfoParent.transform.GetChild(1).transform.SetParent(searchComponentContainer.transform); + var newContainer = new GameObject("TabButtonContainer"); + newContainer.transform.SetParent(instance.MatchInfoParent.transform); + tabButtonContainer = newContainer.AddComponent(); + tabButtonContainer.cellSize = new Vector2(0.65f, 0.55f); + tabButtonContainer.rectTransform.offsetMax = tabButtonContainer.rectTransform.offsetMin = Vector2.zero; + tabContainerRend = new GameObject("BackRenderer").AddComponent(); + tabContainerRend.transform.SetParent(newContainer.transform); + tabContainerRend.sprite = MiraAssets.TabButtonContainer.LoadAsset(); + tabContainerRend.transform.localPosition = Vector3.zero; + tabContainerRend.gameObject.layer = 5; + var sortingOrderButton = Object.Instantiate( HudManager.Instance.SettingsButton, searchComponentContainer.transform); @@ -661,6 +710,7 @@ public static class RoleGuidePatches MiraAssets.WikiModifiersButtonIdleSprite, MiraAssets.WikiModifiersButtonOpenSprite, MiraAssets.WikiModifiersButtonHoverSprite); + modifiersButton.name = "ModifiersButton"; modifiersButton.OnClick = new Button.ButtonClickedEvent(); modifiersButton.OnClick.AddListener((Action)(() => OpenModifiersTab())); instance.TabButtons.Add(modifiersButton); @@ -765,6 +815,8 @@ public static class RoleGuidePatches buttonCollider.size = new Vector2(0.8f, 0.74f); buttonCollider.offset = Vector2.zero; button.transform.localScale = new Vector3(0.7f, 0.7f, 1); + button.transform.SetParent(tabButtonContainer.transform); + button.gameObject.AddComponent(); } private static void SetupSearchButton(PassiveButton button, string objName, Action onClick, Sprite inactive, Sprite hover, Vector3 pos, out SpriteRenderer inactiveSprite, out SpriteRenderer hoverSprite) diff --git a/MiraAPI/Resources/Locale/en_US.xml b/MiraAPI/Resources/Locale/en_US.xml index be6809d..9073a6f 100644 --- a/MiraAPI/Resources/Locale/en_US.xml +++ b/MiraAPI/Resources/Locale/en_US.xml @@ -2,6 +2,7 @@ Mira API Visuals/UI + Match Info Guide Accessibility MiraAPI Show Modifiers HUD on Left Side @@ -9,8 +10,12 @@ Button Scale Factor Top-Right UI Scale Factor Match Info Guide Scale Factor + Tab Buttons Grid Placement + Left-Side + Right-Side + Below UI Max FPS - Display Wiki on Bottom Row + Display Button on Bottom Row Enable Sabotage Flashes Enable Sabotage Blare Enable Variable Color Changes diff --git a/MiraAPI/Resources/Wiki/TabButtonContainer.png b/MiraAPI/Resources/Wiki/TabButtonContainer.png new file mode 100644 index 0000000..7aa62a3 Binary files /dev/null and b/MiraAPI/Resources/Wiki/TabButtonContainer.png differ diff --git a/MiraAPI/Utilities/Assets/MiraAssets.cs b/MiraAPI/Utilities/Assets/MiraAssets.cs index 705bc32..96ac078 100644 --- a/MiraAPI/Utilities/Assets/MiraAssets.cs +++ b/MiraAPI/Utilities/Assets/MiraAssets.cs @@ -370,5 +370,10 @@ public static class MiraAssets /// public static LoadableResourceAsset PageRightIdle { get; } = new("MiraAPI.Resources.Wiki.PageRightIdle.png"); + /// + /// Gets the sprite used below the tab icons in the wiki. + /// + public static LoadableResourceAsset TabButtonContainer { get; } = new("MiraAPI.Resources.Wiki.TabButtonContainer.png"); + internal static LoadableResourceAsset BlankSprite { get; } = new("MiraAPI.Resources.BlankSprite.png"); }