/* * Copyright 2022 Peter Han * Permission is hereby granted, free of charge, to any person obtaining a copy of this software * and associated documentation files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or * substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ using HarmonyLib; using PeterHan.PLib.Core; using System; using System.Collections.Generic; using TMPro; using UnityEngine; namespace PeterHan.PLib.UI { /// /// Patches bugs in Text Mesh Pro. /// internal static class TextMeshProPatcher { /// /// The ID to use for Harmony patches. /// private const string HARMONY_ID = "TextMeshProPatch"; /// /// Tracks whether the TMP patches have been checked. /// private static volatile bool patchChecked = false; /// /// Serializes multiple thread access to the patch status. /// private static readonly object patchLock = new object(); /// /// Applied to TMP_InputField to fix a bug that prevented auto layout from ever /// working. /// private static bool AssignPositioningIfNeeded_Prefix(TMP_InputField __instance, RectTransform ___caretRectTrans, TMP_Text ___m_TextComponent) { bool cont = true; var crt = ___caretRectTrans; if (___m_TextComponent != null && crt != null && __instance != null && ___m_TextComponent.isActiveAndEnabled) { var rt = ___m_TextComponent.rectTransform; if (crt.localPosition != rt.localPosition || crt.localRotation != rt.localRotation || crt.localScale != rt.localScale || crt.anchorMin != rt.anchorMin || crt.anchorMax != rt.anchorMax || crt.anchoredPosition != rt.anchoredPosition || crt.sizeDelta != rt.sizeDelta || crt.pivot != rt.pivot) { __instance.StartCoroutine(ResizeCaret(crt, rt)); cont = false; } } return cont; } /// /// Checks to see if a patch with our class name has already been applied. /// /// The patch list to search. /// true if a patch with this class has already patched the method, or false otherwise. private static bool HasOurPatch(IEnumerable patchList) { bool found = false; if (patchList != null) { foreach (var patch in patchList) { string ownerName = patch.PatchMethod.DeclaringType.Name; // Avoid stomping ourselves, or legacy PLibs < 3.14 if (ownerName == nameof(TextMeshProPatcher) || ownerName == "PLibPatches") { #if DEBUG PUtil.LogDebug("TextMeshProPatcher found existing patch from: {0}". F(patch.owner)); #endif found = true; break; } } } return found; } /// /// Patches TMP_InputField with fixes, but only if necessary. /// /// The type of TMP_InputField. /// The Harmony instance to use for patching. private static void InputFieldPatches(Type tmpType) { var instance = new Harmony(HARMONY_ID); var aip = tmpType.GetMethodSafe("AssignPositioningIfNeeded", false, PPatchTools.AnyArguments); if (aip != null && !HasOurPatch(Harmony.GetPatchInfo(aip)?.Prefixes)) instance.Patch(aip, prefix: new HarmonyMethod(typeof(TextMeshProPatcher), nameof(AssignPositioningIfNeeded_Prefix))); var oe = tmpType.GetMethodSafe("OnEnable", false, PPatchTools.AnyArguments); if (oe != null && !HasOurPatch(Harmony.GetPatchInfo(oe)?.Postfixes)) instance.Patch(oe, postfix: new HarmonyMethod(typeof(TextMeshProPatcher), nameof(OnEnable_Postfix))); } /// /// Applied to TMPro.TMP_InputField to fix a clipping bug inside of Scroll Rects. /// /// https://forum.unity.com/threads/textmeshpro-text-still-visible-when-using-nested-rectmask2d.537967/ /// private static void OnEnable_Postfix(UnityEngine.UI.Scrollbar ___m_VerticalScrollbar, TMP_Text ___m_TextComponent) { var component = ___m_TextComponent; if (component != null) component.ignoreRectMaskCulling = ___m_VerticalScrollbar != null; } /// /// Patches Text Mesh Pro input fields to fix a variety of bugs. Should be used before /// any Text Mesh Pro objects are created. /// public static void Patch() { lock (patchLock) { if (!patchChecked) { var tmpType = PPatchTools.GetTypeSafe("TMPro.TMP_InputField"); if (tmpType != null) try { InputFieldPatches(tmpType); } catch (Exception) { PUtil.LogWarning("Unable to patch TextMeshPro bug, text fields may display improperly inside scroll areas"); } patchChecked = true; } } } /// /// Resizes the caret object to match the text. Used as an enumerator. /// /// The rectTransform of the caret. /// The rectTransform of the text. private static System.Collections.IEnumerator ResizeCaret( RectTransform caretTransform, RectTransform textTransform) { yield return new WaitForEndOfFrame(); caretTransform.localPosition = textTransform.localPosition; caretTransform.localRotation = textTransform.localRotation; caretTransform.localScale = textTransform.localScale; caretTransform.anchorMin = textTransform.anchorMin; caretTransform.anchorMax = textTransform.anchorMax; caretTransform.anchoredPosition = textTransform.anchoredPosition; caretTransform.sizeDelta = textTransform.sizeDelta; caretTransform.pivot = textTransform.pivot; } } }