summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-07-20 13:58:11 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-07-20 13:58:11 +0300
commit10f44efd277855761f69687193d6d5250650186d (patch)
treef1adf38c4d215b7466952c2ba1c18692675cf191 /src/ui
parentdc1528e89d48947780e00fc1a49ce57cccdfbfe5 (diff)
Text: Hit testing during a text run
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/text.c47
-rw-r--r--src/ui/text.h4
2 files changed, 41 insertions, 10 deletions
diff --git a/src/ui/text.c b/src/ui/text.c
index 49b3f0cd..0d101813 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -1376,12 +1376,15 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1376 iAttributedText attrText; 1376 iAttributedText attrText;
1377 init_AttributedText(&attrText, args->text, args->maxLen, d, get_Color(args->color), 1377 init_AttributedText(&attrText, args->text, args->maxLen, d, get_Color(args->color),
1378 args->baseDir); 1378 args->baseDir);
1379 if (args->wrap) { 1379 iWrapText *wrap = args->wrap;
1380 args->wrap->baseDir = attrText.isBaseRTL ? -1 : +1; 1380 if (wrap) {
1381 wrap->baseDir = attrText.isBaseRTL ? -1 : +1;
1381 /* TODO: Duplicated args? */ 1382 /* TODO: Duplicated args? */
1382 iAssert(equalRange_Rangecc(args->wrap->text, args->text)); 1383 iAssert(equalRange_Rangecc(wrap->text, args->text));
1383 /* Initialize the wrap range. */ 1384 /* Initialize the wrap range. */
1384 args->wrap->wrapRange_ = args->text; 1385 wrap->wrapRange_ = args->text;
1386 wrap->hitChar_out = NULL;
1387 wrap->hitGlyphNormX_out = 0.0f;
1385 } 1388 }
1386 const iChar *logicalText = constData_Array(&attrText.logical); 1389 const iChar *logicalText = constData_Array(&attrText.logical);
1387 const iChar *visualText = constData_Array(&attrText.visual); 1390 const iChar *visualText = constData_Array(&attrText.visual);
@@ -1423,8 +1426,9 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1423 iRangei wrapPosRange = { 0, textLen }; 1426 iRangei wrapPosRange = { 0, textLen };
1424 int wrapResumePos = textLen; /* logical position where next line resumes */ 1427 int wrapResumePos = textLen; /* logical position where next line resumes */
1425 size_t wrapResumeRunIndex = runCount; /* index of run where next line resumes */ 1428 size_t wrapResumeRunIndex = runCount; /* index of run where next line resumes */
1426 const int layoutBound = (args->wrap ? args->wrap->maxWidth : 0); 1429 const int layoutBound = (wrap ? wrap->maxWidth : 0);
1427 iBool isFirst = iTrue; 1430 iBool isFirst = iTrue;
1431 const iBool checkHitPoint = wrap && !isEqual_I2(wrap->hitPoint, zero_I2());
1428 while (!isEmpty_Range(&wrapRuns)) { 1432 while (!isEmpty_Range(&wrapRuns)) {
1429 if (isFirst) { 1433 if (isFirst) {
1430 isFirst = iFalse; 1434 isFirst = iFalse;
@@ -1434,7 +1438,9 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1434 } 1438 }
1435 float wrapAdvance = 0.0f; 1439 float wrapAdvance = 0.0f;
1436 /* First we need to figure out how much text fits on the current line. */ 1440 /* First we need to figure out how much text fits on the current line. */
1437 if (args->wrap && args->wrap->maxWidth > 0) { 1441 if (wrap && (wrap->maxWidth > 0 || checkHitPoint)) {
1442 const iBool isHitPointOnThisLine = (checkHitPoint && wrap->hitPoint.y >= yCursor &&
1443 wrap->hitPoint.y < yCursor + d->height);
1438 float breakAdvance = -1.0f; 1444 float breakAdvance = -1.0f;
1439 iAssert(wrapPosRange.end == textLen); 1445 iAssert(wrapPosRange.end == textLen);
1440 /* Determine ends of wrapRuns and wrapVisRange. */ 1446 /* Determine ends of wrapRuns and wrapVisRange. */
@@ -1463,6 +1469,11 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1463 if (logPos < wrapPosRange.start || logPos >= wrapPosRange.end) { 1469 if (logPos < wrapPosRange.start || logPos >= wrapPosRange.end) {
1464 continue; 1470 continue;
1465 } 1471 }
1472 /* Check if the hit point is on the left side of this line. */
1473 if (isHitPointOnThisLine && !wrap->hitChar_out && wrap->hitPoint.x < orig.x) {
1474 wrap->hitChar_out = sourcePtr_AttributedText_(&attrText, logPos);
1475 wrap->hitGlyphNormX_out = 0.0f;
1476 }
1466 const iGlyph *glyph = glyphByIndex_Font_(run->font, glyphId); 1477 const iGlyph *glyph = glyphByIndex_Font_(run->font, glyphId);
1467 const int glyphFlags = hb_glyph_info_get_glyph_flags(info); 1478 const int glyphFlags = hb_glyph_info_get_glyph_flags(info);
1468 const float xOffset = run->font->xScale * buf->glyphPos[i].x_offset; 1479 const float xOffset = run->font->xScale * buf->glyphPos[i].x_offset;
@@ -1489,8 +1500,16 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1489 breakAdvance = wrapAdvance; 1500 breakAdvance = wrapAdvance;
1490 //} 1501 //}
1491 } 1502 }
1503 if (isHitPointOnThisLine) {
1504 if (wrap->hitPoint.x >= orig.x + wrapAdvance &&
1505 wrap->hitPoint.x < orig.x + wrapAdvance + xAdvance) {
1506 wrap->hitChar_out = sourcePtr_AttributedText_(&attrText, logPos);
1507 wrap->hitGlyphNormX_out = (wrap->hitPoint.x - wrapAdvance) / xAdvance;
1508 }
1509 }
1492 /* Out of room? */ 1510 /* Out of room? */
1493 if (wrapAdvance + xOffset + glyph->d[0].x + glyph->rect[0].size.x > 1511 if (wrap->maxWidth > 0 &&
1512 wrapAdvance + xOffset + glyph->d[0].x + glyph->rect[0].size.x >
1494 args->wrap->maxWidth) { 1513 args->wrap->maxWidth) {
1495 if (safeBreakPos >= 0) { 1514 if (safeBreakPos >= 0) {
1496 wrapPosRange.end = safeBreakPos; 1515 wrapPosRange.end = safeBreakPos;
@@ -1509,8 +1528,10 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1509 breakAdvance = wrapAdvance; 1528 breakAdvance = wrapAdvance;
1510 } 1529 }
1511 wrapResumePos = wrapPosRange.end; 1530 wrapResumePos = wrapPosRange.end;
1512 while (wrapResumePos < textLen && isSpace_Char(logicalText[wrapResumePos])) { 1531 if (args->wrap->mode != anyCharacter_WrapTextMode) {
1513 wrapResumePos++; /* skip space */ 1532 while (wrapResumePos < textLen && isSpace_Char(logicalText[wrapResumePos])) {
1533 wrapResumePos++; /* skip space */
1534 }
1514 } 1535 }
1515 wrapRuns.end = runIndex + 1; /* still includes this run */ 1536 wrapRuns.end = runIndex + 1; /* still includes this run */
1516 wrapResumeRunIndex = runIndex; /* ...but continue from the same one */ 1537 wrapResumeRunIndex = runIndex; /* ...but continue from the same one */
@@ -1527,6 +1548,11 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1527 } 1548 }
1528 } 1549 }
1529 } 1550 }
1551 if (isHitPointOnThisLine && wrap->hitPoint.x >= orig.x + wrapAdvance) {
1552 /* On the right side. */
1553 wrap->hitChar_out = sourcePtr_AttributedText_(&attrText, iMax(0, wrapResumePos - 1));
1554 wrap->hitGlyphNormX_out = 1.0f;
1555 }
1530 } 1556 }
1531 else { 1557 else {
1532 /* Not wrapped so everything fits! Calculate total advance without wrapping. */ 1558 /* Not wrapped so everything fits! Calculate total advance without wrapping. */
@@ -1589,7 +1615,8 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1589// } 1615// }
1590 } 1616 }
1591 /* Make a callback for each wrapped line. */ 1617 /* Make a callback for each wrapped line. */
1592 if (!notify_WrapText_(args->wrap, 1618 if (wrap && wrap->wrapFunc &&
1619 !notify_WrapText_(args->wrap,
1593 sourcePtr_AttributedText_(&attrText, wrapResumePos), 1620 sourcePtr_AttributedText_(&attrText, wrapResumePos),
1594 origin, 1621 origin,
1595 iRound(wrapAdvance), 1622 iRound(wrapAdvance),
diff --git a/src/ui/text.h b/src/ui/text.h
index d4f820d4..a82da22c 100644
--- a/src/ui/text.h
+++ b/src/ui/text.h
@@ -212,6 +212,10 @@ struct Impl_WrapText {
212 iBool (*wrapFunc)(iWrapText *, iRangecc wrappedText, int origin, int advance, iBool isBaseRTL); 212 iBool (*wrapFunc)(iWrapText *, iRangecc wrappedText, int origin, int advance, iBool isBaseRTL);
213 void * context; 213 void * context;
214 int baseDir; /* set to +1 for LTR, -1 for RTL */ 214 int baseDir; /* set to +1 for LTR, -1 for RTL */
215 iInt2 hitPoint;
216 /* output */
217 const char *hitChar_out;
218 float hitGlyphNormX_out; /* normalized X inside the glyph */
215 /* internal */ 219 /* internal */
216 iRangecc wrapRange_; 220 iRangecc wrapRange_;
217}; 221};