I'm creating a text box that allows for selectable text, based off of Unity's InputField. An issue I am now facing is that the selection highlight does not respond to masks (The parent has a mask, but it is just ignored) depth is also ignored, this seems to render through any other UI element that's in-front of it.:
I'm completely lost on how to go about masking it, so it does not show up past the bounds of the box. The code I used to generate it is heavily based off of Unity's, it's a bit long and kind of hacked together at the moment, I'm including it for references sake.
The method that generates the verts for the highlight in the .gif is GenerateHighlight
private void UpdateGeometry()
{
if(caretCanvasRenderer == null && textBacker != null)
{
GameObject caret = new GameObject(transform.name + "Caret");
caret.hideFlags = HideFlags.DontSave;
caret.transform.SetParent(textBacker.transform);
caret.transform.SetAsFirstSibling();
caret.layer = gameObject.layer;
caretRectTransform = caret.AddComponent<RectTransform>();
caretCanvasRenderer = caret.AddComponent<CanvasRenderer>();
caretCanvasRenderer.SetMaterial(Graphic.defaultGraphicMaterial, null);
//caretCanvasRenderer.isMask = true;
caret.AddComponent<LayoutElement>().ignoreLayout = true;
AssignPositioningIfNeeded();
}
if (caretCanvasRenderer == null)
return;
OnFillVBO(VBO);
if (VBO.Count == 0)
caretCanvasRenderer.SetVertices(null, 0);
else
caretCanvasRenderer.SetVertices(VBO.ToArray(), VBO.Count);
VBO.Clear();
}
private void OnFillVBO(List<UIVertex> vbo)
{
//if (!isFocused)
// return;
Rect inputRect = textBacker.rectTransform.rect;
Vector2 extents = inputRect.size;
// get the text alignment anchor point for the text in local space
Vector2 textAnchorPivot = Text.GetTextAnchorPivot(textBacker.alignment);
Vector2 refPoint = Vector2.zero;
refPoint.x = Mathf.Lerp(inputRect.xMin, inputRect.xMax, textAnchorPivot.x);
refPoint.y = Mathf.Lerp(inputRect.yMin, inputRect.yMax, textAnchorPivot.y);
// Ajust the anchor point in screen space
Vector2 roundedRefPoint = textBacker.PixelAdjustPoint(refPoint);
// Determine fraction of pixel to offset text mesh.
// This is the rounding in screen space, plus the fraction of a pixel the text anchor pivot is from the corner of the text mesh.
Vector2 roundingOffset = roundedRefPoint - refPoint + Vector2.Scale(extents, textAnchorPivot);
roundingOffset.x = roundingOffset.x - Mathf.Floor(0.5f + roundingOffset.x);
roundingOffset.y = roundingOffset.y - Mathf.Floor(0.5f + roundingOffset.y);
if (!hasSelection)
GenerateCursor(vbo, roundingOffset);
else
GenerateHightlight(vbo, roundingOffset);
}
private void GenerateCursor(List<UIVertex> vbo, Vector2 roundingOffset)
{
if (!caretVisible)
return;
if(cursorVerts == null)
CreateCursorVerts();
float width = 1f;
float height = textBacker.fontSize;
int adjustedPosition = Mathf.Max(0, caretPositionInternal - drawStart);
TextGenerator generator = textBacker.cachedTextGenerator;
if(generator == null)
return;
if (textBacker.resizeTextForBestFit)
height = generator.fontSizeUsedForBestFit / textBacker.pixelsPerUnit;
Vector2 startPosition = Vector2.zero;
// Calculate startPosition
if (generator.characterCountVisible + 1 > adjustedPosition || adjustedPosition == 0)
{
UICharInfo cursorChar = generator.characters[adjustedPosition];
startPosition.x = cursorChar.cursorPos.x;
startPosition.y = cursorChar.cursorPos.y;
}
startPosition.x /= textBacker.pixelsPerUnit;
// TODO: Only clamp when Text uses horizontal word wrap.
if (startPosition.x > textBacker.rectTransform.rect.xMax)
startPosition.x = textBacker.rectTransform.rect.xMax;
cursorVerts[0].position = new Vector3(startPosition.x, startPosition.y - height, 0.0f);
cursorVerts[1].position = new Vector3(startPosition.x + width, startPosition.y - height, 0.0f);
cursorVerts[2].position = new Vector3(startPosition.x + width, startPosition.y, 0.0f);
cursorVerts[3].position = new Vector3(startPosition.x, startPosition.y, 0.0f);
if (roundingOffset != Vector2.zero)
{
for (int i = 0; i < cursorVerts.Length; i++)
{
UIVertex uiv = cursorVerts[i];
uiv.position.x += roundingOffset.x;
uiv.position.y += roundingOffset.y;
vbo.Add(uiv);
}
}
else
{
for (int i = 0; i < cursorVerts.Length; i++)
{
vbo.Add(cursorVerts[i]);
}
}
startPosition.y = Screen.height - startPosition.y;
Input.compositionCursorPos = startPosition;
}
private void GenerateHightlight(List<UIVertex> vbo, Vector2 roundingOffset)
{
int startChar = Mathf.Max(0, caretPositionInternal - drawStart);
int endChar = Mathf.Max(0, caretSelectPositionInternal - drawStart);
// Ensure pos is always less then selPos to make the code simpler
if (startChar > endChar)
{
int temp = startChar;
startChar = endChar;
endChar = temp;
}
endChar -= 1;
TextGenerator generator = textBacker.cachedTextGenerator;
int currentLineIndex = DetermineCharacterLine(startChar, generator);
float height = textBacker.fontSize;
if (textBacker.resizeTextForBestFit)
height = generator.fontSizeUsedForBestFit / textBacker.pixelsPerUnit;
if (generator != null && generator.lines.Count > 0)
{
// TODO: deal with multiple lines with different line heights.
height = generator.lines[0].height;
}
if (textBacker.resizeTextForBestFit && generator != null)
{
height = generator.fontSizeUsedForBestFit;
}
int nextLineStartIdx = GetLineEndPosition(generator, currentLineIndex);
UIVertex vert = UIVertex.simpleVert;
vert.uv0 = Vector2.zero;
vert.color = selectionColor;
int currentChar = startChar;
while (currentChar <= endChar && currentChar < generator.characterCountVisible)
{
if (currentChar + 1 == nextLineStartIdx || currentChar == endChar)
{
UICharInfo startCharInfo = generator.characters[startChar];
UICharInfo endCharInfo = generator.characters[currentChar];
Vector2 startPosition = new Vector2(startCharInfo.cursorPos.x / textBacker.pixelsPerUnit, startCharInfo.cursorPos.y);
Vector2 endPosition = new Vector2((endCharInfo.cursorPos.x + endCharInfo.charWidth) / textBacker.pixelsPerUnit, startPosition.y - height / textBacker.pixelsPerUnit);
// Checking xMin as well due to text generator not setting possition if char is not rendered.
if (endPosition.x > textBacker.rectTransform.rect.xMax || endPosition.x < textBacker.rectTransform.rect.xMin)
endPosition.x = textBacker.rectTransform.rect.xMax;
vert.position = new Vector3(startPosition.x, endPosition.y, 0.0f) + (Vector3)roundingOffset;
vbo.Add(vert);
vert.position = new Vector3(endPosition.x, endPosition.y, 0.0f) + (Vector3)roundingOffset;
vbo.Add(vert);
vert.position = new Vector3(endPosition.x, startPosition.y, 0.0f) + (Vector3)roundingOffset;
vbo.Add(vert);
vert.position = new Vector3(startPosition.x, startPosition.y, 0.0f) + (Vector3)roundingOffset;
vbo.Add(vert);
startChar = currentChar + 1;
currentLineIndex++;
nextLineStartIdx = GetLineEndPosition(generator, currentLineIndex);
}
currentChar++;
}
}
The gist of it, is it creates a new gameobject. Adds a canvas renderer, and rect transform. Calls the appropriate method that generates the vertices, and then assigns those to the `CanvasRenderer.