diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-07-20 14:00:59 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-07-21 07:37:09 +0300 |
commit | aaa5d18c3f1540c18bb9dae80927df2803c42049 (patch) | |
tree | 8af0cb9dd9b0419153e287551bbeb99f5e76b931 /src/ui/inputwidget.c | |
parent | 10f44efd277855761f69687193d6d5250650186d (diff) |
Revising InputWidget (continued)
Finding cursor position via coordinates. Handling the mark. Visual wrapped lines vs. content lines. Vertical scrolling inside the visual range.
Diffstat (limited to 'src/ui/inputwidget.c')
-rw-r--r-- | src/ui/inputwidget.c | 510 |
1 files changed, 372 insertions, 138 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) { | |||
58 | iDeclareType(InputLine) | 58 | iDeclareType(InputLine) |
59 | 59 | ||
60 | struct Impl_InputLine { | 60 | struct 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 | ||
68 | static void init_InputLine(iInputLine *d) { | 66 | static 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 | |||
72 | iLocalDef int numWrapLines_InputLine_(const iInputLine *d) { | ||
73 | return size_Range(&d->wrapLines); | ||
72 | } | 74 | } |
73 | 75 | ||
74 | static void deinit_InputLine(iInputLine *d) { | 76 | static 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 | ||
268 | static int numContentLines_InputWidget_(const iInputWidget *d) { | 271 | iLocalDef 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; | 275 | iLocalDef 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 | |||
280 | static int numWrapLines_InputWidget_(const iInputWidget *d) { | ||
281 | return lastLine_InputWidget_(d)->wrapLines.end; | ||
275 | } | 282 | } |
276 | 283 | ||
277 | static const iInputLine *line_InputWidget_(const iInputWidget *d, size_t index) { | 284 | static 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 | ||
282 | iLocalDef iBool isLastLine_InputWidget_(const iInputWidget *d, const iInputLine *line) { | 289 | static 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 | |||
293 | static const char *charPos_InputWidget_(const iInputWidget *d, iInt2 pos) { | ||
294 | return cstr_String(lineString_InputWidget_(d, pos.y)) + pos.x; | ||
284 | } | 295 | } |
285 | 296 | ||
286 | static int endX_InputWidget_(const iInputWidget *d, int y) { | 297 | static 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 | ||
292 | static void scrollVisLines_InputWidget_(iInputWidget *d, int delta) { | 303 | static 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; | 310 | static 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 | |||
321 | static 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 | ||
307 | static void updateVisible_InputWidget_(iInputWidget *d) { | 326 | static 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 | ||
326 | static void showCursor_InputWidget_(iInputWidget *d) { | 376 | static void showCursor_InputWidget_(iInputWidget *d) { |
@@ -448,7 +498,7 @@ static void updateLines_InputWidget_(iInputWidget *d) { | |||
448 | #endif | 498 | #endif |
449 | 499 | ||
450 | static int contentHeight_InputWidget_(const iInputWidget *d) { | 500 | static 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 | ||
479 | static void updateLine_InputWidget_(iInputWidget *d, iInputLine *line) { | 529 | static 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 | ||
494 | static void updateAllLinesAndResizeHeight_InputWidget_(iInputWidget *d) { | 545 | static 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 | ||
644 | void setLineLimits_InputWidget(iInputWidget *d, int minVis, int maxVis) { | 695 | void 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 | ||
718 | iLocalDef const iInputLine *lastLine_InputWidget_(const iInputWidget *d) { | ||
719 | iAssert(!isEmpty_Array(&d->lines)); | ||
720 | return constBack_Array(&d->lines); | ||
721 | } | ||
722 | |||
723 | iLocalDef iInt2 cursorMax_InputWidget_(const iInputWidget *d) { | 769 | iLocalDef 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 | ||
728 | void setText_InputWidget(iInputWidget *d, const iString *text) { | 774 | void 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 | ||
940 | static 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 | |||
956 | static 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 | |||
894 | static void insertRange_InputWidget_(iInputWidget *d, iRangecc range) { | 965 | static 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 | ||
952 | static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const iInputLine *line) { | 1046 | static 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 | |||
1076 | iDeclareType(LineMover) | ||
1077 | |||
1078 | struct Impl_LineMover { | ||
1079 | iInputWidget *d; | ||
1080 | int dir; | ||
1081 | int x; | ||
1082 | }; | ||
1083 | |||
1084 | static iBool findXBreaks_LineMover_(iWrapText *wrap, iRangecc wrappedText, | ||
1085 | int origin, int advance, iBool isBaseRTL) { | ||
1086 | iUnused(isBaseRTL); | ||
1087 | |||
1088 | } | ||
980 | 1089 | ||
981 | static iBool moveCursorByLine_InputWidget_(iInputWidget *d, int dir) { | 1090 | static 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 | ||
1199 | static 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 | |||
1046 | static iBool deleteMarked_InputWidget_(iInputWidget *d) { | 1258 | static 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 | ||
1059 | static const iString *lineString_InputWidget_(const iInputWidget *d, int y) { | ||
1060 | return &line_InputWidget_(d, y)->text; | ||
1061 | } | ||
1062 | |||
1063 | static const char *charPos_InputWidget_(const iInputWidget *d, iInt2 pos) { | ||
1064 | return cstr_String(lineString_InputWidget_(d, pos.y)) + pos.x; | ||
1065 | } | ||
1066 | |||
1067 | static iChar at_InputWidget_(const iInputWidget *d, iInt2 pos) { | 1268 | static 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 | ||
1159 | static size_t coordIndex_InputWidget_(const iInputWidget *d, iInt2 coord) { | 1360 | static 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 | |||
1376 | static 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 | ||
1169 | static iBool copy_InputWidget_(iInputWidget *d, iBool doCut) { | 1411 | static 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 | ||
1219 | static void extendRange_InputWidget_(iInputWidget *d, size_t *pos, int dir) { | 1461 | static 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 | ||
1238 | static iRect bounds_InputWidget_(const iInputWidget *d) { | 1480 | static iRect bounds_InputWidget_(const iInputWidget *d) { |
@@ -1254,18 +1496,8 @@ static iBool contains_InputWidget_(const iInputWidget *d, iInt2 coord) { | |||
1254 | } | 1496 | } |
1255 | 1497 | ||
1256 | static void lineTextWasChanged_InputWidget_(iInputWidget *d, iInputLine *line) { | 1498 | static 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 | ||
1271 | static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | 1503 | static 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 }; |