Skip to content

Instantly share code, notes, and snippets.

@studentutu
Last active December 30, 2021 12:35
Show Gist options
  • Save studentutu/714d78ad6cebfe147bfa852fb7046a90 to your computer and use it in GitHub Desktop.
Save studentutu/714d78ad6cebfe147bfa852fb7046a90 to your computer and use it in GitHub Desktop.
Unity Custom caret, set caret position from world position, from raycast. A script for Unity3D to customize the caret of a Text Mesh Pro Input Field game object.
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using UnityEngine.Scripting;
/// Should be Used in combination with the Second Transparent text ( substring of the inputfield text based on inputfield caret position)
/// This transparent text should use ContentSizeFitter + Horizontal Layout Group. Add Text Caret as child to the transparent text.
/// This way you will always have the same alighment as the original text.
/// Be sure to use Update function to set custom transparent text acording to new InputField caret Position (update function)
/// Add Separate Event Trigger with some UI Image Element On Top Of the Input Field.
/// Make sure anchors are the same for it as well as InputField Text.
/// Invoke OnClickMovePointerByEvenData from OnClick event
public class CustomUICaret : MonoBehaviour
{
[SerializeField] private TMP_InputField _inputField;
public static Vector2 SetX(Vector2 vector, float value)
{
return new Vector2(value, vector.y);
}
public static Vector2 SetY(Vector2 vector, float value)
{
return new Vector2(vector.x, value);
}
public static Vector2 GetSize(RectTransform trans)
{
return trans.rect.size;
}
public static Vector2 GetPixelFromWorldPoint(RectTransform rectTransform, Vector3 worldPoint)
{
Vector3[] v = new Vector3[4];
rectTransform.GetWorldCorners(v);
var loverLeft = v[0];
var upperLeft = v[1];
var upperRight = v[2];
var worldDistanceWidth = (upperLeft - upperRight).magnitude;
var worldDistanceHeight = (upperLeft - loverLeft).magnitude;
Vector2 imagePos = Vector2.one * -1;
var size = GetSize(rectTransform);
// Assume lover left is (0,0) pixel
// Project on worldHeight
var vectorToWorldPoint = loverLeft - worldPoint;
var upperLeftPosition = upperLeft;
var projectOnHeight = Vector3.Project(
vectorToWorldPoint,
loverLeft - upperLeftPosition
);
// Project on worldWidth
var projectOnWidth = Vector3.Project(
vectorToWorldPoint,
upperLeftPosition - upperRight
);
// if using sqrt - we will not know if it is inside
var percentWidth = projectOnWidth.magnitude / worldDistanceWidth;
var percentHeight = projectOnHeight.magnitude / worldDistanceHeight;
if (percentWidth >= 0 && percentWidth <= 1)
{
imagePos = SetX(imagePos,percentWidth * size.x);
}
if (percentHeight >= 0 && percentHeight <= 1)
{
imagePos = SetY(imagePos,percentHeight * size.y);
}
return imagePos;
}
/// Invoke this method from OnClick on the event trigger
[Preserve]
public void OnClickMovePointerByEvenData(BaseEventData eventData)
{
// Get World Point
var asPointerEventData = eventData as PointerEventData;
if (asPointerEventData == null)
{
return;
}
// Transform Into Local Anchored Position
var positionWorld = asPointerEventData.pointerCurrentRaycast.worldPosition;
var fromExtension = GetPixelFromWorldPoint(_inputField.textViewport, positionWorld);
// find nearest
float currentX = 0;
int maxNumber = _inputField.text.Length;
var TextObject = _inputField.textComponent;
float constantValue = 0;
for (int i = 0; i < maxNumber; i++)
{
if (i == 1)
{
constantValue = TextObject.textInfo.characterInfo[i].xAdvance;
}
if (currentX >= fromExtension.x)
{
_inputField.caretPosition = Mathf.Clamp(i - 1, 0, maxNumber);
return;
}
if (TextObject.textInfo.characterInfo.Length > i)
{
currentX = TextObject.textInfo.characterInfo[i].xAdvance;
}
else
{
currentX += constantValue;
}
}
_inputField.caretPosition = maxNumber;
}
}
@studentutu
Copy link
Author

this will set your caret into target world position ( raycast position)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment