summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ui/inputwidget.c510
-rw-r--r--src/ui/inputwidget.h2
-rw-r--r--src/ui/text.c17
-rw-r--r--src/ui/uploadwidget.c1
4 files changed, 385 insertions, 145 deletions
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index 3aec99c9..a5e8cf43 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -58,17 +58,19 @@ static void enableEditorKeysInMenus_(iBool enable) {
58iDeclareType(InputLine) 58iDeclareType(InputLine)
59 59
60struct Impl_InputLine { 60struct Impl_InputLine {
61 //size_t offset; /* character position from the beginning */
62 //size_t len; /* length as characters */
63 iRanges range; /* byte offset inside the entire content; for marking */
64 iString text; /* UTF-8 */ 61 iString text; /* UTF-8 */
65 int numVisLines; /* wrapped lines */ 62 iRanges range; /* byte offset inside the entire content; for marking */
63 iRangei wrapLines; /* range of visual wrapped lines */
66}; 64};
67 65
68static void init_InputLine(iInputLine *d) { 66static void init_InputLine(iInputLine *d) {
69 iZap(d->range); 67 iZap(d->range);
70 init_String(&d->text); 68 init_String(&d->text);
71 d->numVisLines = 1; 69 d->wrapLines = (iRangei){ 0, 1 };
70}
71
72iLocalDef int numWrapLines_InputLine_(const iInputLine *d) {
73 return size_Range(&d->wrapLines);
72} 74}
73 75
74static void deinit_InputLine(iInputLine *d) { 76static void deinit_InputLine(iInputLine *d) {
@@ -194,9 +196,10 @@ struct Impl_InputWidget {
194 iInt2 cursor; /* cursor position: x = byte offset, y = line index */ 196 iInt2 cursor; /* cursor position: x = byte offset, y = line index */
195 iInt2 prevCursor; /* previous cursor position */ 197 iInt2 prevCursor; /* previous cursor position */
196// int verticalMoveX; 198// int verticalMoveX;
197 iRanges visLines; /* which lines are current visible */ 199 iRangei visWrapLines; /* which wrap lines are current visible */
198 int visLineOffsetY; /* vertical offset of first visible line */ 200// int visLineOffsetY; /* vertical offset of first visible line (pixels) */
199 int minVisLines, maxVisLines; /* min/max number of visible lines allowed */ 201 int minWrapLines, maxWrapLines; /* min/max number of visible lines allowed */
202// int scrollY; /* wrap lines */
200 iRanges mark; 203 iRanges mark;
201 iRanges initialMark; 204 iRanges initialMark;
202 iArray undoStack; 205 iArray undoStack;
@@ -265,13 +268,17 @@ static void updateCursorLine_InputWidget_(iInputWidget *d) {
265} 268}
266#endif 269#endif
267 270
268static int numContentLines_InputWidget_(const iInputWidget *d) { 271iLocalDef iBool isLastLine_InputWidget_(const iInputWidget *d, const iInputLine *line) {
269 int num = 0; 272 return (const void *) line == constBack_Array(&d->lines);
270 iConstForEach(Array, i, &d->lines) { 273}
271 const iInputLine *line = i.value; 274
272 num += line->numVisLines; 275iLocalDef const iInputLine *lastLine_InputWidget_(const iInputWidget *d) {
273 } 276 iAssert(!isEmpty_Array(&d->lines));
274 return num; 277 return constBack_Array(&d->lines);
278}
279
280static int numWrapLines_InputWidget_(const iInputWidget *d) {
281 return lastLine_InputWidget_(d)->wrapLines.end;
275} 282}
276 283
277static const iInputLine *line_InputWidget_(const iInputWidget *d, size_t index) { 284static const iInputLine *line_InputWidget_(const iInputWidget *d, size_t index) {
@@ -279,8 +286,12 @@ static const iInputLine *line_InputWidget_(const iInputWidget *d, size_t index)
279 return constAt_Array(&d->lines, index); 286 return constAt_Array(&d->lines, index);
280} 287}
281 288
282iLocalDef iBool isLastLine_InputWidget_(const iInputWidget *d, const iInputLine *line) { 289static const iString *lineString_InputWidget_(const iInputWidget *d, int y) {
283 return (const void *) line == constBack_Array(&d->lines); 290 return &line_InputWidget_(d, y)->text;
291}
292
293static const char *charPos_InputWidget_(const iInputWidget *d, iInt2 pos) {
294 return cstr_String(lineString_InputWidget_(d, pos.y)) + pos.x;
284} 295}
285 296
286static int endX_InputWidget_(const iInputWidget *d, int y) { 297static int endX_InputWidget_(const iInputWidget *d, int y) {
@@ -289,25 +300,63 @@ static int endX_InputWidget_(const iInputWidget *d, int y) {
289 return line->range.end - (isLastLine_InputWidget_(d, line) ? 0 : 1) - line->range.start; 300 return line->range.end - (isLastLine_InputWidget_(d, line) ? 0 : 1) - line->range.start;
290} 301}
291 302
292static void scrollVisLines_InputWidget_(iInputWidget *d, int delta) { 303static iRangecc rangeSize_String(const iString *d, size_t size) {
293 const int numContentLines = numContentLines_InputWidget_(d); 304 return (iRangecc){
294 d->visLines.start += delta; 305 constBegin_String(d),
295 d->visLines.end += delta; 306 constBegin_String(d) + iMin(size, size_String(d))
296 if (d->visLines.end > numContentLines) { 307 };
297 const int over = d->visLines.end - numContentLines; 308}
298 d->visLines.start -= over; 309
299 d->visLines.end = numContentLines; 310static const iInputLine *findLineByWrapY_InputWidget_(const iInputWidget *d, int wrapY) {
300 } 311 iConstForEach(Array, i, &d->lines) {
301 if (d->visLines.start < 0) { 312 const iInputLine *line = i.value;
302 d->visLines.end -= d->visLines.start; 313 if (contains_Range(&line->wrapLines, wrapY)) {
303 d->visLines.start = 0; 314 return line;
315 }
304 } 316 }
317 iAssert(iFalse); /* wrap y is out of bounds */
318 return wrapY < 0 ? constFront_Array(&d->lines) : constBack_Array(&d->lines);
319}
320
321static int visLineOffsetY_InputWidget_(const iInputWidget *d) {
322 const iInputLine *line = findLineByWrapY_InputWidget_(d, d->visWrapLines.start);
323 return (line->wrapLines.start - d->visWrapLines.start) * lineHeight_Text(d->font);
305} 324}
306 325
307static void updateVisible_InputWidget_(iInputWidget *d) { 326static void updateVisible_InputWidget_(iInputWidget *d) {
308 const int numContentLines = numContentLines_InputWidget_(d); 327 const int totalWraps = numWrapLines_InputWidget_(d);
328 const int visWraps = iClamp(totalWraps, d->minWrapLines, d->maxWrapLines);
329 /* Resize the height of the editor. */
330 d->visWrapLines.end = d->visWrapLines.start + visWraps;
331 /* Determine which wraps are currently visible. */
332 const iInputLine *curLine = constAt_Array(&d->lines, d->cursor.y);
333 const int cursorY = curLine->wrapLines.start +
334 measureRange_Text(d->font, rangeSize_String(&curLine->text, d->cursor.x))
335 .advance.y / lineHeight_Text(d->font);
336
337 //int scrollY = d->visLineOffsetY / lineHeight_Text(d->font);
338 /* Scroll to cursor. */
339 int delta = 0;
340 if (d->visWrapLines.end < cursorY + 1) {
341 delta = cursorY + 1 - d->visWrapLines.end;
342 }
343 else if (cursorY < d->visWrapLines.start) {
344 delta = cursorY - d->visWrapLines.start;
345 }
346 d->visWrapLines.start += delta;
347 d->visWrapLines.end += delta;
348 iAssert(contains_Range(&d->visWrapLines, cursorY));
349// iAssert(d->visWrapLines.start >= 0);
350// iAssert(d->visWrapLines.end <= totalWraps);
351
352// d->visLineOffsetY = scrollY * lineHeight_Text(d->font);
353#if 0
354// const int numContentLines = numContentLines_InputWidget_(d);
309 /* Expand or shrink the number of visible lines depending on how much content is available. */ 355 /* Expand or shrink the number of visible lines depending on how much content is available. */
310 d->visLines.end = d->visLines.start + iClamp(numContentLines, d->minVisLines, d->maxVisLines); 356 //d->visLines.end = d->visLines.start + iClamp(numContentLines, d->minVisLines, d->maxVisLines);
357 const int maxVisLines = iMin(d->maxVisLines, size_Array(&d->lines));
358 d->visLines.end = d->visLines.start;
359
311 if (d->visLines.end > numContentLines) { 360 if (d->visLines.end > numContentLines) {
312 const int avail = d->visLines.start; 361 const int avail = d->visLines.start;
313 int offset = iMin(avail, d->visLines.end - numContentLines); 362 int offset = iMin(avail, d->visLines.end - numContentLines);
@@ -321,6 +370,7 @@ static void updateVisible_InputWidget_(iInputWidget *d) {
321 if (d->cursor.y > d->visLines.end - 1) { 370 if (d->cursor.y > d->visLines.end - 1) {
322 scrollVisLines_InputWidget_(d, d->cursor.y - d->visLines.end - 1); 371 scrollVisLines_InputWidget_(d, d->cursor.y - d->visLines.end - 1);
323 } 372 }
373#endif
324} 374}
325 375
326static void showCursor_InputWidget_(iInputWidget *d) { 376static void showCursor_InputWidget_(iInputWidget *d) {
@@ -448,7 +498,7 @@ static void updateLines_InputWidget_(iInputWidget *d) {
448#endif 498#endif
449 499
450static int contentHeight_InputWidget_(const iInputWidget *d) { 500static int contentHeight_InputWidget_(const iInputWidget *d) {
451 return size_Range(&d->visLines) * lineHeight_Text(d->font); 501 return size_Range(&d->visWrapLines) * lineHeight_Text(d->font);
452} 502}
453 503
454#if 0 504#if 0
@@ -477,6 +527,7 @@ static void updateMetrics_InputWidget_(iInputWidget *d) {
477} 527}
478 528
479static void updateLine_InputWidget_(iInputWidget *d, iInputLine *line) { 529static void updateLine_InputWidget_(iInputWidget *d, iInputLine *line) {
530 iAssert(endsWith_String(&line->text, "\n") || isLastLine_InputWidget_(d, line));
480 iWrapText wrapText = { 531 iWrapText wrapText = {
481 .text = range_String(&line->text), 532 .text = range_String(&line->text),
482 .maxWidth = width_Rect(contentBounds_InputWidget_(d)), 533 .maxWidth = width_Rect(contentBounds_InputWidget_(d)),
@@ -484,19 +535,19 @@ static void updateLine_InputWidget_(iInputWidget *d, iInputLine *line) {
484 : word_WrapTextMode), 535 : word_WrapTextMode),
485 }; 536 };
486 if (wrapText.maxWidth <= 0) { 537 if (wrapText.maxWidth <= 0) {
487 line->numVisLines = 1; 538 line->wrapLines.end = line->wrapLines.start + 1;
488 return; 539 return;
489 } 540 }
490 const iTextMetrics tm = measure_WrapText(&wrapText, d->font); 541 const iTextMetrics tm = measure_WrapText(&wrapText, d->font);
491 line->numVisLines = height_Rect(tm.bounds) / lineHeight_Text(d->font); 542 line->wrapLines.end = line->wrapLines.start + height_Rect(tm.bounds) / lineHeight_Text(d->font);
492} 543}
493 544
494static void updateAllLinesAndResizeHeight_InputWidget_(iInputWidget *d) { 545static void updateAllLinesAndResizeHeight_InputWidget_(iInputWidget *d) {
495 const int oldCount = numContentLines_InputWidget_(d); 546 const int oldWraps = numWrapLines_InputWidget_(d);
496 iForEach(Array, i, &d->lines) { 547 iForEach(Array, i, &d->lines) {
497 updateLine_InputWidget_(d, i.value); /* count number of visible lines */ 548 updateLine_InputWidget_(d, i.value); /* count number of visible lines */
498 } 549 }
499 if (oldCount != numContentLines_InputWidget_(d)) { 550 if (oldWraps != numWrapLines_InputWidget_(d)) {
500// d->click.minHeight = contentHeight_InputWidget_(d, iFalse); 551// d->click.minHeight = contentHeight_InputWidget_(d, iFalse);
501 updateMetrics_InputWidget_(d); 552 updateMetrics_InputWidget_(d);
502 } 553 }
@@ -533,11 +584,11 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) {
533 } 584 }
534 iZap(d->mark); 585 iZap(d->mark);
535 setMaxLen_InputWidget(d, maxLen); 586 setMaxLen_InputWidget(d, maxLen);
536 d->visLines.start = 0; 587 d->visWrapLines.start = 0;
537 d->visLines.end = 1; 588 d->visWrapLines.end = 1;
538 d->visLineOffsetY = 0; 589// d->visLineOffsetY = 0;
539 d->maxVisLines = maxLen > 0 ? 1 : 20; /* TODO: Choose maximum dynamically? */ 590 d->maxWrapLines = maxLen > 0 ? 1 : 20; /* TODO: Choose maximum dynamically? */
540 d->minVisLines = 1; 591 d->minWrapLines = 1;
541 splitToLines_(&iStringLiteral(""), &d->lines); 592 splitToLines_(&iStringLiteral(""), &d->lines);
542 //d->maxLayoutLines = iInvalidSize; 593 //d->maxLayoutLines = iInvalidSize;
543 setFlags_Widget(w, fixedHeight_WidgetFlag, iTrue); /* resizes its own height */ 594 setFlags_Widget(w, fixedHeight_WidgetFlag, iTrue); /* resizes its own height */
@@ -641,9 +692,9 @@ void setMaxLen_InputWidget(iInputWidget *d, size_t maxLen) {
641 updateSizeForFixedLength_InputWidget_(d); 692 updateSizeForFixedLength_InputWidget_(d);
642} 693}
643 694
644void setLineLimits_InputWidget(iInputWidget *d, int minVis, int maxVis) { 695void setLineLimits_InputWidget(iInputWidget *d, int minLines, int maxLines) {
645 d->minVisLines = minVis; 696 d->minWrapLines = minLines;
646 d->maxVisLines = maxVis; 697 d->maxWrapLines = maxLines;
647 updateVisible_InputWidget_(d); 698 updateVisible_InputWidget_(d);
648 updateMetrics_InputWidget_(d); 699 updateMetrics_InputWidget_(d);
649} 700}
@@ -715,14 +766,9 @@ iLocalDef const iInputLine *constCursorLine_InputWidget_(const iInputWidget *d)
715 return constAt_Array(&d->lines, d->cursor.y); 766 return constAt_Array(&d->lines, d->cursor.y);
716} 767}
717 768
718iLocalDef const iInputLine *lastLine_InputWidget_(const iInputWidget *d) {
719 iAssert(!isEmpty_Array(&d->lines));
720 return constBack_Array(&d->lines);
721}
722
723iLocalDef iInt2 cursorMax_InputWidget_(const iInputWidget *d) { 769iLocalDef iInt2 cursorMax_InputWidget_(const iInputWidget *d) {
724 //return iMin(size_Array(&d->text), d->maxLen - 1); 770 const int yLast = size_Array(&d->lines) - 1;
725 return init_I2(lastLine_InputWidget_(d)->range.end, size_Array(&d->lines) - 1); 771 return init_I2(endX_InputWidget_(d, yLast), yLast);
726} 772}
727 773
728void setText_InputWidget(iInputWidget *d, const iString *text) { 774void setText_InputWidget(iInputWidget *d, const iString *text) {
@@ -891,15 +937,62 @@ void end_InputWidget(iInputWidget *d, iBool accept) {
891 accept ? 1 : 0); 937 accept ? 1 : 0);
892} 938}
893 939
940static void updateLineRangesStartingFrom_InputWidget_(iInputWidget *d, int y) {
941 iInputLine *line = at_Array(&d->lines, y);
942 line->range.end = line->range.start + size_String(&line->text);
943 for (size_t i = y + 1; i < size_Array(&d->lines); i++) {
944 iInputLine *next = at_Array(&d->lines, i);
945 next->range.start = line->range.end;
946 next->range.end = next->range.start + size_String(&next->text);
947 /* Update wrap line range as well. */
948 next->wrapLines = (iRangei){
949 line->wrapLines.end,
950 line->wrapLines.end + numWrapLines_InputLine_(next)
951 };
952 line = next;
953 }
954}
955
956static void textOfLinesWasChanged_InputWidget_(iInputWidget *d, iRangei lineRange) {
957 for (int i = lineRange.start; i < lineRange.end; i++) {
958 updateLine_InputWidget_(d, at_Array(&d->lines, i));
959 }
960 updateLineRangesStartingFrom_InputWidget_(d, lineRange.start);
961 updateVisible_InputWidget_(d);
962 updateMetrics_InputWidget_(d);
963}
964
894static void insertRange_InputWidget_(iInputWidget *d, iRangecc range) { 965static void insertRange_InputWidget_(iInputWidget *d, iRangecc range) {
895 iWidget *w = as_Widget(d); 966 iWidget *w = as_Widget(d);
896 /* TODO: If there's a newline, we'll need to break and insert a new line. */ 967 iRangecc nextRange = { range.end, range.end };
897 iAssert(lastIndexOfCStr_Rangecc(range, "\n") == iInvalidPos); 968 const int firstModified = d->cursor.y;
898 iInputLine *line = cursorLine_InputWidget_(d); 969 for (;; range = nextRange) {
899 if (d->mode == insert_InputMode) { 970 /* If there's a newline, we'll need to break and begin a new line. */
900 insertData_Block(&line->text.chars, d->cursor.x, range.start, size_Range(&range)); 971 const char *newline = iStrStrN(range.start, "\n", size_Range(&range));
901 d->cursor.x += size_Range(&range); 972 if (newline) {
902 } 973 nextRange = (iRangecc){ newline + 1, range.end };
974 range.end = newline;
975 }
976 iInputLine *line = cursorLine_InputWidget_(d);
977 if (d->mode == insert_InputMode) {
978 insertData_Block(&line->text.chars, d->cursor.x, range.start, size_Range(&range));
979 d->cursor.x += size_Range(&range);
980 }
981 if (!newline) {
982 break;
983 }
984 /* Split current line into a new line. */
985 iInputLine split;
986 init_InputLine(&split);
987 setRange_String(&split.text, (iRangecc){
988 cstr_String(&line->text) + d->cursor.x, constEnd_String(&line->text)
989 });
990 truncate_String(&line->text, d->cursor.x);
991 appendCStr_String(&line->text, "\n");
992 insert_Array(&d->lines, ++d->cursor.y, &split);
993 d->cursor.x = 0;
994 }
995 textOfLinesWasChanged_InputWidget_(d, (iRangei){ firstModified, d->cursor.y + 1 });
903#if 0 996#if 0
904 else if (d->maxLen == 0 || d->cursor < d->maxLen) { 997 else if (d->maxLen == 0 || d->cursor < d->maxLen) {
905 if (d->cursor >= size_Array(&d->text)) { 998 if (d->cursor >= size_Array(&d->text)) {
@@ -949,6 +1042,7 @@ void setCursor_InputWidget(iInputWidget *d, iInt2 pos) {
949 showCursor_InputWidget_(d); 1042 showCursor_InputWidget_(d);
950} 1043}
951 1044
1045#if 0
952static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const iInputLine *line) { 1046static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const iInputLine *line) {
953 size_t index = line->range.start; 1047 size_t index = line->range.start;
954 if (x <= 0) { 1048 if (x <= 0) {
@@ -977,8 +1071,67 @@ static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const
977#endif 1071#endif
978 return iMax(index, line->range.start); 1072 return iMax(index, line->range.start);
979} 1073}
1074#endif
1075
1076iDeclareType(LineMover)
1077
1078struct Impl_LineMover {
1079 iInputWidget *d;
1080 int dir;
1081 int x;
1082};
1083
1084static iBool findXBreaks_LineMover_(iWrapText *wrap, iRangecc wrappedText,
1085 int origin, int advance, iBool isBaseRTL) {
1086 iUnused(isBaseRTL);
1087
1088}
980 1089
981static iBool moveCursorByLine_InputWidget_(iInputWidget *d, int dir) { 1090static iBool moveCursorByLine_InputWidget_(iInputWidget *d, int dir) {
1091 const iInputLine *line = cursorLine_InputWidget_(d);
1092 iRangecc text = range_String(&line->text);
1093 iInt2 relCoord = measureRange_Text(d->font, (iRangecc){ text.start,
1094 text.start + d->cursor.x }).advance;
1095 const int cursorWidth = measureN_Text(d->font, charPos_InputWidget_(d, d->cursor), 1).advance.x;
1096 int relLine = relCoord.y / lineHeight_Text(d->font);
1097 if ((dir < 0 && relLine > 0) || (dir > 0 && relLine < numWrapLines_InputLine_(line) - 1)) {
1098 /* We can just change the cursor X value, but we'll have to check where the cursor lands. */
1099 relCoord.y += dir * lineHeight_Text(d->font);
1100 }
1101 else if (dir < 0 && d->cursor.y > 0) {
1102 d->cursor.y--;
1103 line = cursorLine_InputWidget_(d);
1104 relCoord.y = lineHeight_Text(d->font) * (numWrapLines_InputLine_(line) - 1);
1105 }
1106 else if (dir > 0 && d->cursor.y < size_Array(&d->lines) - 1) {
1107 d->cursor.y++;
1108 line = cursorLine_InputWidget_(d);
1109 relCoord.y = 0;
1110 }
1111 else {
1112 return iFalse;
1113 }
1114 iWrapText wrapText = {
1115 .text = range_String(&cursorLine_InputWidget_(d)->text),
1116 .maxWidth = width_Rect(contentBounds_InputWidget_(d)),
1117 .mode = (d->inFlags & isUrl_InputWidgetFlag ? anyCharacter_WrapTextMode
1118 : word_WrapTextMode),
1119 .hitPoint = addY_I2(relCoord, 1), //arelCddX_I2(relCoord, cursorWidth),
1120 };
1121 measure_WrapText(&wrapText, d->font);
1122 iAssert(wrapText.hitChar_out);
1123 d->cursor.x = wrapText.hitChar_out - wrapText.text.start;
1124 /*
1125 if (wrapText.hitGlyphNormX_out > 0.5f && d->cursor.x < endX_InputWidget_(d, d->cursor.y)) {
1126 iChar ch;
1127 int n = decodeBytes_MultibyteChar(wrapText.text.start + d->cursor.x, wrapText.text.end, &ch);
1128 if (n > 0) {
1129 d->cursor.x += n;
1130 }
1131 }*/
1132 showCursor_InputWidget_(d);
1133 return iTrue;
1134
982#if 0 1135#if 0
983 const iInputLine *line = line_InputWidget_(d, d->cursorLine); 1136 const iInputLine *line = line_InputWidget_(d, d->cursorLine);
984 int xPos1 = maxWidth_TextMetrics(measureN_Text(d->font, 1137 int xPos1 = maxWidth_TextMetrics(measureN_Text(d->font,
@@ -1043,27 +1196,75 @@ static void contentsWereChanged_InputWidget_(iInputWidget *d) {
1043 } 1196 }
1044} 1197}
1045 1198
1199static void deleteIndexRange_InputWidget_(iInputWidget *d, iRanges deleted) {
1200 size_t firstModified = iInvalidPos;
1201 for (size_t i = 0; i < size_Array(&d->lines); i++) {
1202 iInputLine *line = at_Array(&d->lines, i);
1203 if (line->range.end < deleted.start) {
1204 continue;
1205 }
1206 if (line->range.start >= deleted.end) {
1207 break;
1208 }
1209 if (firstModified == iInvalidPos) {
1210 firstModified = i;
1211 }
1212 if (line->range.start >= deleted.start && line->range.end <= deleted.end) {
1213 /* Delete the entire line. */
1214 deinit_InputLine(line);
1215 remove_Array(&d->lines, i--);
1216 continue;
1217 }
1218 else if (deleted.start > line->range.start && deleted.end >= line->range.end) {
1219 truncate_Block(&line->text.chars, deleted.start - line->range.start);
1220 }
1221 else if (deleted.start <= line->range.start && deleted.end < line->range.end) {
1222 remove_Block(&line->text.chars, 0, deleted.end - line->range.start);
1223 }
1224 else if (deleted.start > line->range.start && deleted.end < line->range.end) {
1225 remove_Block(&line->text.chars, deleted.start - line->range.start, size_Range(&deleted));
1226 }
1227 else {
1228 iAssert(iFalse); /* all cases exhausted */
1229 }
1230 if (i + 1 < size_Array(&d->lines) && !endsWith_String(&line->text, "\n")) {
1231 /* Newline deleted, so merge with next line. */
1232 iInputLine *nextLine = at_Array(&d->lines, i + 1);
1233 append_String(&line->text, &nextLine->text);
1234 deinit_InputLine(nextLine);
1235 remove_Array(&d->lines, i + 1);
1236 }
1237 }
1238 if (isEmpty_Array(&d->lines)) {
1239 /* Everything was deleted. */
1240 iInputLine empty;
1241 init_InputLine(&empty);
1242 pushBack_Array(&d->lines, &empty);
1243 }
1244 iZap(d->mark);
1245 /* Update lines. */
1246 if (firstModified != iInvalidPos) {
1247 /* Rewrap the lines that may have been cut in half. */
1248 updateLine_InputWidget_(d, at_Array(&d->lines, firstModified));
1249 if (firstModified + 1 < size_Array(&d->lines)) {
1250 updateLine_InputWidget_(d, at_Array(&d->lines, firstModified + 1));
1251 }
1252 updateLineRangesStartingFrom_InputWidget_(d, firstModified);
1253 }
1254 updateVisible_InputWidget_(d);
1255 updateMetrics_InputWidget_(d);
1256}
1257
1046static iBool deleteMarked_InputWidget_(iInputWidget *d) { 1258static iBool deleteMarked_InputWidget_(iInputWidget *d) {
1047 const iRanges m = mark_InputWidget_(d); 1259 const iRanges m = mark_InputWidget_(d);
1048 if (!isEmpty_Range(&m)) { 1260 if (!isEmpty_Range(&m)) {
1049#if 0 1261 deleteIndexRange_InputWidget_(d, m);
1050 removeRange_Array(&d->text, m);
1051#endif
1052 setCursor_InputWidget(d, indexToCursor_InputWidget_(d, m.start)); 1262 setCursor_InputWidget(d, indexToCursor_InputWidget_(d, m.start));
1053 iZap(d->mark);
1054 return iTrue; 1263 return iTrue;
1055 } 1264 }
1056 return iFalse; 1265 return iFalse;
1057} 1266}
1058 1267
1059static const iString *lineString_InputWidget_(const iInputWidget *d, int y) {
1060 return &line_InputWidget_(d, y)->text;
1061}
1062
1063static const char *charPos_InputWidget_(const iInputWidget *d, iInt2 pos) {
1064 return cstr_String(lineString_InputWidget_(d, pos.y)) + pos.x;
1065}
1066
1067static iChar at_InputWidget_(const iInputWidget *d, iInt2 pos) { 1268static iChar at_InputWidget_(const iInputWidget *d, iInt2 pos) {
1068 if (pos.y >= 0 && pos.y < size_Array(&d->lines) && 1269 if (pos.y >= 0 && pos.y < size_Array(&d->lines) &&
1069 pos.x >= 0 && pos.x <= endX_InputWidget_(d, pos.y)) { 1270 pos.x >= 0 && pos.x <= endX_InputWidget_(d, pos.y)) {
@@ -1156,14 +1357,55 @@ static iInt2 skipWord_InputWidget_(const iInputWidget *d, iInt2 pos, int dir) {
1156 return pos; 1357 return pos;
1157} 1358}
1158 1359
1159static size_t coordIndex_InputWidget_(const iInputWidget *d, iInt2 coord) { 1360static iRangei visibleLineRange_InputWidget_(const iInputWidget *d) {
1160 const iInt2 pos = sub_I2(coord, contentBounds_InputWidget_(d).pos); 1361 iRangei vis = { -1, -1 };
1161 const size_t lineNumber = iMin(iMax(0, pos.y) / lineHeight_Text(d->font), 1362 /* Determine which lines are in the potentially visible range. */
1162 (int) size_Array(&d->lines) - 1); 1363 for (int i = 0; i < size_Array(&d->lines); i++) {
1163 const iInputLine *line = line_InputWidget_(d, lineNumber); 1364 const iInputLine *line = constAt_Array(&d->lines, i);
1164// const char *endPos; 1365 if (vis.start < 0 && line->wrapLines.end > d->visWrapLines.start) {
1165// tryAdvanceNoWrap_Text(d->font, range_String(&line->text), pos.x, &endPos); 1366 vis.start = vis.end = i;
1166 return indexForRelativeX_InputWidget_(d, pos.x, line); 1367 }
1368 if (line->wrapLines.start < d->visWrapLines.end) {
1369 vis.end = i + 1;
1370 }
1371 else break;
1372 }
1373 return vis;
1374}
1375
1376static iInt2 coordCursor_InputWidget_(const iInputWidget *d, iInt2 coord) {
1377 const iRect bounds = contentBounds_InputWidget_(d);
1378 const iInt2 relCoord = sub_I2(coord, addY_I2(topLeft_Rect(bounds),
1379 visLineOffsetY_InputWidget_(d)));
1380 if (relCoord.y < 0) {
1381 return zero_I2();
1382 }
1383 if (relCoord.y >= height_Rect(bounds)) {
1384 return cursorMax_InputWidget_(d);
1385 }
1386 iWrapText wrapText = {
1387 .maxWidth = width_Rect(bounds),
1388 .mode = (d->inFlags & isUrl_InputWidgetFlag ? anyCharacter_WrapTextMode : word_WrapTextMode),
1389 .hitPoint = relCoord,
1390 };
1391 const iRangei visLines = visibleLineRange_InputWidget_(d);
1392 for (size_t y = visLines.start; y < visLines.end; y++) {
1393 wrapText.text = range_String(lineString_InputWidget_(d, y));
1394 measure_WrapText(&wrapText, d->font);
1395 if (wrapText.hitChar_out) {
1396 const char *pos = wrapText.hitChar_out;
1397 /* Cursor is between characters, so jump to next character if halfway there. */
1398 if (wrapText.hitGlyphNormX_out > 0.5f) {
1399 iChar ch;
1400 int n = decodeBytes_MultibyteChar(pos, wrapText.text.end, &ch);
1401 if (n > 0) {
1402 pos += n;
1403 }
1404 }
1405 return init_I2(iMin(pos - wrapText.text.start, endX_InputWidget_(d, y)), y);
1406 }
1407 }
1408 return cursorMax_InputWidget_(d);
1167} 1409}
1168 1410
1169static iBool copy_InputWidget_(iInputWidget *d, iBool doCut) { 1411static iBool copy_InputWidget_(iInputWidget *d, iBool doCut) {
@@ -1216,23 +1458,23 @@ static iRanges lineRange_InputWidget_(const iInputWidget *d) {
1216} 1458}
1217#endif 1459#endif
1218 1460
1219static void extendRange_InputWidget_(iInputWidget *d, size_t *pos, int dir) { 1461static void extendRange_InputWidget_(iInputWidget *d, size_t *index, int dir) {
1220#if 0 1462 iInt2 pos = indexToCursor_InputWidget_(d, *index);
1221 const size_t textLen = size_Array(&d->text); 1463 if (dir < 0) {
1222 if (dir < 0 && *pos > 0) { 1464 while (movePos_InputWidget_(d, &pos, dir)) {
1223 for ((*pos)--; *pos > 0; (*pos)--) { 1465 if (isSelectionBreaking_Char(at_InputWidget_(d, pos))) {
1224 if (isSelectionBreaking_Char(at_InputWidget_(d, *pos))) { 1466 movePos_InputWidget_(d, &pos, +1);
1225 (*pos)++;
1226 break; 1467 break;
1227 } 1468 }
1228 } 1469 }
1229 } 1470 }
1230 if (dir > 0) { 1471 if (dir > 0) {
1231 for (; *pos < textLen && !isSelectionBreaking_Char(at_InputWidget_(d, *pos)); (*pos)++) { 1472 while (!isSelectionBreaking_Char(at_InputWidget_(d, pos)) &&
1232 /* continue */ 1473 movePos_InputWidget_(d, &pos, dir)) {
1474 /* keep going */
1233 } 1475 }
1234 } 1476 }
1235#endif 1477 *index = cursorToIndex_InputWidget_(d, pos);
1236} 1478}
1237 1479
1238static iRect bounds_InputWidget_(const iInputWidget *d) { 1480static iRect bounds_InputWidget_(const iInputWidget *d) {
@@ -1254,18 +1496,8 @@ static iBool contains_InputWidget_(const iInputWidget *d, iInt2 coord) {
1254} 1496}
1255 1497
1256static void lineTextWasChanged_InputWidget_(iInputWidget *d, iInputLine *line) { 1498static void lineTextWasChanged_InputWidget_(iInputWidget *d, iInputLine *line) {
1257 updateLine_InputWidget_(d, line); 1499 const int y = indexOf_Array(&d->lines, line);
1258 /* Update affected ranges. */ { 1500 textOfLinesWasChanged_InputWidget_(d, (iRangei){ y, y + 1 });
1259 line->range.end = line->range.start + size_String(&line->text);
1260 for (size_t i = indexOf_Array(&d->lines, line) + 1; i < size_Array(&d->lines); i++) {
1261 iInputLine *next = at_Array(&d->lines, i);
1262 next->range.start = line->range.end;
1263 next->range.end = next->range.start + size_String(&next->text);
1264 line = next;
1265 }
1266 }
1267 updateVisible_InputWidget_(d);
1268 updateMetrics_InputWidget_(d);
1269} 1501}
1270 1502
1271static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { 1503static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
@@ -1351,11 +1583,13 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1351 break; 1583 break;
1352 case started_ClickResult: { 1584 case started_ClickResult: {
1353 setFocus_Widget(w); 1585 setFocus_Widget(w);
1354#if 0 1586 const iInt2 oldCursor = d->cursor;
1355 const size_t oldCursor = d->cursor; 1587 setCursor_InputWidget(d, coordCursor_InputWidget_(d, pos_Click(&d->click)));
1356 setCursor_InputWidget(d, coordIndex_InputWidget_(d, pos_Click(&d->click)));
1357 if (keyMods_Sym(modState_Keys()) == KMOD_SHIFT) { 1588 if (keyMods_Sym(modState_Keys()) == KMOD_SHIFT) {
1358 d->mark = d->initialMark = (iRanges){ oldCursor, d->cursor }; 1589 d->mark = d->initialMark = (iRanges){
1590 cursorToIndex_InputWidget_(d, oldCursor),
1591 cursorToIndex_InputWidget_(d, d->cursor)
1592 };
1359 d->inFlags |= isMarking_InputWidgetFlag; 1593 d->inFlags |= isMarking_InputWidgetFlag;
1360 } 1594 }
1361 else { 1595 else {
@@ -1364,7 +1598,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1364 d->inFlags &= ~(isMarking_InputWidgetFlag | markWords_InputWidgetFlag); 1598 d->inFlags &= ~(isMarking_InputWidgetFlag | markWords_InputWidgetFlag);
1365 if (d->click.count == 2) { 1599 if (d->click.count == 2) {
1366 d->inFlags |= isMarking_InputWidgetFlag | markWords_InputWidgetFlag; 1600 d->inFlags |= isMarking_InputWidgetFlag | markWords_InputWidgetFlag;
1367 d->mark.start = d->mark.end = d->cursor; 1601 d->mark.start = d->mark.end = cursorToIndex_InputWidget_(d, d->cursor);
1368 extendRange_InputWidget_(d, &d->mark.start, -1); 1602 extendRange_InputWidget_(d, &d->mark.start, -1);
1369 extendRange_InputWidget_(d, &d->mark.end, +1); 1603 extendRange_InputWidget_(d, &d->mark.end, +1);
1370 d->initialMark = d->mark; 1604 d->initialMark = d->mark;
@@ -1374,27 +1608,24 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1374 selectAll_InputWidget(d); 1608 selectAll_InputWidget(d);
1375 } 1609 }
1376 } 1610 }
1377#endif
1378 return iTrue; 1611 return iTrue;
1379 } 1612 }
1380 case aborted_ClickResult: 1613 case aborted_ClickResult:
1381 d->inFlags &= ~isMarking_InputWidgetFlag; 1614 d->inFlags &= ~isMarking_InputWidgetFlag;
1382 return iTrue; 1615 return iTrue;
1383 case drag_ClickResult: 1616 case drag_ClickResult:
1384#if 0 1617 d->cursor = coordCursor_InputWidget_(d, pos_Click(&d->click));
1385 d->cursor = coordIndex_InputWidget_(d, pos_Click(&d->click));
1386 showCursor_InputWidget_(d); 1618 showCursor_InputWidget_(d);
1387 if (~d->inFlags & isMarking_InputWidgetFlag) { 1619 if (~d->inFlags & isMarking_InputWidgetFlag) {
1388 d->inFlags |= isMarking_InputWidgetFlag; 1620 d->inFlags |= isMarking_InputWidgetFlag;
1389 d->mark.start = d->cursor; 1621 d->mark.start = cursorToIndex_InputWidget_(d, d->cursor);
1390 } 1622 }
1391 d->mark.end = d->cursor; 1623 d->mark.end = cursorToIndex_InputWidget_(d, d->cursor);
1392 if (d->inFlags & markWords_InputWidgetFlag) { 1624 if (d->inFlags & markWords_InputWidgetFlag) {
1393 const iBool isFwd = d->mark.end >= d->mark.start; 1625 const iBool isFwd = d->mark.end >= d->mark.start;
1394 extendRange_InputWidget_(d, &d->mark.end, isFwd ? +1 : -1); 1626 extendRange_InputWidget_(d, &d->mark.end, isFwd ? +1 : -1);
1395 d->mark.start = isFwd ? d->initialMark.start : d->initialMark.end; 1627 d->mark.start = isFwd ? d->initialMark.start : d->initialMark.end;
1396 } 1628 }
1397#endif
1398 refresh_Widget(w); 1629 refresh_Widget(w);
1399 return iTrue; 1630 return iTrue;
1400 case finished_ClickResult: 1631 case finished_ClickResult:
@@ -1421,11 +1652,6 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1421 if (ev->type == SDL_KEYUP && isFocused_Widget(w)) { 1652 if (ev->type == SDL_KEYUP && isFocused_Widget(w)) {
1422 return iTrue; 1653 return iTrue;
1423 } 1654 }
1424#if 0
1425 const size_t curMax = cursorMax_InputWidget_(d);
1426 const size_t lineFirst = lineRange.start;
1427 const size_t lineLast = lineRange.end == curMax ? curMax : iMax(lineRange.start, lineRange.end - 1);
1428#endif
1429 const iInt2 curMax = cursorMax_InputWidget_(d); 1655 const iInt2 curMax = cursorMax_InputWidget_(d);
1430 const iInt2 lineFirst = init_I2(0, d->cursor.y); 1656 const iInt2 lineFirst = init_I2(0, d->cursor.y);
1431 const iInt2 lineLast = init_I2(endX_InputWidget_(d, d->cursor.y), d->cursor.y); 1657 const iInt2 lineLast = init_I2(endX_InputWidget_(d, d->cursor.y), d->cursor.y);
@@ -1449,19 +1675,17 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1449 return iTrue; 1675 return iTrue;
1450 } 1676 }
1451 } 1677 }
1452#if 0
1453#if defined (iPlatformApple) 1678#if defined (iPlatformApple)
1454 if (mods == KMOD_PRIMARY || mods == (KMOD_PRIMARY | KMOD_SHIFT)) { 1679 if (mods == KMOD_PRIMARY || mods == (KMOD_PRIMARY | KMOD_SHIFT)) {
1455 switch (key) { 1680 switch (key) {
1456 case SDLK_UP: 1681 case SDLK_UP:
1457 case SDLK_DOWN: 1682 case SDLK_DOWN:
1458 setCursor_InputWidget(d, key == SDLK_UP ? 0 : curMax); 1683 setCursor_InputWidget(d, key == SDLK_UP ? zero_I2() : curMax);
1459 refresh_Widget(d); 1684 refresh_Widget(d);
1460 return iTrue; 1685 return iTrue;
1461 } 1686 }
1462 } 1687 }
1463#endif 1688#endif
1464#endif // 0
1465 d->prevCursor = d->cursor; 1689 d->prevCursor = d->cursor;
1466 switch (key) { 1690 switch (key) {
1467 case SDLK_INSERT: 1691 case SDLK_INSERT:
@@ -1469,7 +1693,6 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1469 paste_InputWidget_(d); 1693 paste_InputWidget_(d);
1470 } 1694 }
1471 return iTrue; 1695 return iTrue;
1472#if 0
1473 case SDLK_RETURN: 1696 case SDLK_RETURN:
1474 case SDLK_KP_ENTER: 1697 case SDLK_KP_ENTER:
1475 if (mods == KMOD_SHIFT || (d->maxLen == 0 && 1698 if (mods == KMOD_SHIFT || (d->maxLen == 0 &&
@@ -1486,12 +1709,10 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1486 setFocus_Widget(NULL); 1709 setFocus_Widget(NULL);
1487 } 1710 }
1488 return iTrue; 1711 return iTrue;
1489#endif
1490 case SDLK_ESCAPE: 1712 case SDLK_ESCAPE:
1491 end_InputWidget(d, iFalse); 1713 end_InputWidget(d, iFalse);
1492 setFocus_Widget(NULL); 1714 setFocus_Widget(NULL);
1493 return (d->inFlags & eatEscape_InputWidgetFlag) != 0; 1715 return (d->inFlags & eatEscape_InputWidgetFlag) != 0;
1494#if 0
1495 case SDLK_BACKSPACE: 1716 case SDLK_BACKSPACE:
1496 if (!isEmpty_Range(&d->mark)) { 1717 if (!isEmpty_Range(&d->mark)) {
1497 pushUndo_InputWidget_(d); 1718 pushUndo_InputWidget_(d);
@@ -1500,24 +1721,30 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1500 } 1721 }
1501 else if (mods & byWord_KeyModifier) { 1722 else if (mods & byWord_KeyModifier) {
1502 pushUndo_InputWidget_(d); 1723 pushUndo_InputWidget_(d);
1503 d->mark.start = d->cursor; 1724 d->mark.start = cursorToIndex_InputWidget_(d, d->cursor);
1504 d->mark.end = skipWord_InputWidget_(d, d->cursor, -1); 1725 d->mark.end = cursorToIndex_InputWidget_(d, skipWord_InputWidget_(d, d->cursor, -1));
1505 deleteMarked_InputWidget_(d); 1726 deleteMarked_InputWidget_(d);
1506 contentsWereChanged_InputWidget_(d); 1727 contentsWereChanged_InputWidget_(d);
1507 } 1728 }
1508 else if (d->cursor > 0) { 1729 else if (!isEqual_I2(d->cursor, zero_I2())) {
1509 pushUndo_InputWidget_(d); 1730 pushUndo_InputWidget_(d);
1510 remove_Array(&d->text, --d->cursor); 1731 d->mark.end = cursorToIndex_InputWidget_(d, d->cursor);
1732 movePos_InputWidget_(d, &d->cursor, -1);
1733 d->mark.start = cursorToIndex_InputWidget_(d, d->cursor);
1734 deleteMarked_InputWidget_(d);
1511 contentsWereChanged_InputWidget_(d); 1735 contentsWereChanged_InputWidget_(d);
1512 } 1736 }
1513 else if (d->cursor == 0 && d->maxLen == 1) { 1737 else if (isEqual_I2(d->cursor, zero_I2()) && d->maxLen == 1) {
1514 pushUndo_InputWidget_(d); 1738 pushUndo_InputWidget_(d);
1515 clear_Array(&d->text); 1739 iInputLine *line = cursorLine_InputWidget_(d);
1740 clear_String(&line->text);
1741 lineTextWasChanged_InputWidget_(d, line);
1516 contentsWereChanged_InputWidget_(d); 1742 contentsWereChanged_InputWidget_(d);
1517 } 1743 }
1518 showCursor_InputWidget_(d); 1744 showCursor_InputWidget_(d);
1519 refresh_Widget(w); 1745 refresh_Widget(w);
1520 return iTrue; 1746 return iTrue;
1747#if 0
1521 case SDLK_d: 1748 case SDLK_d:
1522 if (mods != KMOD_CTRL) break; 1749 if (mods != KMOD_CTRL) break;
1523 case SDLK_DELETE: 1750 case SDLK_DELETE:
@@ -1558,10 +1785,11 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1558 return iTrue; 1785 return iTrue;
1559 } 1786 }
1560 break; 1787 break;
1788#endif
1561 case SDLK_HOME: 1789 case SDLK_HOME:
1562 case SDLK_END: 1790 case SDLK_END:
1563 if (mods == KMOD_PRIMARY || mods == (KMOD_PRIMARY | KMOD_SHIFT)) { 1791 if (mods == KMOD_PRIMARY || mods == (KMOD_PRIMARY | KMOD_SHIFT)) {
1564 setCursor_InputWidget(d, key == SDLK_HOME ? 0 : curMax); 1792 setCursor_InputWidget(d, key == SDLK_HOME ? zero_I2() : curMax);
1565 } 1793 }
1566 else { 1794 else {
1567 setCursor_InputWidget(d, key == SDLK_HOME ? lineFirst : lineLast); 1795 setCursor_InputWidget(d, key == SDLK_HOME ? lineFirst : lineLast);
@@ -1571,8 +1799,9 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1571 case SDLK_a: 1799 case SDLK_a:
1572#if defined (iPlatformApple) 1800#if defined (iPlatformApple)
1573 if (mods == KMOD_PRIMARY) { 1801 if (mods == KMOD_PRIMARY) {
1802 selectAll_InputWidget(d);
1574 d->mark.start = 0; 1803 d->mark.start = 0;
1575 d->mark.end = curMax; 1804 d->mark.end = cursorToIndex_InputWidget_(d, curMax);
1576 d->cursor = curMax; 1805 d->cursor = curMax;
1577 showCursor_InputWidget_(d); 1806 showCursor_InputWidget_(d);
1578 refresh_Widget(w); 1807 refresh_Widget(w);
@@ -1587,7 +1816,6 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1587 return iTrue; 1816 return iTrue;
1588 } 1817 }
1589 break; 1818 break;
1590#endif
1591 case SDLK_LEFT: 1819 case SDLK_LEFT:
1592 case SDLK_RIGHT: { 1820 case SDLK_RIGHT: {
1593 const int dir = (key == SDLK_LEFT ? -1 : +1); 1821 const int dir = (key == SDLK_LEFT ? -1 : +1);
@@ -1611,7 +1839,6 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1611 case SDLK_TAB: 1839 case SDLK_TAB:
1612 /* Allow focus switching. */ 1840 /* Allow focus switching. */
1613 return processEvent_Widget(as_Widget(d), ev); 1841 return processEvent_Widget(as_Widget(d), ev);
1614#if 0
1615 case SDLK_UP: 1842 case SDLK_UP:
1616 case SDLK_DOWN: 1843 case SDLK_DOWN:
1617 if (moveCursorByLine_InputWidget_(d, key == SDLK_UP ? -1 : +1)) { 1844 if (moveCursorByLine_InputWidget_(d, key == SDLK_UP ? -1 : +1)) {
@@ -1627,7 +1854,6 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1627 } 1854 }
1628 refresh_Widget(d); 1855 refresh_Widget(d);
1629 return iTrue; 1856 return iTrue;
1630#endif
1631 } 1857 }
1632 if (mods & (KMOD_PRIMARY | KMOD_SECONDARY)) { 1858 if (mods & (KMOD_PRIMARY | KMOD_SECONDARY)) {
1633 return iFalse; 1859 return iFalse;
@@ -1637,8 +1863,8 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1637 else if (ev->type == SDL_TEXTINPUT && isFocused_Widget(w)) { 1863 else if (ev->type == SDL_TEXTINPUT && isFocused_Widget(w)) {
1638 pushUndo_InputWidget_(d); 1864 pushUndo_InputWidget_(d);
1639 deleteMarked_InputWidget_(d); 1865 deleteMarked_InputWidget_(d);
1866 const int firstInsertLine = d->cursor.y;
1640 insertRange_InputWidget_(d, range_CStr(ev->text.text)); 1867 insertRange_InputWidget_(d, range_CStr(ev->text.text));
1641 lineTextWasChanged_InputWidget_(d, cursorLine_InputWidget_(d));
1642 contentsWereChanged_InputWidget_(d); 1868 contentsWereChanged_InputWidget_(d);
1643 return iTrue; 1869 return iTrue;
1644 } 1870 }
@@ -1719,6 +1945,8 @@ static void draw_InputWidget_(const iInputWidget *d) {
1719 .maxWidth = width_Rect(contentBounds), 1945 .maxWidth = width_Rect(contentBounds),
1720 .mode = (d->inFlags & isUrl_InputWidgetFlag ? anyCharacter_WrapTextMode : word_WrapTextMode) 1946 .mode = (d->inFlags & isUrl_InputWidgetFlag ? anyCharacter_WrapTextMode : word_WrapTextMode)
1721 }; 1947 };
1948 const iRangei visLines = visibleLineRange_InputWidget_(d);
1949 const int visLineOffsetY = visLineOffsetY_InputWidget_(d);
1722 /* If buffered, just draw the buffered copy. */ 1950 /* If buffered, just draw the buffered copy. */
1723 if (d->buffered && !isFocused) { 1951 if (d->buffered && !isFocused) {
1724 /* Most input widgets will use this, since only one is focused at a time. */ 1952 /* Most input widgets will use this, since only one is focused at a time. */
@@ -1729,14 +1957,14 @@ static void draw_InputWidget_(const iInputWidget *d) {
1729 } 1957 }
1730 else { 1958 else {
1731 /* TODO: Make a function out of this. */ 1959 /* TODO: Make a function out of this. */
1732 drawPos.y += d->visLineOffsetY; 1960 drawPos.y += visLineOffsetY;
1733 iMarkPainter marker = { 1961 iMarkPainter marker = {
1734 .paint = &p, 1962 .paint = &p,
1735 .d = d, 1963 .d = d,
1736 .contentBounds = contentBounds, 1964 .contentBounds = contentBounds,
1737 .mark = mark_InputWidget_(d) 1965 .mark = mark_InputWidget_(d)
1738 }; 1966 };
1739 for (size_t vis = d->visLines.start; vis < d->visLines.end; vis++) { 1967 for (size_t vis = visLines.start; vis < visLines.end; vis++) {
1740 const iInputLine *line = constAt_Array(&d->lines, vis); 1968 const iInputLine *line = constAt_Array(&d->lines, vis);
1741 wrapText.text = range_String(&line->text); 1969 wrapText.text = range_String(&line->text);
1742 wrapText.wrapFunc = isFocused ? draw_MarkPainter_ : NULL; /* mark is drawn under each line of text */ 1970 wrapText.wrapFunc = isFocused ? draw_MarkPainter_ : NULL; /* mark is drawn under each line of text */
@@ -1781,6 +2009,11 @@ static void draw_InputWidget_(const iInputWidget *d) {
1781 if (isFocused && d->cursorVis) { 2009 if (isFocused && d->cursorVis) {
1782 iString cur; 2010 iString cur;
1783 iInt2 curSize; 2011 iInt2 curSize;
2012 int visWrapsAbove = 0;
2013 for (int i = d->cursor.y - 1; i >= visLines.start; i--) {
2014 const iInputLine *line = constAt_Array(&d->lines, i);
2015 visWrapsAbove += numWrapLines_InputLine_(line);
2016 }
1784#if 0 2017#if 0
1785 if (d->mode == overwrite_InputMode) { 2018 if (d->mode == overwrite_InputMode) {
1786 /* Block cursor that overlaps a character. */ 2019 /* Block cursor that overlaps a character. */
@@ -1810,7 +2043,7 @@ static void draw_InputWidget_(const iInputWidget *d) {
1810 will have reset back to zero. */ 2043 will have reset back to zero. */
1811 wrapText.text = range_String(text); 2044 wrapText.text = range_String(text);
1812 wrapText.text.end = wrapText.text.start + d->cursor.x; 2045 wrapText.text.end = wrapText.text.start + d->cursor.x;
1813 /* TODO: Count lines above for offset. */ 2046 iAssert(wrapText.text.end <= constEnd_String(text));
1814 //const int prefixSize = maxWidth_TextMetrics( 2047 //const int prefixSize = maxWidth_TextMetrics(
1815 const iTextMetrics tm = measure_WrapText(&wrapText, d->font); 2048 const iTextMetrics tm = measure_WrapText(&wrapText, d->font);
1816 // const iInt2 curPos = addX_I2(addY_I2(contentBounds.pos, lineHeight_Text(d->font) 2049 // const iInt2 curPos = addX_I2(addY_I2(contentBounds.pos, lineHeight_Text(d->font)
@@ -1819,7 +2052,8 @@ static void draw_InputWidget_(const iInputWidget *d) {
1819 // (d->mode == insert_InputMode ? -curSize.x / 2 : 2052 // (d->mode == insert_InputMode ? -curSize.x / 2 :
1820 // 0)); 2053 // 0));
1821 //printf("%d -> tm.advance: %d, %d\n", d->cursor.x, tm.advance.x, tm.advance.y); 2054 //printf("%d -> tm.advance: %d, %d\n", d->cursor.x, tm.advance.x, tm.advance.y);
1822 const iInt2 curPos = add_I2(addY_I2(topLeft_Rect(contentBounds), d->visLineOffsetY), 2055 const iInt2 curPos = add_I2(addY_I2(topLeft_Rect(contentBounds), visLineOffsetY +
2056 visWrapsAbove * lineHeight_Text(d->font)),
1823 addX_I2(tm.advance, 2057 addX_I2(tm.advance,
1824 (d->mode == insert_InputMode ? -curSize.x / 2 : 0))); 2058 (d->mode == insert_InputMode ? -curSize.x / 2 : 0)));
1825 const iRect curRect = { curPos, curSize }; 2059 const iRect curRect = { curPos, curSize };
diff --git a/src/ui/inputwidget.h b/src/ui/inputwidget.h
index 4fb2c9dc..a4246733 100644
--- a/src/ui/inputwidget.h
+++ b/src/ui/inputwidget.h
@@ -49,7 +49,7 @@ void setTextCStr_InputWidget (iInputWidget *, const char *cstr);
49void setFont_InputWidget (iInputWidget *, int fontId); 49void setFont_InputWidget (iInputWidget *, int fontId);
50//void setCursor_InputWidget (iInputWidget *, size_t pos); 50//void setCursor_InputWidget (iInputWidget *, size_t pos);
51void setContentPadding_InputWidget (iInputWidget *, int left, int right); /* only affects the text entry */ 51void setContentPadding_InputWidget (iInputWidget *, int left, int right); /* only affects the text entry */
52void setLineLimits_InputWidget (iInputWidget *, int minVis, int maxVis); 52void setLineLimits_InputWidget (iInputWidget *, int minLines, int maxLines);
53void setValidator_InputWidget (iInputWidget *, iInputWidgetValidatorFunc validator, void *context); 53void setValidator_InputWidget (iInputWidget *, iInputWidgetValidatorFunc validator, void *context);
54void setEnterInsertsLF_InputWidget (iInputWidget *, iBool enterInsertsLF); 54void setEnterInsertsLF_InputWidget (iInputWidget *, iBool enterInsertsLF);
55void setEnterKeyEnabled_InputWidget (iInputWidget *, iBool enterKeyEnabled); 55void setEnterKeyEnabled_InputWidget (iInputWidget *, iBool enterKeyEnabled);
diff --git a/src/ui/text.c b/src/ui/text.c
index 0d101813..d6a45c69 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -869,6 +869,10 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir) {
869 if (len <= 0) break; 869 if (len <= 0) break;
870 pushBack_Array(&d->logical, &u32); 870 pushBack_Array(&d->logical, &u32);
871 length++; 871 length++;
872 if (length == d->maxLen) {
873 /* TODO: Check the combining class; only count base characters here. */
874 break;
875 }
872 /* Remember the byte offset to each character. We will need this to communicate 876 /* Remember the byte offset to each character. We will need this to communicate
873 back the wrapped UTF-8 ranges. */ 877 back the wrapped UTF-8 ranges. */
874 pushBack_Array(&d->logicalToSourceOffset, &(int){ ch - d->source.start }); 878 pushBack_Array(&d->logicalToSourceOffset, &(int){ ch - d->source.start });
@@ -905,7 +909,6 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir) {
905 pushBack_Array(&d->logicalToVisual, &(int){ length }); 909 pushBack_Array(&d->logicalToVisual, &(int){ length });
906 pushBack_Array(&d->visualToLogical, &(int){ length }); 910 pushBack_Array(&d->visualToLogical, &(int){ length });
907 } 911 }
908 size_t avail = d->maxLen;
909 iAttributedRun run = { .logical = { 0, length }, 912 iAttributedRun run = { .logical = { 0, length },
910 .font = d->font, 913 .font = d->font,
911 .fgColor = d->fgColor }; 914 .fgColor = d->fgColor };
@@ -978,11 +981,6 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir) {
978 if (isControl_Char_(ch)) { 981 if (isControl_Char_(ch)) {
979 continue; 982 continue;
980 } 983 }
981 if (avail-- == 0) {
982 /* TODO: Check the combining class; only count base characters here. */
983 run.logical.end = pos;
984 break;
985 }
986 if (ch == 0x20) { 984 if (ch == 0x20) {
987 if (run.font->family == emojiAndSymbols_TextFont) { 985 if (run.font->family == emojiAndSymbols_TextFont) {
988 finishRun_AttributedText_(d, &run, pos); 986 finishRun_AttributedText_(d, &run, pos);
@@ -1434,6 +1432,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1434 isFirst = iFalse; 1432 isFirst = iFalse;
1435 } 1433 }
1436 else { 1434 else {
1435 xCursor = 0;
1437 yCursor += d->height; 1436 yCursor += d->height;
1438 } 1437 }
1439 float wrapAdvance = 0.0f; 1438 float wrapAdvance = 0.0f;
@@ -1724,6 +1723,12 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1724 wrapPosRange.start = wrapResumePos; 1723 wrapPosRange.start = wrapResumePos;
1725 wrapPosRange.end = textLen; 1724 wrapPosRange.end = textLen;
1726 } 1725 }
1726 if (endsWith_Rangecc(args->text, "\n")) {
1727 /* FIXME: This is a kludge, the wrap loop should handle this case, too. */
1728 /* The last wrap is an empty newline wrap. */
1729 xCursor = 0;
1730 yCursor += d->height;
1731 }
1727 if (args->cursorAdvance_out) { 1732 if (args->cursorAdvance_out) {
1728 *args->cursorAdvance_out = init_I2(xCursor, yCursor); 1733 *args->cursorAdvance_out = init_I2(xCursor, yCursor);
1729 } 1734 }
diff --git a/src/ui/uploadwidget.c b/src/ui/uploadwidget.c
index fd363201..8f84eb81 100644
--- a/src/ui/uploadwidget.c
+++ b/src/ui/uploadwidget.c
@@ -86,6 +86,7 @@ void init_UploadWidget(iUploadWidget *d) {
86 setFlags_Widget(page, arrangeSize_WidgetFlag, iTrue); 86 setFlags_Widget(page, arrangeSize_WidgetFlag, iTrue);
87 d->input = new_InputWidget(0); 87 d->input = new_InputWidget(0);
88 setFont_InputWidget(d->input, monospace_FontId); 88 setFont_InputWidget(d->input, monospace_FontId);
89 setLineLimits_InputWidget(d->input, 10, 20);
89 setHint_InputWidget(d->input, "${hint.upload.text}"); 90 setHint_InputWidget(d->input, "${hint.upload.text}");
90 setEnterInsertsLF_InputWidget(d->input, iTrue); 91 setEnterInsertsLF_InputWidget(d->input, iTrue);
91 setFixedSize_Widget(as_Widget(d->input), init_I2(120 * gap_UI, -1)); 92 setFixedSize_Widget(as_Widget(d->input), init_I2(120 * gap_UI, -1));