summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-08-19 17:22:07 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-08-19 17:22:07 +0300
commit7c51814e51a67c82391477125ddf360a8300d4e6 (patch)
tree3dc5bd1d030d06ff5baab6832350555e5f19fd5b /src
parent599be9cf0710f6f56133a8f5a9af37fb8ae6f013 (diff)
InputWidget: Cursor behavior around invisible codepoints
Emoji variation selectors would confuse cursor positioning and insertion behavior, breaking the assumption that each line ends with a single newline.
Diffstat (limited to 'src')
-rw-r--r--src/ui/inputwidget.c87
-rw-r--r--src/ui/text.c7
2 files changed, 59 insertions, 35 deletions
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index 9f233345..2f0cabbb 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -360,35 +360,64 @@ static int endX_InputWidget_(const iInputWidget *d, int y) {
360 return line->range.end - (isLastLine_InputWidget_(d, line) ? 0 : 1) - line->range.start; 360 return line->range.end - (isLastLine_InputWidget_(d, line) ? 0 : 1) - line->range.start;
361} 361}
362 362
363static iBool isCursorFocusable_Char_(iChar c) {
364 return !isDefaultIgnorable_Char(c) &&
365 !isVariationSelector_Char(c) &&
366 !isFitzpatrickType_Char(c);
367}
368
369static iChar at_InputWidget_(const iInputWidget *d, iInt2 pos) {
370 if (pos.y >= 0 && pos.y < size_Array(&d->lines) &&
371 pos.x >= 0 && pos.x <= endX_InputWidget_(d, pos.y)) {
372 iChar ch = 0;
373 decodeBytes_MultibyteChar(charPos_InputWidget_(d, pos),
374 constEnd_String(lineString_InputWidget_(d, pos.y)),
375 &ch);
376 return ch;
377 }
378 return ' ';
379}
380
363static iInt2 movedCursor_InputWidget_(const iInputWidget *d, iInt2 pos, int xDir, int yDir) { 381static iInt2 movedCursor_InputWidget_(const iInputWidget *d, iInt2 pos, int xDir, int yDir) {
364 iChar ch; 382 iChar ch = 0;
365 if (xDir < 0) { 383 int n = 0;
366 if (pos.x == 0) { 384 /* TODO: The cursor should never land on any combining codepoints either. */
367 if (pos.y > 0) { 385 for (;;) {
368 pos.x = endX_InputWidget_(d, --pos.y); 386 if (xDir < 0) {
387 if (pos.x == 0) {
388 if (pos.y > 0) {
389 pos.x = endX_InputWidget_(d, --pos.y);
390 }
369 } 391 }
370 } 392 else {
371 else { 393 iAssert(pos.x > 0);
372 iAssert(pos.x > 0); 394 n = decodePrecedingBytes_MultibyteChar(charPos_InputWidget_(d, pos),
373 int n = decodePrecedingBytes_MultibyteChar(charPos_InputWidget_(d, pos),
374 cstr_String(lineString_InputWidget_(d, pos.y)), 395 cstr_String(lineString_InputWidget_(d, pos.y)),
375 &ch); 396 &ch);
376 pos.x -= n; 397 pos.x -= n;
377 } 398 if (!isCursorFocusable_Char_(at_InputWidget_(d, pos))) {
378 } 399 continue;
379 else if (xDir > 0) { 400 }
380 if (pos.x == endX_InputWidget_(d, pos.y)) {
381 if (pos.y < size_Array(&d->lines) - 1) {
382 pos.y++;
383 pos.x = 0;
384 } 401 }
385 } 402 }
386 else { 403 else if (xDir > 0) {
387 int n = decodeBytes_MultibyteChar(charPos_InputWidget_(d, pos), 404 if (pos.x == endX_InputWidget_(d, pos.y)) {
405 if (pos.y < size_Array(&d->lines) - 1) {
406 pos.y++;
407 pos.x = 0;
408 }
409 }
410 else {
411 n = decodeBytes_MultibyteChar(charPos_InputWidget_(d, pos),
388 constEnd_String(lineString_InputWidget_(d, pos.y)), 412 constEnd_String(lineString_InputWidget_(d, pos.y)),
389 &ch); 413 &ch);
390 pos.x += n; 414 pos.x += n;
415 if (!isCursorFocusable_Char_(at_InputWidget_(d, pos))) {
416 continue;
417 }
418 }
391 } 419 }
420 break;
392 } 421 }
393 return pos; 422 return pos;
394} 423}
@@ -1026,7 +1055,9 @@ static void insertRange_InputWidget_(iInputWidget *d, iRangecc range) {
1026 cstr_String(&line->text) + d->cursor.x, constEnd_String(&line->text) 1055 cstr_String(&line->text) + d->cursor.x, constEnd_String(&line->text)
1027 }); 1056 });
1028 truncate_String(&line->text, d->cursor.x); 1057 truncate_String(&line->text, d->cursor.x);
1029 appendCStr_String(&line->text, "\n"); 1058 if (!endsWith_String(&line->text, "\n")) {
1059 appendCStr_String(&line->text, "\n");
1060 }
1030 insert_Array(&d->lines, ++d->cursor.y, &split); 1061 insert_Array(&d->lines, ++d->cursor.y, &split);
1031 d->cursor.x = 0; 1062 d->cursor.x = 0;
1032 } 1063 }
@@ -1059,6 +1090,8 @@ void setCursor_InputWidget(iInputWidget *d, iInt2 pos) {
1059 iAssert(!isEmpty_Array(&d->lines)); 1090 iAssert(!isEmpty_Array(&d->lines));
1060 pos.x = iClamp(pos.x, 0, endX_InputWidget_(d, pos.y)); 1091 pos.x = iClamp(pos.x, 0, endX_InputWidget_(d, pos.y));
1061 d->cursor = pos; 1092 d->cursor = pos;
1093 iChar ch = at_InputWidget_(d, pos);
1094 printf("cursor x:%d ch:%08x (%lc)\n", pos.x, ch, (int)ch);
1062 /* Update selection. */ 1095 /* Update selection. */
1063 if (isMarking_()) { 1096 if (isMarking_()) {
1064 if (isEmpty_Range(&d->mark)) { 1097 if (isEmpty_Range(&d->mark)) {
@@ -1221,18 +1254,6 @@ static iBool deleteMarked_InputWidget_(iInputWidget *d) {
1221 return iFalse; 1254 return iFalse;
1222} 1255}
1223 1256
1224static iChar at_InputWidget_(const iInputWidget *d, iInt2 pos) {
1225 if (pos.y >= 0 && pos.y < size_Array(&d->lines) &&
1226 pos.x >= 0 && pos.x <= endX_InputWidget_(d, pos.y)) {
1227 iChar ch = 0;
1228 decodeBytes_MultibyteChar(charPos_InputWidget_(d, pos),
1229 constEnd_String(lineString_InputWidget_(d, pos.y)),
1230 &ch);
1231 return ch;
1232 }
1233 return ' ';
1234}
1235
1236static iBool isWordChar_InputWidget_(const iInputWidget *d, iInt2 pos) { 1257static iBool isWordChar_InputWidget_(const iInputWidget *d, iInt2 pos) {
1237 return isAlphaNumeric_Char(at_InputWidget_(d, pos)); 1258 return isAlphaNumeric_Char(at_InputWidget_(d, pos));
1238} 1259}
diff --git a/src/ui/text.c b/src/ui/text.c
index 0a8386e4..639d8f13 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -1433,6 +1433,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1433 iBool isFirst = iTrue; 1433 iBool isFirst = iTrue;
1434 const iBool checkHitPoint = wrap && !isEqual_I2(wrap->hitPoint, zero_I2()); 1434 const iBool checkHitPoint = wrap && !isEqual_I2(wrap->hitPoint, zero_I2());
1435 const iBool checkHitChar = wrap && wrap->hitChar; 1435 const iBool checkHitChar = wrap && wrap->hitChar;
1436 iBool wasCharHit = iFalse;
1436 while (!isEmpty_Range(&wrapRuns)) { 1437 while (!isEmpty_Range(&wrapRuns)) {
1437 if (isFirst) { 1438 if (isFirst) {
1438 isFirst = iFalse; 1439 isFirst = iFalse;
@@ -1452,9 +1453,10 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1452 for (size_t runIndex = wrapRuns.start; runIndex < wrapRuns.end; runIndex++) { 1453 for (size_t runIndex = wrapRuns.start; runIndex < wrapRuns.end; runIndex++) {
1453 const iAttributedRun *run = at_Array(&attrText.runs, runIndex); 1454 const iAttributedRun *run = at_Array(&attrText.runs, runIndex);
1454 if (run->flags.isLineBreak) { 1455 if (run->flags.isLineBreak) {
1455 if (checkHitChar) { 1456 if (checkHitChar && !wasCharHit) {
1456 if (wrap->hitChar == sourcePtr_AttributedText_(&attrText, run->logical.start)) { 1457 if (wrap->hitChar == sourcePtr_AttributedText_(&attrText, run->logical.start)) {
1457 wrap->hitAdvance_out = init_I2(wrapAdvance, yCursor); 1458 wrap->hitAdvance_out = init_I2(wrapAdvance, yCursor);
1459 wasCharHit = iTrue;
1458 } 1460 }
1459 } 1461 }
1460 wrapPosRange.end = run->logical.start; 1462 wrapPosRange.end = run->logical.start;
@@ -1479,9 +1481,10 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1479 if (logPos < wrapPosRange.start || logPos >= wrapPosRange.end) { 1481 if (logPos < wrapPosRange.start || logPos >= wrapPosRange.end) {
1480 continue; 1482 continue;
1481 } 1483 }
1482 if (checkHitChar) { 1484 if (checkHitChar && !wasCharHit) {
1483 if (wrap->hitChar == sourcePtr_AttributedText_(&attrText, logPos)) { 1485 if (wrap->hitChar == sourcePtr_AttributedText_(&attrText, logPos)) {
1484 wrap->hitAdvance_out = init_I2(wrapAdvance, yCursor); 1486 wrap->hitAdvance_out = init_I2(wrapAdvance, yCursor);
1487 wasCharHit = iTrue;
1485 } 1488 }
1486 } 1489 }
1487 /* Check if the hit point is on the left side of this line. */ 1490 /* Check if the hit point is on the left side of this line. */