diff --git a/Readme.md b/Readme.md index 6a6d44f79..903bf4d57 100644 --- a/Readme.md +++ b/Readme.md @@ -46,14 +46,9 @@ To get started, check out [our tutorial on the docs](https://owml.outerwildsmods ## Compatibility -|Version|Compatible| -|-|-| -|1.1.10|Yes| -|1.1.9|Unknown| -|1.1.8|Unknown| -|1.0.0 - 1.0.7|No| +OWML is only supported on the latest game version. It may work on older versions, but that cannot be guaranteed. -OWML is compatible with Echoes of the Eye, and works on both Epic and Steam installations. +OWML is compatible with Echoes of the Eye, and works on Epic, Steam, and Microsoft Store installations. ## Feedback and Support @@ -80,7 +75,7 @@ Contributors: * [JohnCorby](https://github.com/JohnCorby) - Helped with audio loading stuff. Special thanks to: -* [Outer Wilds](http://www.outerwilds.com) +* [Outer Wilds](https://www.mobiusdigitalgames.com/outer-wilds.html) * [Outer Wilds on Reddit](https://www.reddit.com/r/outerwilds) * The unnofficial Outer Wilds Discord * Inspired by (and some code from) [SMAPI](https://smapi.io) diff --git a/docs/content/pages/mod_helper/config.md b/docs/content/pages/mod_helper/config.md index 17281a2e5..b54f747e7 100644 --- a/docs/content/pages/mod_helper/config.md +++ b/docs/content/pages/mod_helper/config.md @@ -16,11 +16,11 @@ A `Dictionary` containing your mod's settings, it's recommended ## GetSettingValue<T> -Gets the setting's value from the mod's config with the given key. Deserialized into type `T` +Gets the setting's value from the mod's config with the given key. Deserialized into type `T`. ### Get Parameters -- `string key`: The key to get +- `string key`: The key to get. ## SetSettingsValue @@ -28,9 +28,9 @@ Sets the setting's value in the mod's config with the given key to the given val ### Set Parameters -- `string key`: The key to set -- `object value`: The value to set the key to, auto-serialized to a JSON string +- `string key`: The key to set. +- `object value`: The value to set the key to, auto-serialized to a JSON string. ## Copy -Copies the config of the mod +Copies the config of the mod. diff --git a/docs/content/pages/mod_helper/menus.md b/docs/content/pages/mod_helper/menus.md index ad983e870..e55dd32f8 100644 --- a/docs/content/pages/mod_helper/menus.md +++ b/docs/content/pages/mod_helper/menus.md @@ -4,157 +4,5 @@ Title: Menus # ModHelper.Menus -Provides utilities to extend base-game menus and UI. - -## Interfaces - -These interfaces are used throughout the module - -## IModMenu - -Represents a menu - -### OnInit - -The event that's fired when the menu has been initialized. - -## IModButton - -Represents a button - -### OnClick - -The event that fires when clicked - -### Title - -Text displayed on the button - -### Duplicate - -Duplicates the button with a new label - -### Show - -Shows the button - -### Hide - -Hides the button - -## IModMessagePopup - -### OnConfirm - -The event that's fired when the confirm button is pressed - -### OnCancel - -The event that's fired when the cancel button is pressed - -## IModInputMenu - -### OnConfirm(string) - -The event that's fired when the input is confirmed, it expects an Action<string> - -## MainMenu - -The main menu of the game. It is represented by `IModMainMenu`. - -### IModButtons - -- OptionsButton -- QuitButton -- ResumeExpeditionButton -- NewExpeditionButton -- ViewCreditsButton -- SwitchProfileButton - -## PauseMenu - -The pause menu. It is represented by `IModPauseMenu`. - -### IModButtons - -- ResumeButton -- OptionsButton -- QuitButton - -## Button Example - -```csharp -public class MyCoolMod : ModBehaviour { - public void Start() { - ModHelper.Menus.MainMenu.OnInit += () => { - var myButton = ModHelper.Menus.MainMenu.OptionsButton.Duplicate("My Cool Button"); - myButton.OnClick += MyButtonClicked; - }; - } - - public void MyButtonClicked() { - ModHelper.Console.WriteLine("My Button Was Clicked!"); - } -} -``` - -## PopupManager - -Allows you to show information and input messages - -### CreateMessagePopup - -Creates a new message popup - -#### Message Parameters - -(*italicized* = optional) - -- `string message`: The message to show -- *`bool addCancel`*: Whether to add a cancel button to the popup -- *`string okMessage`*: The message to show in the OK button -- *`string cancelMessage`*: The message to show in the cancel button - -#### Message Example - -```csharp -public class MyCoolMod : ModBehaviour { - public void Start() { - var popup = ModHelper.Menus.PopupManager.CreateMessagePopup("What do?", true, "Yes", "What"); - popup.OnConfirm += OnOk; - popup.OnCancel += OnCancel; - } - - public void OnOk() { - ModHelper.Console.WriteLine("You Clicked OK!", MessageType.Success); - } - - public void OnCancel() { - ModHelper.Console.WriteLine("You Clicked Cancel!", MessageType.Warning); - } -} -``` - -### CreateInputPopup - -Creates a new input popup for the player. - -#### Input Parameters - -- `InputType inputType`: Either `InputType.Text` or `InputType.Number` -- `string value`: The default value for the prompt - -#### Input Example - -```csharp -public class MyCoolMod : ModBehaviour { - public void Start() { - var prompt = ModHelper.Menus.PopupManager.CreateInputPrompt(InputType.Text, "Default"); - prompt.OnConfirm += OnConfirm; - } - - public void OnConfirm(string value) { - ModHelper.Console.WriteLine($"You entered: {value}!"); - } -} -``` +!!! alert-warning "Deprecated" + This module has been deprecated in favor of the new menu system, covered in [the tutorial]({{ "Creating Custom Menus"|route }}){class="link-info"}. diff --git a/src/OWML.Common/Constants.cs b/src/OWML.Common/Constants.cs index 2958e4edb..d3b27c65d 100644 --- a/src/OWML.Common/Constants.cs +++ b/src/OWML.Common/Constants.cs @@ -1,4 +1,11 @@ -namespace OWML.Common +using System.Runtime.CompilerServices; + +// make everything in OWML.Common visible to these namespaces +// this is in this file just because it's the first one that comes up in VS :P +[assembly: InternalsVisibleTo("OWML.ModHelper.Menus")] +[assembly: InternalsVisibleTo("OWML.ModLoader")] + +namespace OWML.Common { public class Constants { diff --git a/src/OWML.Common/Enums/MenuSide.cs b/src/OWML.Common/Enums/MenuSide.cs new file mode 100644 index 000000000..f18481931 --- /dev/null +++ b/src/OWML.Common/Enums/MenuSide.cs @@ -0,0 +1,9 @@ +namespace OWML.Common +{ + public enum MenuSide + { + LEFT, + CENTER, + RIGHT + } +} diff --git a/src/OWML.Common/Interfaces/IModBehaviour.cs b/src/OWML.Common/Interfaces/IModBehaviour.cs index de961fba7..3decad16b 100644 --- a/src/OWML.Common/Interfaces/IModBehaviour.cs +++ b/src/OWML.Common/Interfaces/IModBehaviour.cs @@ -16,6 +16,12 @@ public interface IModBehaviour object GetApi(); + void SetupTitleMenu(); + + void SetupPauseMenu(); + + void SetupOptionsMenu(); + void Init(IModHelper helper); } } diff --git a/src/OWML.Common/Interfaces/IModData.cs b/src/OWML.Common/Interfaces/IModData.cs index 8d13b9b9c..f3a01a13f 100644 --- a/src/OWML.Common/Interfaces/IModData.cs +++ b/src/OWML.Common/Interfaces/IModData.cs @@ -6,7 +6,7 @@ public interface IModData IModConfig Config { get; } - IModConfig DefaultConfig { get; } + IModDefaultConfig DefaultConfig { get; } IModStorage Storage { get; } diff --git a/src/OWML.Common/Interfaces/IModDefaultConfig.cs b/src/OWML.Common/Interfaces/IModDefaultConfig.cs new file mode 100644 index 000000000..538c7d528 --- /dev/null +++ b/src/OWML.Common/Interfaces/IModDefaultConfig.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace OWML.Common +{ + public interface IModDefaultConfig + { + bool Enabled { get; set; } + + Dictionary Settings { get; set; } + + T GetSettingsValue(string key); + + IModConfig Copy(); + } +} diff --git a/src/OWML.Common/Interfaces/IModHelper.cs b/src/OWML.Common/Interfaces/IModHelper.cs index c120ae2b5..ae1464047 100644 --- a/src/OWML.Common/Interfaces/IModHelper.cs +++ b/src/OWML.Common/Interfaces/IModHelper.cs @@ -1,4 +1,5 @@ using OWML.Common.Menus; +using System; namespace OWML.Common { @@ -16,14 +17,19 @@ public interface IModHelper IModStorage Storage { get; } + [Obsolete("Use the new menu system instead.", true)] IModMenus Menus { get; } IModManifest Manifest { get; } IModConfig Config { get; } + IModDefaultConfig DefaultConfig { get; } + IOwmlConfig OwmlConfig { get; } IModInteraction Interaction { get; } + + IMenuManager MenuHelper { get; } } } diff --git a/src/OWML.Common/Interfaces/Menus/IMenuManager.cs b/src/OWML.Common/Interfaces/Menus/IMenuManager.cs new file mode 100644 index 000000000..0899dfcd4 --- /dev/null +++ b/src/OWML.Common/Interfaces/Menus/IMenuManager.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace OWML.Common +{ + public interface IMenuManager + { + public ITitleMenuManager TitleMenuManager { get; } + public IPauseMenuManager PauseMenuManager { get; } + public IOptionsMenuManager OptionsMenuManager { get; } + public IPopupMenuManager PopupMenuManager { get; } + + internal IList ModList { get; set; } + } +} diff --git a/src/OWML.Common/Interfaces/Menus/IOWMLFourChoicePopupMenu.cs b/src/OWML.Common/Interfaces/Menus/IOWMLFourChoicePopupMenu.cs new file mode 100644 index 000000000..cc5c2e425 --- /dev/null +++ b/src/OWML.Common/Interfaces/Menus/IOWMLFourChoicePopupMenu.cs @@ -0,0 +1,13 @@ +namespace OWML.Common +{ + public interface IOWMLFourChoicePopupMenu + { + event PopupConfirmEvent OnPopupConfirm1; + event PopupConfirmEvent OnPopupConfirm2; + event PopupConfirmEvent OnPopupConfirm3; + event PopupValidateEvent OnPopupValidate; + event PopupCancelEvent OnPopupCancel; + + void EnableMenu(bool value); + } +} diff --git a/src/OWML.Common/Interfaces/Menus/IOWMLMenuValueOption.cs b/src/OWML.Common/Interfaces/Menus/IOWMLMenuValueOption.cs new file mode 100644 index 000000000..0855bb661 --- /dev/null +++ b/src/OWML.Common/Interfaces/Menus/IOWMLMenuValueOption.cs @@ -0,0 +1,7 @@ +namespace OWML.Common +{ + public interface IOWMLMenuValueOption + { + string ModSettingKey { get; set; } + } +} diff --git a/src/OWML.Common/Interfaces/Menus/IOWMLOptionsSelectorElement.cs b/src/OWML.Common/Interfaces/Menus/IOWMLOptionsSelectorElement.cs new file mode 100644 index 000000000..f40a3e736 --- /dev/null +++ b/src/OWML.Common/Interfaces/Menus/IOWMLOptionsSelectorElement.cs @@ -0,0 +1,9 @@ +namespace OWML.Common +{ + public delegate void OptionValueChangedEvent(int newIndex, string newSelection); + + public interface IOWMLOptionsSelectorElement : IOWMLMenuValueOption + { + public event OptionValueChangedEvent OnValueChanged; + } +} diff --git a/src/OWML.Common/Interfaces/Menus/IOWMLPopupInputMenu.cs b/src/OWML.Common/Interfaces/Menus/IOWMLPopupInputMenu.cs new file mode 100644 index 000000000..ac1da6277 --- /dev/null +++ b/src/OWML.Common/Interfaces/Menus/IOWMLPopupInputMenu.cs @@ -0,0 +1,17 @@ +using UnityEngine.UI; + +namespace OWML.Common.Interfaces.Menus +{ + public interface IOWMLPopupInputMenu + { + public event PopupInputMenu.InputPopupValidateCharEvent OnInputPopupValidateChar; + + public void EnableMenu(bool value); + + public string GetInputText(); + + public InputField GetInputField(); + + public event PopupMenu.PopupConfirmEvent OnPopupConfirm; + } +} diff --git a/src/OWML.Common/Interfaces/Menus/IOWMLSliderElement.cs b/src/OWML.Common/Interfaces/Menus/IOWMLSliderElement.cs new file mode 100644 index 000000000..26d32cd6c --- /dev/null +++ b/src/OWML.Common/Interfaces/Menus/IOWMLSliderElement.cs @@ -0,0 +1,9 @@ +namespace OWML.Common +{ + public delegate void FloatOptionValueChangedEvent(float newValue); + + public interface IOWMLSliderElement : IOWMLMenuValueOption + { + public event FloatOptionValueChangedEvent OnValueChanged; + } +} diff --git a/src/OWML.Common/Interfaces/Menus/IOWMLTextEntryElement.cs b/src/OWML.Common/Interfaces/Menus/IOWMLTextEntryElement.cs new file mode 100644 index 000000000..3764ce257 --- /dev/null +++ b/src/OWML.Common/Interfaces/Menus/IOWMLTextEntryElement.cs @@ -0,0 +1,11 @@ +namespace OWML.Common +{ + public delegate void TextEntryConfirmEvent(); + + public interface IOWMLTextEntryElement : IOWMLMenuValueOption + { + public event TextEntryConfirmEvent OnConfirmEntry; + + public string GetInputText(); + } +} diff --git a/src/OWML.Common/Interfaces/Menus/IOWMLThreeChoicePopupMenu.cs b/src/OWML.Common/Interfaces/Menus/IOWMLThreeChoicePopupMenu.cs new file mode 100644 index 000000000..6a19616a4 --- /dev/null +++ b/src/OWML.Common/Interfaces/Menus/IOWMLThreeChoicePopupMenu.cs @@ -0,0 +1,16 @@ +namespace OWML.Common +{ + public delegate void PopupConfirmEvent(); + public delegate bool PopupValidateEvent(); + public delegate void PopupCancelEvent(); + + public interface IOWMLThreeChoicePopupMenu + { + event PopupConfirmEvent OnPopupConfirm1; + event PopupConfirmEvent OnPopupConfirm2; + event PopupValidateEvent OnPopupValidate; + event PopupCancelEvent OnPopupCancel; + + void EnableMenu(bool value); + } +} diff --git a/src/OWML.Common/Interfaces/Menus/IOWMLToggleElement.cs b/src/OWML.Common/Interfaces/Menus/IOWMLToggleElement.cs new file mode 100644 index 000000000..c0d81606a --- /dev/null +++ b/src/OWML.Common/Interfaces/Menus/IOWMLToggleElement.cs @@ -0,0 +1,10 @@ +namespace OWML.Common +{ + public delegate void BoolOptionValueChangedEvent(bool newValue); + + public interface IOWMLToggleElement : IOWMLMenuValueOption + { + public event BoolOptionValueChangedEvent OnValueChanged; + + } +} diff --git a/src/OWML.Common/Interfaces/Menus/IOWMLTwoButtonToggleElement.cs b/src/OWML.Common/Interfaces/Menus/IOWMLTwoButtonToggleElement.cs new file mode 100644 index 000000000..38c3175b5 --- /dev/null +++ b/src/OWML.Common/Interfaces/Menus/IOWMLTwoButtonToggleElement.cs @@ -0,0 +1,7 @@ +namespace OWML.Common +{ + public interface IOWMLTwoButtonToggleElement : IOWMLMenuValueOption + { + public event BoolOptionValueChangedEvent OnValueChanged; + } +} diff --git a/src/OWML.Common/Interfaces/Menus/IOptionsMenuManager.cs b/src/OWML.Common/Interfaces/Menus/IOptionsMenuManager.cs new file mode 100644 index 000000000..93b89b0a3 --- /dev/null +++ b/src/OWML.Common/Interfaces/Menus/IOptionsMenuManager.cs @@ -0,0 +1,126 @@ +using UnityEngine; + +namespace OWML.Common +{ + public interface IOptionsMenuManager + { + /// + /// Creates a tab with a standard layout - for example, the Audio tab. + /// + /// The name of the tab. + public (Menu menu, TabButton button) CreateStandardTab(string name); + + /// + /// Creates a tab that has sub tabs - for example, the Input tab. + /// + /// The name of the tab. + public (TabbedSubMenu menu, TabButton button) CreateTabWithSubTabs(string name); + + /// + /// Adds a sub-tab to a given TabbedSubMenu. + /// + /// The menu to add the sub-tab to. + /// The name of the sub-tab. + /// The sub-tab. + public (Menu subTabMenu, TabButton subTabButton) AddSubTab(TabbedSubMenu menu, string name); + + /// + /// Opens the options menu to the given tab. + /// + /// Which tab to open. + public void OpenOptionsAtTab(TabButton button); + + /// + /// Adds a checkbox (boolean) input to a given menu. + /// + /// The menu to add the input to. + /// The name of the control. + /// The description of the control. + /// The value the input should be set to upon creation. + /// + public IOWMLToggleElement AddCheckboxInput(Menu menu, string label, string tooltip, bool initialValue); + + /// + /// Adds a toggle (boolean) input to a given menu. Different from a checkbox in that the user is given two named choices, instead of just an input. + /// + /// The menu to add the input to. + /// The name of the input. + /// The name of the first () choice. + /// The name of the second () choice. + /// The description of the input. + /// The value the input should be set to upon creation. + /// + public IOWMLTwoButtonToggleElement AddToggleInput(Menu menu, string label, string leftButtonString, string rightButtonString, string tooltip, bool initialValue); + + /// + /// Adds an input to select between multiple choices. + /// + /// The menu to add the input to. + /// The name of the input. + /// The list of options to choose from. + /// The description of the input. + /// Whether or not the selection will roll back to the first choice upon going past the last choice. + /// The index of that the input should be set to upon creation. + public IOWMLOptionsSelectorElement AddSelectorInput(Menu menu, string label, string[] options, string tooltip, bool loopsAround, int initialValue); + + /// + /// Adds an sliding input. + /// + /// The menu to add the input to. + /// The name of the input. + /// The lower value of the slider. + /// The upper value of the slider. + /// The description of the input. + /// The starting value of the sider. + public IOWMLSliderElement AddSliderInput(Menu menu, string label, float lower, float upper, string tooltip, float initialValue); + + /// + /// Adds a visual seperator. + /// + /// The menu to add to. + /// Whether the seperator should have a line of dots or not. + public GameObject AddSeparator(Menu menu, bool dots); + + /// + /// Adds a button input to a menu. + /// + /// The menu to add the input to. + /// The label of the button. + /// The description of the input. + /// Where to place the button in the menu. + public SubmitAction CreateButton(Menu menu, string buttonLabel, string tooltip, MenuSide side); + + /// + /// Adds a button input with a text label to a menu. + /// + /// The menu to add the input to. + /// The label next to the button. + /// The label of the button. + /// The description of the input. + public SubmitAction CreateButtonWithLabel(Menu menu, string label, string buttonLabel, string tooltip); + + /// + /// Adds a button input with a text label, that opens a popup to enter text. + /// + /// The menu to add the input to. + /// The label next to the button. + /// The label of the button. + /// The description of the input. + /// If true, only valid numbers can be typed in. Includes decimal points (both . and ,) + public IOWMLTextEntryElement AddTextEntryInput(Menu menu, string label, string initialValue, string tooltip, bool isNumeric); + + /// + /// Removes a tab from a menu. This removes both the menu, and the tab for the menu. + /// Does not work for sub-tabs. + /// + /// The tab to remove. + public void RemoveTab(Menu tab); + + /// + /// Adds a visual label in a menu. + /// + /// The menu to add the label to. + /// The text of the label. + public void CreateLabel(Menu menu, string label); + } +} diff --git a/src/OWML.Common/Interfaces/Menus/IPauseMenuManager.cs b/src/OWML.Common/Interfaces/Menus/IPauseMenuManager.cs new file mode 100644 index 000000000..5e9e88b87 --- /dev/null +++ b/src/OWML.Common/Interfaces/Menus/IPauseMenuManager.cs @@ -0,0 +1,45 @@ +namespace OWML.Common +{ + public interface IPauseMenuManager + { + /// + /// Makes another list of buttons that can be seen in the pause menu. + /// + /// The title that appears at the top. + public Menu MakePauseListMenu(string title); + + /// + /// Makes a button on a pause menu. + /// + /// The text of the button. + /// The position index of the button. + /// Whether the position index should be from the top or from the bottom. + /// If given, this is the custom menu this button will appear on. If not given, the button will appear on the default pause menu. + public SubmitAction MakeSimpleButton(string name, int index, bool fromTop, Menu customMenu = null); + + /// + /// Makes a button on a pause menu that opens a different menu. + /// + /// The text of the button. + /// The menu to be opened when this button is pressed. + /// The position index of the button. + /// Whether the position index should be from the top or from the bottom. + /// If given, this is the custom menu this button will appear on. If not given, the button will appear on the default pause menu. + public SubmitAction MakeMenuOpenButton(string name, Menu menuToOpen, int index, bool fromTop, Menu customMenu = null); + + /// + /// Set the text of a given button. + /// + /// The button to set the text of. + /// The text to set the button to. + public void SetButtonText(SubmitAction button, string text); + + /// + /// Sets the position of a given button. + /// + /// The button to set the position of. + /// The new position index of the button. + /// Whether the position index should be from the top or from the bottom. + public void SetButtonIndex(SubmitAction button, int index, bool fromTop); + } +} diff --git a/src/OWML.Common/Interfaces/Menus/IPopupMenuManager.cs b/src/OWML.Common/Interfaces/Menus/IPopupMenuManager.cs new file mode 100644 index 000000000..72dbe7656 --- /dev/null +++ b/src/OWML.Common/Interfaces/Menus/IPopupMenuManager.cs @@ -0,0 +1,66 @@ +using OWML.Common.Interfaces.Menus; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OWML.Common +{ + public interface IPopupMenuManager + { + /// + /// Creates a popup that will appear when the title screen loads. Must be called in Start(). + /// + /// The text of the popup. + public void RegisterStartupPopup(string message); + + /// + /// Creates a popup with two buttons to choose from. + /// + /// The message text of the popup. + /// The text that appears on the left/confirm button. + /// The text that appears on the right/cancel button. + /// The PopupMenu for you to enable/disable, or hook events into. + public PopupMenu CreateTwoChoicePopup(string message, string confirmText, string cancelText); + + /// + /// Creates a popup with only one button that closes it. + /// + /// The message to display. + /// The text that appears on the singular button. + /// The PopupMenu for you to enable/disable, or hook events into. + public PopupMenu CreateInfoPopup(string message, string continueButtonText); + + /// + /// Creates a popup with three buttons to choose from. + /// + /// The message text of the popup. + /// The text that appears on the left/first confirm button. + /// The text that appears on the center/second confirm button. + /// The text that appears on the right/cancel button. + /// The IOWMLThreeChoicePopupMenu for you to enable/disable, or hook events into. + public IOWMLThreeChoicePopupMenu CreateThreeChoicePopup(string message, string confirm1Text, string confirm2Text, string cancelText); + + /// + /// Creates a popup with three buttons to choose from. + /// + /// The message text of the popup. + /// The text that appears on the first-from-the-left confirm button. + /// The text that appears on the second-from-the-left confirm button. + /// The text that appears on the third-from-the-left confirm button. + /// The text that appears on the cancel button. + /// The IOWMLFourChoicePopupMenu for you to enable/disable, or hook events into. + public IOWMLFourChoicePopupMenu CreateFourChoicePopup(string message, string confirm1Text, string confirm2Text, string confirm3Text, string cancelText); + + /// + /// Creats a popup with a text entry box. + /// + /// The message text of the popup. + /// The greyed out text that appears when nothing is written in the text box. + /// The text that appears on the left/confirm button. + /// The text that appears on the right/cancel button. + /// + public IOWMLPopupInputMenu CreateInputFieldPopup(string message, string placeholderMessage, string confirmText, string cancelText); + } +} diff --git a/src/OWML.Common/Interfaces/Menus/ITitleMenuManager.cs b/src/OWML.Common/Interfaces/Menus/ITitleMenuManager.cs new file mode 100644 index 000000000..877258578 --- /dev/null +++ b/src/OWML.Common/Interfaces/Menus/ITitleMenuManager.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OWML.Common +{ + public interface ITitleMenuManager + { + /// + /// Creates a button on the main title screen. + /// + /// The string to display on the button. + /// The index of the button. 0 means it should be the first button, 1 means the second etc. + /// Whethether the index should be top-down or bottom-up. + /// The SubmitAction of the button. + SubmitAction CreateTitleButton(string name, int index = 0, bool fromTop = false); + + /// + /// Set the string to display on the button. + /// + /// The button to change the text of. + /// The text to change it to. + void SetButtonText(SubmitAction button, string text); + + /// + /// Change the position of a button in the main menu list. + /// + /// The button to move. + /// The index to change it to. + /// Whether to base the index from the bottom or top. If true, then an index of 0 will mean the top. + void SetButtonIndex(SubmitAction buttonAction, int index, bool fromTop); + } +} diff --git a/src/OWML.Launcher/OWML.Manifest.json b/src/OWML.Launcher/OWML.Manifest.json index 0cc6962e6..06d55c717 100644 --- a/src/OWML.Launcher/OWML.Manifest.json +++ b/src/OWML.Launcher/OWML.Manifest.json @@ -3,7 +3,7 @@ "author": "Alek", "name": "OWML", "uniqueName": "Alek.OWML", - "version": "2.9.8", + "version": "2.10.0", "minGameVersion": "1.1.14.768", "maxGameVersion": "1.1.14.768" } diff --git a/src/OWML.ModHelper.Assets/ModAssets.cs b/src/OWML.ModHelper.Assets/ModAssets.cs index f7a7af096..8d340d3fe 100644 --- a/src/OWML.ModHelper.Assets/ModAssets.cs +++ b/src/OWML.ModHelper.Assets/ModAssets.cs @@ -21,7 +21,7 @@ public ModAssets(IModConsole console, IModManifest manifest, IObjImporter objImp public AssetBundle LoadBundle(string filename) { var path = _manifest.ModFolderPath + filename; - _console.WriteLine("Loading asset bundle from " + path); + _console.WriteLine("Loading asset bundle from " + path, MessageType.Debug); var bundle = AssetBundle.LoadFromFile(path); if (bundle == null) { @@ -74,14 +74,14 @@ public IModAsset LoadAudio(string audioFilename) public Mesh GetMesh(string filename) { var path = _manifest.ModFolderPath + filename; - _console.WriteLine($"Loading mesh from {path}"); + _console.WriteLine($"Loading mesh from {path}", MessageType.Debug); return _objImporter.ImportFile(path); } public Texture2D GetTexture(string filename) { var path = _manifest.ModFolderPath + filename; - _console.WriteLine($"Loading texture from {path}"); + _console.WriteLine($"Loading texture from {path}", MessageType.Debug); var data = File.ReadAllBytes(path); var texture = new Texture2D(2, 2); texture.LoadImage(data); @@ -99,7 +99,7 @@ public GameObject Get3DObject(string objectFilename, string imageFilename) public AudioClip GetAudio(string filename) { var path = _manifest.ModFolderPath + filename; - _console.WriteLine($"Loading audio from {path}"); + _console.WriteLine($"Loading audio from {path}", MessageType.Debug); using var reader = new AudioFileReader(path); var sampleCount = (int)(reader.Length * 8 / reader.WaveFormat.BitsPerSample); var outputSamples = new float[sampleCount]; diff --git a/src/OWML.ModHelper.Input/OWML.ModHelper.Input.csproj b/src/OWML.ModHelper.Input/OWML.ModHelper.Input.csproj index 8a7df8688..f3c04a9f5 100644 --- a/src/OWML.ModHelper.Input/OWML.ModHelper.Input.csproj +++ b/src/OWML.ModHelper.Input/OWML.ModHelper.Input.csproj @@ -3,6 +3,7 @@ net48 9.0 + True diff --git a/src/OWML.ModHelper.Menus/CustomInputs/OWMLFourChoicePopupMenu.cs b/src/OWML.ModHelper.Menus/CustomInputs/OWMLFourChoicePopupMenu.cs new file mode 100644 index 000000000..9cdfbb91d --- /dev/null +++ b/src/OWML.ModHelper.Menus/CustomInputs/OWMLFourChoicePopupMenu.cs @@ -0,0 +1,379 @@ +using System; +using OWML.Common; +using UnityEngine.EventSystems; +using UnityEngine.UI; +using UnityEngine; + +namespace OWML.ModHelper.Menus.CustomInputs +{ + public class OWMLFourChoicePopupMenu : Menu, IOWMLFourChoicePopupMenu + { + public Text _labelText; + public SubmitAction _cancelAction; + public SubmitAction _ok1Action; + public SubmitAction _ok2Action; + public SubmitAction _ok3Action; + public ButtonWithHotkeyImageElement _cancelButton; + public ButtonWithHotkeyImageElement _confirmButton1; + public ButtonWithHotkeyImageElement _confirmButton2; + public ButtonWithHotkeyImageElement _confirmButton3; + public Canvas _rootCanvas; + + protected Canvas _popupCanvas; + protected GameObject _blocker; + protected bool _closeMenuOnOk = true; + protected IInputCommands _ok1Command; + protected IInputCommands _ok2Command; + protected IInputCommands _ok3Command; + protected IInputCommands _cancelCommand; + protected bool _usingGamepad; + + public event PopupConfirmEvent OnPopupConfirm1; + public event PopupConfirmEvent OnPopupConfirm2; + public event PopupConfirmEvent OnPopupConfirm3; + public event PopupValidateEvent OnPopupValidate; + public event PopupCancelEvent OnPopupCancel; + + public override Selectable GetSelectOnActivate() + { + _usingGamepad = OWInput.UsingGamepad(); + return _usingGamepad ? null : _selectOnActivate; + } + + public virtual void SetUpPopup( + string message, + IInputCommands ok1Command, + IInputCommands ok2Command, + IInputCommands ok3Command, + IInputCommands cancelCommand, + ScreenPrompt ok1Prompt, + ScreenPrompt ok2Prompt, + ScreenPrompt ok3Prompt, + ScreenPrompt cancelPrompt, + bool closeMenuOnOk = true, + bool setCancelButtonActive = true) + { + _labelText.text = message; + SetUpPopupCommands(ok1Command, ok2Command, ok3Command, cancelCommand, ok1Prompt, ok2Prompt, ok3Prompt, cancelPrompt); + if (!(_cancelAction != null)) + { + Debug.LogWarning("PopupMenu.SetUpPopup Cancel button not set!"); + return; + } + + _cancelAction.gameObject.SetActive(setCancelButtonActive); + if (setCancelButtonActive) + { + _selectOnActivate = _cancelAction.GetRequiredComponent(); + return; + } + + _selectOnActivate = _ok1Action.GetRequiredComponent(); + } + + public virtual void SetUpPopupCommands( + IInputCommands ok1Command, + IInputCommands ok2Command, + IInputCommands ok3Command, + IInputCommands cancelCommand, + ScreenPrompt ok1Prompt, + ScreenPrompt ok2Prompt, + ScreenPrompt ok3Prompt, + ScreenPrompt cancelPrompt) + { + _ok1Command = ok1Command; + _ok2Command = ok2Command; + _ok3Command = ok3Command; + _cancelCommand = cancelCommand; + _confirmButton1.SetPrompt(ok1Prompt, InputMode.Menu); + _confirmButton2.SetPrompt(ok2Prompt, InputMode.Menu); + _confirmButton3.SetPrompt(ok3Prompt, InputMode.Menu); + _cancelButton.SetPrompt(cancelPrompt, InputMode.Menu); + } + + public virtual void ResetPopup() + { + _labelText.text = ""; + _ok1Command = null; + _ok2Command = null; + _ok3Command = null; + _cancelCommand = null; + _cancelButton.SetPrompt(null, InputMode.Menu); + _confirmButton1.SetPrompt(null, InputMode.Menu); + _confirmButton2.SetPrompt(null, InputMode.Menu); + _confirmButton3.SetPrompt(null, InputMode.Menu); + _selectOnActivate = null; + } + + public virtual void CloseMenuOnOk(bool value) + { + _closeMenuOnOk = value; + } + + public virtual bool EventsHaveListeners() + { + return OnPopupCancel != null + || OnPopupConfirm1 != null + || OnPopupConfirm2 != null + || OnPopupConfirm3 != null; + } + + public override void InitializeMenu() + { + base.InitializeMenu(); + if (_cancelAction != null) + { + _cancelAction.OnSubmitAction += InvokeCancel; + } + + _ok1Action.OnSubmitAction += InvokeOk1; + _ok2Action.OnSubmitAction += InvokeOk2; + _ok3Action.OnSubmitAction += InvokeOk3; + // PopupCanvas is disabled after the menus are initialized, and overrideSorting can only be set when it's enabled + _menuActivationRoot.gameObject.SetActive(true); + _popupCanvas = gameObject.GetAddComponent(); + _popupCanvas.overrideSorting = true; + _popupCanvas.sortingOrder = 30000; + _menuActivationRoot.gameObject.SetActive(false); + gameObject.GetAddComponent(); + gameObject.GetAddComponent(); + } + + protected virtual void Update() + { + if (_cancelCommand != null && OWInput.IsNewlyPressed(_cancelCommand, InputMode.All)) + { + InvokeCancel(); + return; + } + + if (_ok1Command != null && OWInput.IsNewlyPressed(_ok1Command, InputMode.All)) + { + InvokeOk1(); + return; + } + + if (_ok2Command != null && OWInput.IsNewlyPressed(_ok2Command, InputMode.All)) + { + InvokeOk2(); + return; + } + + if (_ok3Command != null && OWInput.IsNewlyPressed(_ok3Command, InputMode.All)) + { + InvokeOk3(); + return; + } + + if (_usingGamepad != OWInput.UsingGamepad()) + { + _usingGamepad = OWInput.UsingGamepad(); + if (_usingGamepad) + { + Locator.GetMenuInputModule().SelectOnNextUpdate(null); + return; + } + + Locator.GetMenuInputModule().SelectOnNextUpdate(_selectOnActivate); + } + } + + public override void EnableMenu(bool value) + { + if (value == _enabledMenu) + { + return; + } + + _enabledMenu = value; + if (_enabledMenu && !_initialized) + { + InitializeMenu(); + } + + if (!_addToMenuStackManager) + { + if (_enabledMenu) + { + Activate(); + if (_selectOnActivate != null) + { + var component = _selectOnActivate.GetComponent(); + if (component != null) + { + component.SilenceNextSelectEvent(); + } + + Locator.GetMenuInputModule().SelectOnNextUpdate(_selectOnActivate); + return; + } + } + else + { + Deactivate(false); + } + + return; + } + + if (_enabledMenu) + { + MenuStackManager.SharedInstance.Push(this, true); + return; + } + + if (MenuStackManager.SharedInstance.Peek() == this) + { + MenuStackManager.SharedInstance.Pop(false); + return; + } + + Debug.LogError("Cannot disable Menu unless it is on the top the MenuLayerManager stack. Current menu on top: " + MenuStackManager.SharedInstance.Peek().name); + } + + public override void Activate() + { + base.Activate(); + if (_rootCanvas != null) + { + _blocker = CreateBlocker(_rootCanvas); + } + } + + public override void Deactivate(bool keepPreviousMenuVisible = false) + { + if (_rootCanvas != null) + { + DestroyBlocker(_blocker); + } + + var component = _cancelAction.GetComponent(); + if (component != null) + { + component.ChangeState(UIElementState.NORMAL, true); + } + + component = _ok1Action.GetComponent(); + if (component != null) + { + component.ChangeState(UIElementState.NORMAL, true); + } + + component = _ok2Action.GetComponent(); + if (component != null) + { + component.ChangeState(UIElementState.NORMAL, true); + } + + component = _ok3Action.GetComponent(); + if (component != null) + { + component.ChangeState(UIElementState.NORMAL, true); + } + + base.Deactivate(keepPreviousMenuVisible); + } + + public override void OnCancelEvent(GameObject selectedObj, BaseEventData eventData) + { + base.OnCancelEvent(selectedObj, eventData); + OnPopupCancel?.Invoke(); + } + + protected virtual void InvokeCancel() + { + EnableMenu(false); + OnPopupCancel?.Invoke(); + } + + protected virtual bool Validate() + { + var flag = true; + if (OnPopupValidate != null) + { + var invocationList = OnPopupValidate.GetInvocationList(); + for (var i = 0; i < invocationList.Length; i++) + { + var flag2 = (bool)invocationList[i].DynamicInvoke(Array.Empty()); + flag = flag && flag2; + } + } + + return flag; + } + + protected virtual void InvokeOk1() + { + if (!Validate()) + { + return; + } + + if (_closeMenuOnOk) + { + EnableMenu(false); + } + + OnPopupConfirm1?.Invoke(); + } + + protected virtual void InvokeOk2() + { + if (!Validate()) + { + return; + } + + if (_closeMenuOnOk) + { + EnableMenu(false); + } + + OnPopupConfirm2?.Invoke(); + } + + protected virtual void InvokeOk3() + { + if (!Validate()) + { + return; + } + + if (_closeMenuOnOk) + { + EnableMenu(false); + } + + OnPopupConfirm3?.Invoke(); + } + + protected virtual GameObject CreateBlocker(Canvas rootCanvas) + { + var gameObject = new GameObject("Blocker"); + var rectTransform = gameObject.AddComponent(); + rectTransform.SetParent(rootCanvas.transform, false); + rectTransform.anchorMin = Vector3.zero; + rectTransform.anchorMax = Vector3.one; + rectTransform.sizeDelta = Vector2.zero; + var canvas = gameObject.AddComponent(); + canvas.overrideSorting = true; + canvas.sortingLayerID = _popupCanvas.sortingLayerID; + canvas.sortingOrder = _popupCanvas.sortingOrder - 1; + gameObject.AddComponent(); + var image = gameObject.AddComponent(); + if (Locator.GetUIStyleManager() != null) + { + image.color = Locator.GetUIStyleManager().GetPopupBlockerColor(); + return gameObject; + } + + image.color = Color.clear; + return gameObject; + } + + protected virtual void DestroyBlocker(GameObject blocker) + { + Destroy(blocker); + } + } +} diff --git a/src/OWML.ModHelper.Menus/CustomInputs/OWMLMenuValueOption.cs b/src/OWML.ModHelper.Menus/CustomInputs/OWMLMenuValueOption.cs new file mode 100644 index 000000000..aff1b4c62 --- /dev/null +++ b/src/OWML.ModHelper.Menus/CustomInputs/OWMLMenuValueOption.cs @@ -0,0 +1,26 @@ +namespace OWML.ModHelper.Menus.CustomInputs +{ + public class OWMLMenuValueOption : MenuOption + { + public string ModSettingKey { get; set; } + + protected int _value; + + public virtual void Initialize(bool inputBool) + { + if (inputBool) + { + Initialize(1); + return; + } + + Initialize(0); + } + + public virtual void Initialize(int startingValue) + { + base.Initialize(); + _value = startingValue; + } + } +} diff --git a/src/OWML.ModHelper.Menus/CustomInputs/OWMLOptionsSelectorElement.cs b/src/OWML.ModHelper.Menus/CustomInputs/OWMLOptionsSelectorElement.cs new file mode 100644 index 000000000..9374449a4 --- /dev/null +++ b/src/OWML.ModHelper.Menus/CustomInputs/OWMLOptionsSelectorElement.cs @@ -0,0 +1,247 @@ +using OWML.Common; +using UnityEngine; +using UnityEngine.Events; +using UnityEngine.EventSystems; +using UnityEngine.UI; + +namespace OWML.ModHelper.Menus.CustomInputs +{ + public class OWMLOptionsSelectorElement : OWMLMenuValueOption, IMoveHandler, IEventSystemHandler, ISelectHandler, IDeselectHandler, IPointerExitHandler, IOWMLOptionsSelectorElement + { + internal bool _loopAround; + internal string[] _optionsList; + internal Text _displayText; + internal UIStyleApplier _optionsBoxStyleApplier; + internal Selectable _selectOnLeft; + internal Selectable _selectOnRight; + + public event OptionValueChangedEvent OnValueChanged; + + protected bool _initOnNextFrame; + protected bool _amISelected; + protected bool _callbacksInitialized; + + protected virtual void Update() + { + if (_initOnNextFrame) + { + Initialize((int)_value, _optionsList); + } + } + + protected virtual void OnEnable() + { + if (!_callbacksInitialized && _optionsList.Length != 0) + { + if (_selectOnLeft != null) + { + var button = _selectOnLeft as Button; + button?.onClick.AddListener(new UnityAction(OnArrowSelectableOnLeftClick)); + } + + if (_selectOnRight != null) + { + var button = _selectOnRight as Button; + button?.onClick.AddListener(new UnityAction(OnArrowSelectableOnRightClick)); + } + + _callbacksInitialized = true; + } + + UpdateOptionsBoxColors(); + } + + protected virtual void OnDisable() + { + _amISelected = false; + UpdateOptionsBoxColors(); + if (_selectOnLeft != null) + { + var button = _selectOnLeft as Button; + button?.onClick.RemoveListener(new UnityAction(OnArrowSelectableOnLeftClick)); + } + + if (_selectOnRight != null) + { + var button = _selectOnRight as Button; + button?.onClick.RemoveListener(new UnityAction(OnArrowSelectableOnRightClick)); + } + + _callbacksInitialized = false; + } + + public override void Initialize(int index) => Initialize(index, _optionsList); + + public virtual void Initialize(int index, string[] displayedOptions) + { + base.Initialize(index); + _value = index; + _optionsList = displayedOptions; + if (!_initOnNextFrame) + { + _initOnNextFrame = true; + return; + } + + if (_selectable == null) + { + _selectable = this.GetRequiredComponent(); + } + + var component = base.GetComponent(); + if (component != null && EventSystem.current.currentSelectedGameObject != base.gameObject) + { + component.ChangeState(UIElementState.NORMAL, true); + } + + if (!_callbacksInitialized) + { + if (_selectOnLeft != null) + { + var button = _selectOnLeft as Button; + button?.onClick.AddListener(new UnityAction(OnArrowSelectableOnLeftClick)); + } + + if (_selectOnRight != null) + { + var button = _selectOnRight as Button; + button?.onClick.AddListener(new UnityAction(OnArrowSelectableOnRightClick)); + } + + _callbacksInitialized = true; + } + + SetSelectedOption(); + UpdateOptionsBoxColors(); + UpdateArrowSelectables(); + _initOnNextFrame = false; + OnValueChanged?.Invoke(_value, _optionsList[_value]); + } + + public virtual string GetSelectedOption() => _optionsList[_value]; + + protected virtual void SetSelectedOption() => _displayText.text = _optionsList[_value]; + + protected virtual void UpdateArrowSelectables() + { + var flag3 = true; + var flag4 = true; + if (!_loopAround) + { + if (_value == 0) + { + flag3 = false; + } + else if (_value == _optionsList.Length - 1) + { + flag4 = false; + } + } + + if (_selectOnLeft != null) + { + _selectOnLeft.interactable = flag3; + } + + if (_selectOnRight != null) + { + _selectOnRight.interactable = flag4; + } + } + + public override void OnSelect(BaseEventData eventData) + { + base.OnSelect(eventData); + _amISelected = true; + UpdateOptionsBoxColors(); + } + + public override void OnDeselect(BaseEventData eventData) + { + base.OnDeselect(eventData); + _amISelected = false; + UpdateOptionsBoxColors(); + } + + public virtual void OnPointerExit(PointerEventData pointerEventData) => UpdateOptionsBoxColors(); + + protected virtual void UpdateOptionsBoxColors() + { + if (_optionsBoxStyleApplier != null) + { + if (_amISelected) + { + _optionsBoxStyleApplier.ChangeState(UIElementState.HIGHLIGHTED, false); + return; + } + + _optionsBoxStyleApplier.ChangeState(UIElementState.INTERMEDIATELY_HIGHLIGHTED, false); + } + } + + public virtual void OnMove(AxisEventData eventData) + { + if (_optionsList.Length == 0) + { + return; + } + + OptionsMove(eventData.moveVector); + } + + protected virtual void OptionsMove(Vector2 moveVector) + { + var num = _value; + if (moveVector.x > 0f) + { + num++; + } + else if (moveVector.x < 0f) + { + num--; + } + + if (num >= _optionsList.Length) + { + num = _loopAround ? 0 : _optionsList.Length - 1; + } + else if (num < 0) + { + num = _loopAround ? _optionsList.Length - 1 : 0; + } + + if (num != _value) + { + _value = num; + OnValueChanged?.Invoke(_value, _optionsList[_value]); + SetSelectedOption(); + UpdateArrowSelectables(); + Locator.GetMenuAudioController().PlayOptionToggle(); + } + } + + public virtual void OnArrowSelectableOnUpClick() + { + OptionsMove(new Vector2(0f, 1f)); + Locator.GetMenuInputModule().SelectOnNextUpdate(_selectable); + } + + public virtual void OnArrowSelectableOnDownClick() + { + OptionsMove(new Vector2(0f, -1f)); + Locator.GetMenuInputModule().SelectOnNextUpdate(_selectable); + } + + public virtual void OnArrowSelectableOnLeftClick() + { + OptionsMove(new Vector2(-1f, 0f)); + Locator.GetMenuInputModule().SelectOnNextUpdate(_selectable); + } + + public virtual void OnArrowSelectableOnRightClick() + { + OptionsMove(new Vector2(1f, 0f)); + Locator.GetMenuInputModule().SelectOnNextUpdate(_selectable); + } + } +} diff --git a/src/OWML.ModHelper.Menus/CustomInputs/OWMLPopupInputMenu.cs b/src/OWML.ModHelper.Menus/CustomInputs/OWMLPopupInputMenu.cs new file mode 100644 index 000000000..d3709cf42 --- /dev/null +++ b/src/OWML.ModHelper.Menus/CustomInputs/OWMLPopupInputMenu.cs @@ -0,0 +1,219 @@ +using System; +using UnityEngine.UI; +using UnityEngine; +using Steamworks; +using UnityEngine.EventSystems; +using OWML.Common.Interfaces.Menus; +using OWML.Logging; + +namespace OWML.ModHelper.Menus.CustomInputs +{ + public class OWMLPopupInputMenu : PopupMenu, IOWMLPopupInputMenu + { + public InputField _inputField; + public InputEventListener _inputFieldEventListener; + + protected Transform _caretTransform; + protected bool _virtualKeyboardOpen; + + public event PopupInputMenu.InputPopupTextChangedEvent OnInputPopupTextChanged; + public event PopupInputMenu.InputPopupValidateCharEvent OnInputPopupValidateChar; + + public override void Awake() + { + base.Awake(); + this._inputField.DeactivateInputField(); + } + + public override void InitializeMenu() + { + base.InitializeMenu(); + + // PopupCanvas is disabled after the menus are initialized, and overrideSorting can only be set when it's enabled + _menuActivationRoot.gameObject.SetActive(true); + _popupCanvas = gameObject.GetAddComponent(); + _popupCanvas.overrideSorting = true; + _popupCanvas.sortingOrder = 30000; + _menuActivationRoot.gameObject.SetActive(false); + } + + public override Selectable GetSelectOnActivate() + { + return this._selectOnActivate; + } + + public override void SetUpPopup(string message, IInputCommands okCommand, IInputCommands cancelCommand, ScreenPrompt okPrompt, ScreenPrompt cancelPrompt, bool closeMenuOnOk = true, bool setCancelButtonActive = true) + { + base.SetUpPopup(message, okCommand, cancelCommand, okPrompt, cancelPrompt, closeMenuOnOk, setCancelButtonActive); + this._selectOnActivate = this._inputField; + } + + public override void Activate() + { + base.Activate(); + this.ClearInputFieldText(); + this._inputField.ActivateInputField(); + this._inputFieldEventListener.OnSelectEvent += this.OnInputFieldSelect; + this._inputFieldEventListener.OnPointerUpEvent += this.OnPointerUpInInputField; + SteamManager.Instance.OnGamepadTextInputDismissed += this.OnSteamVirtualKeyboardDismissed; + if (SteamManager.Initialized) + { + SteamUserStats.RequestCurrentStats(); + } + this._inputField.onValueChanged.AddListener(delegate + { + this.OnTextFieldChanged(); + }); + InputField inputField = this._inputField; + inputField.onValidateInput += this.OnValidateInput; + Transform transform = this._inputField.transform.Find(this._inputField.transform.name + " Input Caret"); + if (transform != null) + { + this._caretTransform = transform; + transform.SetAsLastSibling(); + } + } + + protected void OnInputFieldSelect(BaseEventData eventData, Selectable selectable) + { + if (selectable != this._inputField) + { + return; + } + this._virtualKeyboardOpen = this.TryOpenVirtualKeyboard(); + } + + protected void OnPointerUpInInputField(PointerEventData eventData, Selectable selectable) + { + this._virtualKeyboardOpen = this.TryOpenVirtualKeyboard(); + } + + protected bool TryOpenVirtualKeyboard() + { + this._inputField.ActivateInputField(); + return SteamUtils.IsSteamRunningOnSteamDeck() && SteamUtils.ShowFloatingGamepadTextInput(EFloatingGamepadTextInputMode.k_EFloatingGamepadTextInputModeModeSingleLine, 0, 0, 1280, 370); + } + + protected void OnSteamVirtualKeyboardDismissed(bool bSubmitted, uint unSubmittedText) + { + this._virtualKeyboardOpen = false; + } + + public override void InvokeOk() + { + base.InvokeOk(); + if (this._enabledMenu && this._inputField.text == "") + { + if (Locator.GetEventSystem().currentSelectedGameObject != this._inputField.gameObject) + { + this._inputField.Select(); + return; + } + this._virtualKeyboardOpen = this.TryOpenVirtualKeyboard(); + } + } + + public override void Update() + { + if (this._caretTransform == null) + { + Transform transform = this._inputField.transform.Find(this._inputField.transform.name + " Input Caret"); + if (transform != null) + { + this._caretTransform = transform; + transform.SetAsLastSibling(); + } + } + if (OWInput.IsNewlyPressed(InputLibrary.menuConfirm, InputMode.All)) + { + if (Locator.GetEventSystem().currentSelectedGameObject == this._inputField.gameObject) + { + this._virtualKeyboardOpen = this.TryOpenVirtualKeyboard(); + return; + } + EventSystem eventSystem = Locator.GetEventSystem(); + if (eventSystem.currentSelectedGameObject != this._confirmButton.gameObject && eventSystem.currentSelectedGameObject != this._cancelButton.gameObject) + { + this._inputField.Select(); + } + } + } + + public override void Deactivate(bool keepPreviousMenuVisible = false) + { + base.Deactivate(keepPreviousMenuVisible); + this._inputField.DeactivateInputField(); + this._inputField.onValueChanged.RemoveListener(delegate + { + this.OnTextFieldChanged(); + }); + InputField inputField = this._inputField; + inputField.onValidateInput = (InputField.OnValidateInput)Delegate.Remove(inputField.onValidateInput, new InputField.OnValidateInput(this.OnValidateInput)); + this._inputFieldEventListener.OnSelectEvent -= this.OnInputFieldSelect; + this._inputFieldEventListener.OnPointerUpEvent -= this.OnPointerUpInInputField; + SteamManager.Instance.OnGamepadTextInputDismissed -= this.OnSteamVirtualKeyboardDismissed; + this._virtualKeyboardOpen = false; + } + + private void OnTextFieldChanged() + { + if (this.OnInputPopupTextChanged != null) + { + this.OnInputPopupTextChanged(); + } + } + + private char OnValidateInput(string input, int charIndex, char addedChar) + { + bool flag = true; + if (this.OnInputPopupValidateChar != null) + { + Delegate[] invocationList = this.OnInputPopupValidateChar.GetInvocationList(); + for (int i = 0; i < invocationList.Length; i++) + { + bool flag2 = (bool)invocationList[i].DynamicInvoke(new object[] { addedChar }); + flag = flag && flag2; + } + } + if (flag) + { + return addedChar; + } + return '\0'; + } + + public void ClearInputFieldText() + { + this._inputField.text = ""; + } + + public virtual string GetInputText() + { + return this._inputField.text; + } + + public virtual InputField GetInputField() + { + return this._inputField; + } + + public virtual void SetInputFieldPlaceholderText(string text) + { + Text component = this._inputField.placeholder.GetComponent(); + if (component != null) + { + component.text = text; + return; + } + Debug.LogWarning("Could not find InputField Placeholder Text Element"); + } + + public override void SetUpPopupCommands(IInputCommands okCommand, IInputCommands cancelCommand, ScreenPrompt okPrompt, ScreenPrompt cancelPrompt) + { + this._okCommand = okCommand; + this._cancelCommand = cancelCommand; + this._confirmButton.SetPrompt(okPrompt, InputMode.Menu); + this._cancelButton.SetPrompt(cancelPrompt, InputMode.Menu); + } + } +} diff --git a/src/OWML.ModHelper.Menus/CustomInputs/OWMLSliderElement.cs b/src/OWML.ModHelper.Menus/CustomInputs/OWMLSliderElement.cs new file mode 100644 index 000000000..425473d94 --- /dev/null +++ b/src/OWML.ModHelper.Menus/CustomInputs/OWMLSliderElement.cs @@ -0,0 +1,136 @@ +using OWML.Common; +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.UI; + +namespace OWML.ModHelper.Menus.CustomInputs +{ + internal class OWMLSliderElement : OWMLMenuValueOption, IMoveHandler, IEventSystemHandler, IOWMLSliderElement + { + private Slider _slider; + private Selectable _rootSelectable; + private int _cachedValue; + private bool _initOnNextFrame; + private bool _listenerAttached; + + private float _lower = 0; + private float _upper = 10; + + public event FloatOptionValueChangedEvent OnValueChanged; + + public static float Map(float value, float fromSource, float toSource, float fromTarget, float toTarget) + => ((value - fromSource) / (toSource - fromSource) * (toTarget - fromTarget)) + fromTarget; + + public virtual void Initialize(float startingValue, float lower, float upper) + { + Initialize(Mathf.RoundToInt(Map(startingValue, lower, upper, 0f, 10f))); + _lower = lower; + _upper = upper; + } + + public override void Initialize(int startingValue) + { + base.Initialize(startingValue); + _cachedValue = startingValue; + if (!_initOnNextFrame) + { + _initOnNextFrame = true; + return; + } + _slider = this.GetRequiredComponentInChildren(); + _slider.minValue = 0f; + _slider.maxValue = 10f; + _slider.wholeNumbers = true; + _slider.value = _cachedValue; + if (!_listenerAttached) + { + _slider.onValueChanged.AddListener(delegate + { + OnSliderValueChanged(); + }); + _listenerAttached = true; + } + + _rootSelectable = this.GetRequiredComponent(); + var navigation = _rootSelectable.navigation; + _slider.navigation = navigation; + _initOnNextFrame = false; + OnValueChanged?.Invoke(GetFloatValue()); + } + + private void Update() + { + if (_initOnNextFrame) + { + Initialize(_cachedValue); + } + } + + public float GetFloatValue() + { + if (_initOnNextFrame) + { + Initialize(_cachedValue); + } + + return Map(_slider.value, 0, 10, _lower, _upper); + } + + public void OnMove(AxisEventData eventData) + { + if (_slider == null) + { + return; + } + + var navigation = _rootSelectable.navigation; + var flag = false; + switch (eventData.moveDir) + { + case MoveDirection.Left: + if (navigation.selectOnLeft != null) + { + flag = true; + } + + break; + case MoveDirection.Up: + if (navigation.selectOnUp != null) + { + flag = true; + } + + break; + case MoveDirection.Right: + if (navigation.selectOnRight != null) + { + flag = true; + } + + break; + case MoveDirection.Down: + if (navigation.selectOnDown != null) + { + flag = true; + } + + break; + } + + if (flag) + { + return; + } + + _slider.OnMove(eventData); + } + + private void OnSliderValueChanged() + { + Locator.GetMenuAudioController().PlaySliderIncrement(); + _value = (int)_slider.value; + OnValueChanged?.Invoke(GetFloatValue()); + Locator.GetMenuInputModule().SelectOnNextUpdate(_rootSelectable); + } + } +} diff --git a/src/OWML.ModHelper.Menus/CustomInputs/OWMLTextEntryElement.cs b/src/OWML.ModHelper.Menus/CustomInputs/OWMLTextEntryElement.cs new file mode 100644 index 000000000..ecc06789d --- /dev/null +++ b/src/OWML.ModHelper.Menus/CustomInputs/OWMLTextEntryElement.cs @@ -0,0 +1,33 @@ +using System.Linq; +using OWML.Common; +using OWML.Common.Interfaces.Menus; + +namespace OWML.ModHelper.Menus.CustomInputs +{ + internal class OWMLTextEntryElement : OWMLMenuValueOption, IOWMLTextEntryElement + { + public event TextEntryConfirmEvent OnConfirmEntry; + + public bool IsNumeric; + + private IOWMLPopupInputMenu _popup; + + internal void RegisterPopup(IOWMLPopupInputMenu popup) + { + _popup = popup; + _popup.OnPopupConfirm += () => OnConfirmEntry(); + } + + public string GetInputText() + { + return _popup.GetInputText(); + } + + public void SetCurrentValue(string text) + { + gameObject.GetComponentsInChildren().First(x => x != this)._label.text = text; + _popup.GetInputField().text = text; + OnConfirmEntry(); + } + } +} diff --git a/src/OWML.ModHelper.Menus/CustomInputs/OWMLThreeChoicePopupMenu.cs b/src/OWML.ModHelper.Menus/CustomInputs/OWMLThreeChoicePopupMenu.cs new file mode 100644 index 000000000..c600365ca --- /dev/null +++ b/src/OWML.ModHelper.Menus/CustomInputs/OWMLThreeChoicePopupMenu.cs @@ -0,0 +1,340 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OWML.Common; +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.UI; + +namespace OWML.ModHelper.Menus.CustomInputs +{ + public class OWMLThreeChoicePopupMenu : Menu, IOWMLThreeChoicePopupMenu + { + public Text _labelText; + public SubmitAction _cancelAction; + public SubmitAction _ok1Action; + public SubmitAction _ok2Action; + public ButtonWithHotkeyImageElement _cancelButton; + public ButtonWithHotkeyImageElement _confirmButton1; + public ButtonWithHotkeyImageElement _confirmButton2; + public Canvas _rootCanvas; + + protected Canvas _popupCanvas; + protected GameObject _blocker; + protected bool _closeMenuOnOk = true; + protected IInputCommands _ok1Command; + protected IInputCommands _ok2Command; + protected IInputCommands _cancelCommand; + protected bool _usingGamepad; + + public event PopupConfirmEvent OnPopupConfirm1; + public event PopupConfirmEvent OnPopupConfirm2; + public event PopupValidateEvent OnPopupValidate; + public event PopupCancelEvent OnPopupCancel; + + public override Selectable GetSelectOnActivate() + { + _usingGamepad = OWInput.UsingGamepad(); + return _usingGamepad ? null : _selectOnActivate; + } + + public virtual void SetUpPopup( + string message, + IInputCommands ok1Command, + IInputCommands ok2Command, + IInputCommands cancelCommand, + ScreenPrompt ok1Prompt, + ScreenPrompt ok2Prompt, + ScreenPrompt cancelPrompt, + bool closeMenuOnOk = true, + bool setCancelButtonActive = true) + { + _labelText.text = message; + SetUpPopupCommands(ok1Command, ok2Command, cancelCommand, ok1Prompt, ok2Prompt, cancelPrompt); + if (!(_cancelAction != null)) + { + Debug.LogWarning("PopupMenu.SetUpPopup Cancel button not set!"); + return; + } + + _cancelAction.gameObject.SetActive(setCancelButtonActive); + if (setCancelButtonActive) + { + _selectOnActivate = _cancelAction.GetRequiredComponent(); + return; + } + + _selectOnActivate = _ok1Action.GetRequiredComponent(); + } + + public virtual void SetUpPopupCommands( + IInputCommands ok1Command, + IInputCommands ok2Command, + IInputCommands cancelCommand, + ScreenPrompt ok1Prompt, + ScreenPrompt ok2Prompt, + ScreenPrompt cancelPrompt) + { + _ok1Command = ok1Command; + _ok2Command = ok2Command; + _cancelCommand = cancelCommand; + _confirmButton1.SetPrompt(ok1Prompt, InputMode.Menu); + _confirmButton2.SetPrompt(ok2Prompt, InputMode.Menu); + _cancelButton.SetPrompt(cancelPrompt, InputMode.Menu); + } + + public virtual void ResetPopup() + { + _labelText.text = ""; + _ok1Command = null; + _ok2Command = null; + _cancelCommand = null; + _cancelButton.SetPrompt(null, InputMode.Menu); + _confirmButton1.SetPrompt(null, InputMode.Menu); + _confirmButton2.SetPrompt(null, InputMode.Menu); + _selectOnActivate = null; + } + + public virtual void CloseMenuOnOk(bool value) + { + _closeMenuOnOk = value; + } + + public virtual bool EventsHaveListeners() + { + return OnPopupCancel != null || OnPopupConfirm1 != null || OnPopupConfirm2 != null; + } + + public override void InitializeMenu() + { + base.InitializeMenu(); + if (_cancelAction != null) + { + _cancelAction.OnSubmitAction += InvokeCancel; + } + + _ok1Action.OnSubmitAction += InvokeOk1; + _ok2Action.OnSubmitAction += InvokeOk2; + // PopupCanvas is disabled after the menus are initialized, and overrideSorting can only be set when it's enabled + _menuActivationRoot.gameObject.SetActive(true); + _popupCanvas = gameObject.GetAddComponent(); + _popupCanvas.overrideSorting = true; + _popupCanvas.sortingOrder = 30000; + _menuActivationRoot.gameObject.SetActive(false); + gameObject.GetAddComponent(); + gameObject.GetAddComponent(); + } + + protected virtual void Update() + { + if (_cancelCommand != null && OWInput.IsNewlyPressed(_cancelCommand, InputMode.All)) + { + InvokeCancel(); + return; + } + + if (_ok1Command != null && OWInput.IsNewlyPressed(_ok1Command, InputMode.All)) + { + InvokeOk1(); + return; + } + + if (_ok2Command != null && OWInput.IsNewlyPressed(_ok2Command, InputMode.All)) + { + InvokeOk2(); + return; + } + + if (_usingGamepad != OWInput.UsingGamepad()) + { + _usingGamepad = OWInput.UsingGamepad(); + if (_usingGamepad) + { + Locator.GetMenuInputModule().SelectOnNextUpdate(null); + return; + } + + Locator.GetMenuInputModule().SelectOnNextUpdate(_selectOnActivate); + } + } + + public override void EnableMenu(bool value) + { + if (value == _enabledMenu) + { + return; + } + + _enabledMenu = value; + if (_enabledMenu && !_initialized) + { + InitializeMenu(); + } + + if (!_addToMenuStackManager) + { + if (_enabledMenu) + { + Activate(); + if (_selectOnActivate != null) + { + var component = _selectOnActivate.GetComponent(); + if (component != null) + { + component.SilenceNextSelectEvent(); + } + + Locator.GetMenuInputModule().SelectOnNextUpdate(_selectOnActivate); + return; + } + } + else + { + Deactivate(false); + } + + return; + } + + if (_enabledMenu) + { + MenuStackManager.SharedInstance.Push(this, true); + return; + } + + if (MenuStackManager.SharedInstance.Peek() == this) + { + MenuStackManager.SharedInstance.Pop(false); + return; + } + + Debug.LogError("Cannot disable Menu unless it is on the top the MenuLayerManager stack. Current menu on top: " + MenuStackManager.SharedInstance.Peek().name); + } + + public override void Activate() + { + base.Activate(); + if (_rootCanvas != null) + { + _blocker = CreateBlocker(_rootCanvas); + } + } + + public override void Deactivate(bool keepPreviousMenuVisible = false) + { + if (_rootCanvas != null) + { + DestroyBlocker(_blocker); + } + + var component = _cancelAction.GetComponent(); + if (component != null) + { + component.ChangeState(UIElementState.NORMAL, true); + } + + component = _ok1Action.GetComponent(); + if (component != null) + { + component.ChangeState(UIElementState.NORMAL, true); + } + + component = _ok2Action.GetComponent(); + if (component != null) + { + component.ChangeState(UIElementState.NORMAL, true); + } + + base.Deactivate(keepPreviousMenuVisible); + } + + public override void OnCancelEvent(GameObject selectedObj, BaseEventData eventData) + { + base.OnCancelEvent(selectedObj, eventData); + OnPopupCancel?.Invoke(); + } + + protected virtual void InvokeCancel() + { + EnableMenu(false); + OnPopupCancel?.Invoke(); + } + + protected virtual bool Validate() + { + var flag = true; + if (OnPopupValidate != null) + { + var invocationList = OnPopupValidate.GetInvocationList(); + for (var i = 0; i < invocationList.Length; i++) + { + var flag2 = (bool)invocationList[i].DynamicInvoke(Array.Empty()); + flag = flag && flag2; + } + } + + return flag; + } + + protected virtual void InvokeOk1() + { + if (!Validate()) + { + return; + } + + if (_closeMenuOnOk) + { + EnableMenu(false); + } + + OnPopupConfirm1?.Invoke(); + } + + protected virtual void InvokeOk2() + { + if (!Validate()) + { + return; + } + + if (_closeMenuOnOk) + { + EnableMenu(false); + } + + OnPopupConfirm2?.Invoke(); + } + + protected virtual GameObject CreateBlocker(Canvas rootCanvas) + { + var gameObject = new GameObject("Blocker"); + var rectTransform = gameObject.AddComponent(); + rectTransform.SetParent(rootCanvas.transform, false); + rectTransform.anchorMin = Vector3.zero; + rectTransform.anchorMax = Vector3.one; + rectTransform.sizeDelta = Vector2.zero; + var canvas = gameObject.AddComponent(); + canvas.overrideSorting = true; + canvas.sortingLayerID = _popupCanvas.sortingLayerID; + canvas.sortingOrder = _popupCanvas.sortingOrder - 1; + gameObject.AddComponent(); + var image = gameObject.AddComponent(); + if (Locator.GetUIStyleManager() != null) + { + image.color = Locator.GetUIStyleManager().GetPopupBlockerColor(); + return gameObject; + } + + image.color = Color.clear; + return gameObject; + } + + protected virtual void DestroyBlocker(GameObject blocker) + { + Destroy(blocker); + } + } +} diff --git a/src/OWML.ModHelper.Menus/CustomInputs/OWMLToggleElement.cs b/src/OWML.ModHelper.Menus/CustomInputs/OWMLToggleElement.cs new file mode 100644 index 000000000..fd2656248 --- /dev/null +++ b/src/OWML.ModHelper.Menus/CustomInputs/OWMLToggleElement.cs @@ -0,0 +1,187 @@ +using UnityEngine.UI; +using UnityEngine; +using UnityEngine.EventSystems; +using OWML.Common; + +namespace OWML.ModHelper.Menus.CustomInputs +{ + public class OWMLToggleElement : OWMLMenuValueOption, ISelectHandler, IEventSystemHandler, IDeselectHandler, ISubmitHandler, ICancelHandler, IOWMLToggleElement + { + public Text _displayText; + public Graphic _toggleGraphic; + public Button _toggleElementButton; + + protected InputEventListener _buttonEventListener; + protected UIStyleApplier _navigationButtonStyleApplier; + protected bool _initOnNextFrame; + protected bool _uiElementSelected; + private bool _mouseClickInElement; + + public event BoolOptionValueChangedEvent OnValueChanged; + + private void Update() + { + if (_initOnNextFrame) + { + Initialize(_value); + } + } + + public override void Initialize(int value) + { + if (value < 0 || value > 1) + { + Debug.LogError("Initialize value for " + base.gameObject.name + " is not 0 or 1"); + } + base.Initialize(value); + if (!_initOnNextFrame) + { + _initOnNextFrame = true; + return; + } + if (_toggleElementButton == null) + { + _toggleElementButton = this.GetRequiredComponent