/* ///////////////////////////////////////////////////////////////////////////////////////////////// Utils 2023-11-12 by MacGruber Collection of various utility functions. https://www.patreon.com/MacGruber_Laboratory Licensed under CC-BY. (see https://creativecommons.org/licenses/by/4.0/) Feel free to incorporate this libary in your releases, but credit is required. ///////////////////////////////////////////////////////////////////////////////////////////////// */ using UnityEngine; using UnityEngine.UI; using UnityEngine.XR; using UnityEngine.Events; using System; using System.Text; using System.Collections; using System.Collections.Generic; using Request = MeshVR.AssetLoader.AssetBundleFromFileRequest; using AssetBundles; using SimpleJSON; namespace MacGruber { public static class Utils { // VaM Plugins can contain multiple Scripts, if you load them via a *.cslist file. This function allows you to get // an instance of another script within the same plugin, allowing you directly interact with it by reading/writing // data, calling functions, etc. public static T FindWithinSamePlugin(MVRScript self) where T : MVRScript { int i = self.name.IndexOf('_'); if (i < 0) return null; string prefix = self.name.Substring(0, i+1); string scriptName = prefix + typeof(T).FullName; return self.containingAtom.GetStorableByID(scriptName) as T; } // Send message to all plugins on atoms in the scene. This includes the 'Scene Plugins' tab, but doens't include 'Session Plugins'. // Different VaM Plugins don't know of each other or their datatypes. However, using this you can call public methods with no or // only a single parameter. As parameter types you can only use standard C#/Unity/VaM types, but not your own definitions. // It's recommended to use long/explicit method names. For example you could include your creator name and the name of the plugin. // This helps avoid conflicts between different plugins. Performance isn't too good, so it should only be used for occasional calls. // // Example: // public void MacGruber_VirtualLock_RegisterFocusArea(MVRScript focusAreaScript) { /*...*/ } // // Which can then be called like this: // Utils.SendMessageToPluginsInScene("MacGruber_VirtualLock_RegisterFocusArea", this); public static void SendMessageToPluginsInScene(string method, object parameter) { List atoms = SuperController.singleton.GetAtoms(); for (int i=0; i storables = atom.GetStorableIDs(); for (int j=0; j= 0) return filename.Substring(0, idx+2); else return string.Empty; } // Check if our plugin is running from inside a package public static bool IsInPackage(MVRScript self) { string id = self.name.Substring(0, self.name.IndexOf('_')); string filename = self.manager.GetJSON()["plugins"][id].Value; return filename.IndexOf(":/") >= 0; } // =========================================================================================== // Create VaM-UI Toggle button public static JSONStorableBool SetupToggle(MVRScript script, string label, bool defaultValue, bool rightSide) { JSONStorableBool storable = new JSONStorableBool(label, defaultValue); storable.storeType = JSONStorableParam.StoreType.Full; script.CreateToggle(storable, rightSide); script.RegisterBool(storable); return storable; } // Create VaM-UI Float slider public static JSONStorableFloat SetupSliderFloat(MVRScript script, string label, float defaultValue, float minValue, float maxValue, bool rightSide) { JSONStorableFloat storable = new JSONStorableFloat(label, defaultValue, minValue, maxValue, true, true); storable.storeType = JSONStorableParam.StoreType.Full; script.CreateSlider(storable, rightSide); script.RegisterFloat(storable); return storable; } // Create VaM-UI Float slider public static JSONStorableFloat SetupSliderFloatWithRange(MVRScript script, string label, float defaultValue, float minValue, float maxValue, bool rightSide) { JSONStorableFloat storable = new JSONStorableFloat(label, defaultValue, minValue, maxValue, true, true); storable.storeType = JSONStorableParam.StoreType.Full; storable.constrained = false; UIDynamicSlider slider = script.CreateSlider(storable, rightSide); slider.rangeAdjustEnabled = true; script.RegisterFloat(storable); return storable; } // Create VaM-UI Float slider public static JSONStorableFloat SetupSliderInt(MVRScript script, string label, int defaultValue, int minValue, int maxValue, bool rightSide) { JSONStorableFloat storable = new JSONStorableFloat(label, defaultValue, minValue, maxValue, true, true); storable.storeType = JSONStorableParam.StoreType.Full; UIDynamicSlider slider = script.CreateSlider(storable, rightSide); slider.slider.wholeNumbers = true; slider.valueFormat = "F0"; script.RegisterFloat(storable); return storable; } // Create VaM-UI ColorPicker public static JSONStorableColor SetupColor(MVRScript script, string label, Color color, bool rightSide) { HSVColor hsvColor = HSVColorPicker.RGBToHSV(color.r, color.g, color.b); JSONStorableColor storable = new JSONStorableColor(label, hsvColor); storable.storeType = JSONStorableParam.StoreType.Full; script.CreateColorPicker(storable, rightSide); script.RegisterColor(storable); return storable; } // Create VaM-UI StringChooser public static JSONStorableStringChooser SetupStringChooser(MVRScript self, string label, List entries, bool rightSide) { string defaultEntry = entries.Count > 0 ? entries[0] : ""; JSONStorableStringChooser storable = new JSONStorableStringChooser(label, entries, defaultEntry, label); self.CreateScrollablePopup(storable, rightSide); self.RegisterStringChooser(storable); return storable; } // Create VaM-UI StringChooser public static JSONStorableStringChooser SetupStringChooser(MVRScript self, string label, List entries, int defaultIndex, bool rightSide) { string defaultEntry = (defaultIndex >= 0 && defaultIndex < entries.Count) ? entries[defaultIndex] : ""; JSONStorableStringChooser storable = new JSONStorableStringChooser(label, entries, defaultEntry, label); self.CreateScrollablePopup(storable, rightSide); self.RegisterStringChooser(storable); return storable; } // Create VaM-UI StringChooser for Enum public static JSONStorableStringChooser SetupEnumChooser(MVRScript self, string label, TEnum defaultValue, bool rightSide, EnumSetCallback callback) where TEnum : struct, IComparable, IConvertible, IFormattable { List names = Enum.GetNames(typeof(TEnum)).ToList(); JSONStorableStringChooser storable = new JSONStorableStringChooser(label, names, defaultValue.ToString(), label); storable.setCallbackFunction += (string name) => { TEnum v = (TEnum)Enum.Parse(typeof(TEnum), name); callback(v); }; self.CreateScrollablePopup(storable, rightSide); self.RegisterStringChooser(storable); return storable; } // Create VaM-UI TextureChooser. Note that you are responsible for destroying the texture when you don't need it anymore. public static JSONStorableUrl SetupTexture2DChooser(MVRScript self, string label, string defaultValue, bool rightSide, TextureSettings settings, TextureSetCallback callback) { JSONStorableUrl storable = new JSONStorableUrl(label, string.Empty, (string url) => { QueueLoadTexture(url, settings, callback); }, "jpg|png|tif|tiff"); self.RegisterUrl(storable); UIDynamicButton button = self.CreateButton("Browse " + label, rightSide); UIDynamicTextField textfield = self.CreateTextField(storable, rightSide); textfield.UItext.alignment = TextAnchor.MiddleRight; textfield.UItext.horizontalOverflow = HorizontalWrapMode.Overflow; textfield.UItext.verticalOverflow = VerticalWrapMode.Truncate; LayoutElement layout = textfield.GetComponent(); layout.preferredHeight = layout.minHeight = 35; textfield.height = 35; if (!string.IsNullOrEmpty(defaultValue)) storable.SetFilePath(defaultValue); storable.RegisterFileBrowseButton(button.button); return storable; } // Create VaM-UI AssetBundleChooser. public static JSONStorableUrl SetupAssetBundleChooser(MVRScript self, string label, string defaultValue, bool rightSide, string fileExtensions) { JSONStorableUrl storable = new JSONStorableUrl(label, defaultValue, fileExtensions); self.RegisterUrl(storable); UIDynamicButton button = self.CreateButton("Select " + label, rightSide); UIDynamicTextField textfield = self.CreateTextField(storable, rightSide); textfield.UItext.alignment = TextAnchor.MiddleRight; textfield.UItext.horizontalOverflow = HorizontalWrapMode.Overflow; textfield.UItext.verticalOverflow = VerticalWrapMode.Truncate; LayoutElement layout = textfield.GetComponent(); layout.preferredHeight = layout.minHeight = 35; textfield.height = 35; if (!string.IsNullOrEmpty(defaultValue)) storable.SetFilePath(defaultValue); storable.RegisterFileBrowseButton(button.button); return storable; } // Create VaM-UI InfoText field public static JSONStorableString SetupInfoText(MVRScript script, string text, float height, bool rightSide) { JSONStorableString storable = new JSONStorableString("Info", text); UIDynamic textfield = script.CreateTextField(storable, rightSide); textfield.height = height; LayoutElement sdsfLayout = textfield.GetComponent(); sdsfLayout.preferredHeight = sdsfLayout.minHeight = height; return storable; } public static UIDynamic SetupSpacer(MVRScript script, float height, bool rightSide) { UIDynamic spacer = script.CreateSpacer(rightSide); spacer.height = height; return spacer; } // Create VaM-UI button public static UIDynamicButton SetupButton(MVRScript script, string label, UnityAction callback, bool rightSide) { UIDynamicButton button = script.CreateButton(label, rightSide); button.button.onClick.AddListener(callback); return button; } // Create input action trigger public static JSONStorableAction SetupAction(MVRScript script, string name, JSONStorableAction.ActionCallback callback) { JSONStorableAction action = new JSONStorableAction(name, callback); script.RegisterAction(action); return action; } // =========================================================================================== // Custom UI system with new UI elements and the ability to easily add/remove UI at runtime // // Usage instructions: // - Before using the custom UI elements, call from your MVRScript: // Utils.OnInitUI(CreateUIElement); // - When your MVRScript receives the OnDestroy message call: // Utils.OnDestroyUI(); // Create one-line text input with label public static UIDynamicLabelInput SetupTextInput(MVRScript script, string label, JSONStorableString storable, bool rightSide) { if (ourLabelWithInputPrefab == null) { ourLabelWithInputPrefab = new GameObject("LabelInput"); ourLabelWithInputPrefab.SetActive(false); RectTransform rt = ourLabelWithInputPrefab.AddComponent(); rt.anchorMax = new Vector2(0, 1); rt.anchorMin = new Vector2(0, 1); rt.offsetMax = new Vector2(535, -500); rt.offsetMin = new Vector2(10, -600); LayoutElement le = ourLabelWithInputPrefab.AddComponent(); le.flexibleWidth = 1; le.minHeight = 45; le.minWidth = 350; le.preferredHeight = 45; le.preferredWidth = 500; RectTransform backgroundTransform = script.manager.configurableScrollablePopupPrefab.transform.Find("Background") as RectTransform; backgroundTransform = UnityEngine.Object.Instantiate(backgroundTransform, ourLabelWithInputPrefab.transform); backgroundTransform.name = "Background"; backgroundTransform.anchorMax = new Vector2(1, 1); backgroundTransform.anchorMin = new Vector2(0, 0); backgroundTransform.offsetMax = new Vector2(0, 0); backgroundTransform.offsetMin = new Vector2(0, -10); RectTransform labelTransform = script.manager.configurableScrollablePopupPrefab.transform.Find("Button/Text") as RectTransform;; labelTransform = UnityEngine.Object.Instantiate(labelTransform, ourLabelWithInputPrefab.transform); labelTransform.name = "Text"; labelTransform.anchorMax = new Vector2(0, 1); labelTransform.anchorMin = new Vector2(0, 0); labelTransform.offsetMax = new Vector2(155, -5); labelTransform.offsetMin = new Vector2(5, 0); Text labelText = labelTransform.GetComponent(); labelText.text = "Name"; labelText.color = Color.white; RectTransform inputTransform = script.manager.configurableTextFieldPrefab.transform as RectTransform; inputTransform = UnityEngine.Object.Instantiate(inputTransform, ourLabelWithInputPrefab.transform); inputTransform.anchorMax = new Vector2(1, 1); inputTransform.anchorMin = new Vector2(0, 0); inputTransform.offsetMax = new Vector2(-5, -5); inputTransform.offsetMin = new Vector2(160, -5); UIDynamicTextField textfield = inputTransform.GetComponent(); textfield.backgroundColor = Color.white; LayoutElement layout = textfield.GetComponent(); layout.preferredHeight = layout.minHeight = 35; InputField inputfield = textfield.gameObject.AddComponent(); inputfield.textComponent = textfield.UItext; RectTransform textTransform = textfield.UItext.rectTransform; textTransform.anchorMax = new Vector2(1, 1); textTransform.anchorMin = new Vector2(0, 0); textTransform.offsetMax = new Vector2(-5, -5); textTransform.offsetMin = new Vector2(10, -5); UnityEngine.Object.Destroy(textfield); UIDynamicLabelInput uid = ourLabelWithInputPrefab.AddComponent(); uid.label = labelText; uid.input = inputfield; } { Transform t = ourCreateUIElement(ourLabelWithInputPrefab.transform, rightSide); UIDynamicLabelInput uid = t.gameObject.GetComponent(); storable.inputField = uid.input; uid.label.text = label; t.gameObject.SetActive(true); return uid; } } // Create label that as an X button on the right side. public static UIDynamicLabelXButton SetupLabelXButton(MVRScript script, string label, UnityAction callback, bool rightSide) { if (ourLabelWithXButtonPrefab == null) { ourLabelWithXButtonPrefab = new GameObject("LabelXButton"); ourLabelWithXButtonPrefab.SetActive(false); RectTransform rt = ourLabelWithXButtonPrefab.AddComponent(); rt.anchorMax = new Vector2(0, 1); rt.anchorMin = new Vector2(0, 1); rt.offsetMax = new Vector2(535, -500); rt.offsetMin = new Vector2(10, -600); LayoutElement le = ourLabelWithXButtonPrefab.AddComponent(); le.flexibleWidth = 1; le.minHeight = 50; le.minWidth = 350; le.preferredHeight = 50; le.preferredWidth = 500; RectTransform backgroundTransform = script.manager.configurableScrollablePopupPrefab.transform.Find("Background") as RectTransform; backgroundTransform = UnityEngine.Object.Instantiate(backgroundTransform, ourLabelWithXButtonPrefab.transform); backgroundTransform.name = "Background"; backgroundTransform.anchorMax = new Vector2(1, 1); backgroundTransform.anchorMin = new Vector2(0, 0); backgroundTransform.offsetMax = new Vector2(0, 0); backgroundTransform.offsetMin = new Vector2(0, -10); RectTransform buttonTransform = script.manager.configurableScrollablePopupPrefab.transform.Find("Button") as RectTransform; buttonTransform = UnityEngine.Object.Instantiate(buttonTransform, ourLabelWithXButtonPrefab.transform); buttonTransform.name = "Button"; buttonTransform.anchorMax = new Vector2(1, 1); buttonTransform.anchorMin = new Vector2(1, 0); buttonTransform.offsetMax = new Vector2(0, 0); buttonTransform.offsetMin = new Vector2(-60, -10); Button buttonButton = buttonTransform.GetComponent