@pmndrs/uikit
Version:
Build performant 3D user interfaces with Three.js and yoga.
118 lines (117 loc) • 5.43 kB
JavaScript
import { getGlyphOffsetY, getOffsetToNextLine } from '../utils.js';
import { getTextXOffset, getTextYOffset, getWhitespaceWidth } from './positioned.js';
const noSelectionTransformations = [];
export function getCharIndex(layout, x, y, position) {
if (layout == null) {
return 0;
}
y -= -getTextYOffset(layout, layout.verticalAlign);
const lineIndex = Math.floor(y / -getOffsetToNextLine(layout.lineHeight));
const lines = layout.lines;
if (lineIndex < 0 || lines.length === 0) {
return 0;
}
if (lineIndex >= lines.length) {
const lastLine = lines[lines.length - 1];
return lastLine.charIndexOffset + lastLine.charLength + 1;
}
const line = lines[lineIndex];
for (let i = 0; i < line.entries.length; i++) {
const entry = line.entries[i];
if (x < getEntryX(entry, position === 'between' ? 0.5 : 1) + layout.availableWidth / 2) {
return i + line.charIndexOffset;
}
}
return line.charIndexOffset + line.charLength + 1;
}
export function getCaretTransformation(layout, charIndex) {
if (layout == null || layout.lines.length === 0) {
return undefined;
}
const whitespaceWidth = getWhitespaceWidth(layout);
const { lineIndex, x } = getGlyphLineAndX(layout, charIndex, true, whitespaceWidth);
const y = -(getTextYOffset(layout, layout.verticalAlign) -
layout.availableHeight / 2 +
lineIndex * getOffsetToNextLine(layout.lineHeight) +
getGlyphOffsetY(layout.fontSize, layout.lineHeight));
return { position: [x, y - layout.fontSize / 2], height: layout.fontSize };
}
export function getSelectionTransformations(layout, range) {
if (range == null || layout == null || layout.lines.length === 0) {
return { caret: undefined, selections: noSelectionTransformations };
}
const whitespaceWidth = getWhitespaceWidth(layout);
const [startCharIndexIncl, endCharIndexExcl] = range;
if (endCharIndexExcl <= startCharIndexIncl) {
return {
caret: getCaretTransformation(layout, endCharIndexExcl),
selections: noSelectionTransformations,
};
}
const start = getGlyphLineAndX(layout, startCharIndexIncl, true, whitespaceWidth);
const end = getGlyphLineAndX(layout, endCharIndexExcl - 1, false, whitespaceWidth);
if (start.lineIndex === end.lineIndex) {
return {
caret: undefined,
selections: [computeSelectionTransformation(start.lineIndex, start.x, end.x, layout, whitespaceWidth)],
};
}
const selections = [
computeSelectionTransformation(start.lineIndex, start.x, undefined, layout, whitespaceWidth),
];
for (let i = start.lineIndex + 1; i < end.lineIndex; i++) {
selections.push(computeSelectionTransformation(i, undefined, undefined, layout, whitespaceWidth));
}
selections.push(computeSelectionTransformation(end.lineIndex, undefined, end.x, layout, whitespaceWidth));
return { caret: undefined, selections };
}
function computeSelectionTransformation(lineIndex, startX, endX, layout, whitespaceWidth) {
const line = layout.lines[lineIndex];
const firstEntry = line.entries[0];
const lastEntry = line.entries[line.entries.length - 1];
if (startX == null) {
startX =
firstEntry == null
? getTextXOffset(layout.availableWidth, line.nonWhitespaceWidth, layout.textAlign) - layout.availableWidth / 2
: getEntryX(firstEntry, 0);
}
if (endX == null) {
endX = lastEntry == null ? startX : getEntryX(lastEntry, 1, whitespaceWidth);
}
const height = getOffsetToNextLine(layout.lineHeight);
const y = -(getTextYOffset(layout, layout.verticalAlign) - layout.availableHeight / 2 + lineIndex * height);
const width = endX - startX;
return { position: [startX + width / 2, y - height / 2], size: [width, height] };
}
function getGlyphLineAndX(layout, charIndex, start, whitespaceWidth) {
const { lines, availableWidth, textAlign } = layout;
const linesLength = lines.length;
if (charIndex >= lines[0].charIndexOffset) {
for (let lineIndex = 0; lineIndex < linesLength; lineIndex++) {
const line = lines[lineIndex];
if (charIndex >= line.charIndexOffset + line.charLength) {
continue;
}
const entry = line.entries[Math.max(charIndex - line.charIndexOffset, 0)];
if (entry != null) {
return { lineIndex, x: getEntryX(entry, start ? 0 : 1, whitespaceWidth) };
}
return {
lineIndex,
x: getTextXOffset(availableWidth, line.nonWhitespaceWidth, textAlign) - availableWidth / 2,
};
}
}
const lastLine = lines[linesLength - 1];
if (lastLine.entries.length === 0 || charIndex < lastLine.charIndexOffset) {
return {
lineIndex: linesLength - 1,
x: getTextXOffset(availableWidth, lastLine.nonWhitespaceWidth, textAlign) - availableWidth / 2,
};
}
const lastEntry = lastLine.entries[lastLine.entries.length - 1];
return { lineIndex: linesLength - 1, x: getEntryX(lastEntry, 1, whitespaceWidth) };
}
function getEntryX(entry, widthMultiplier, fallbackWidth) {
return entry.x + widthMultiplier * (entry.type === 'whitespace' ? (fallbackWidth ?? entry.width) : entry.width);
}