oni-priority-ux/mod/PLibCore/TextMeshProPatcher.cs

165 lines
6.3 KiB
C#

/*
* 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 {
/// <summary>
/// Patches bugs in Text Mesh Pro.
/// </summary>
internal static class TextMeshProPatcher {
/// <summary>
/// The ID to use for Harmony patches.
/// </summary>
private const string HARMONY_ID = "TextMeshProPatch";
/// <summary>
/// Tracks whether the TMP patches have been checked.
/// </summary>
private static volatile bool patchChecked = false;
/// <summary>
/// Serializes multiple thread access to the patch status.
/// </summary>
private static readonly object patchLock = new object();
/// <summary>
/// Applied to TMP_InputField to fix a bug that prevented auto layout from ever
/// working.
/// </summary>
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;
}
/// <summary>
/// Checks to see if a patch with our class name has already been applied.
/// </summary>
/// <param name="patchList">The patch list to search.</param>
/// <returns>true if a patch with this class has already patched the method, or false otherwise.</returns>
private static bool HasOurPatch(IEnumerable<Patch> 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;
}
/// <summary>
/// Patches TMP_InputField with fixes, but only if necessary.
/// </summary>
/// <param name="tmpType">The type of TMP_InputField.</param>
/// <param name="instance">The Harmony instance to use for patching.</param>
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)));
}
/// <summary>
/// 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/
/// </summary>
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;
}
/// <summary>
/// Patches Text Mesh Pro input fields to fix a variety of bugs. Should be used before
/// any Text Mesh Pro objects are created.
/// </summary>
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;
}
}
}
/// <summary>
/// Resizes the caret object to match the text. Used as an enumerator.
/// </summary>
/// <param name="caretTransform">The rectTransform of the caret.</param>
/// <param name="textTransform">The rectTransform of the text.</param>
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;
}
}
}