summaryrefslogtreecommitdiff
path: root/src/ui/inputwidget.c
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-05-18 07:04:27 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-05-18 07:04:27 +0300
commitfacc3abb7faafb323cb87e26ddba8dac01af02e9 (patch)
tree387aa23e03b029f516ecca6880131fd6523e1c3f /src/ui/inputwidget.c
parentced855c338b78e05c66d38618373728ef946ebaa (diff)
parent5d517c2f790a38d7fe3c3cc59a1b39fd49e20280 (diff)
Merge branch 'dev' into work/typesetter
# Conflicts: # src/ui/documentwidget.c # src/ui/inputwidget.c
Diffstat (limited to 'src/ui/inputwidget.c')
-rw-r--r--src/ui/inputwidget.c267
1 files changed, 178 insertions, 89 deletions
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index 6719fb40..784fabdd 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -42,8 +42,12 @@ static const size_t maxUndo_InputWidget_ = 64;
42 42
43static void enableEditorKeysInMenus_(iBool enable) { 43static void enableEditorKeysInMenus_(iBool enable) {
44#if defined (iPlatformAppleDesktop) 44#if defined (iPlatformAppleDesktop)
45 enableMenuItemsByKey_MacOS(SDLK_LEFT, KMOD_PRIMARY, enable); 45 enableMenuItemsByKey_MacOS(SDLK_LEFT, KMOD_PRIMARY, enable);
46 enableMenuItemsByKey_MacOS(SDLK_RIGHT, KMOD_PRIMARY, enable); 46 enableMenuItemsByKey_MacOS(SDLK_RIGHT, KMOD_PRIMARY, enable);
47 enableMenuItemsByKey_MacOS(SDLK_UP, KMOD_PRIMARY, enable);
48 enableMenuItemsByKey_MacOS(SDLK_DOWN, KMOD_PRIMARY, enable);
49 enableMenuItemsByKey_MacOS(SDLK_UP, KMOD_PRIMARY | KMOD_SHIFT, enable);
50 enableMenuItemsByKey_MacOS(SDLK_DOWN, KMOD_PRIMARY | KMOD_SHIFT, enable);
47#else 51#else
48 iUnused(enable); 52 iUnused(enable);
49#endif 53#endif
@@ -84,6 +88,7 @@ iDeclareType(InputLine)
84 88
85struct Impl_InputLine { 89struct Impl_InputLine {
86 size_t offset; /* character position from the beginning */ 90 size_t offset; /* character position from the beginning */
91 size_t len; /* length as characters */
87 iString text; /* UTF-8 */ 92 iString text; /* UTF-8 */
88}; 93};
89 94
@@ -109,6 +114,7 @@ struct Impl_InputWidget {
109 iArray text; /* iChar[] */ 114 iArray text; /* iChar[] */
110 iArray oldText; /* iChar[] */ 115 iArray oldText; /* iChar[] */
111 iArray lines; 116 iArray lines;
117 int lastUpdateWidth;
112 iString hint; 118 iString hint;
113 iString srcHint; 119 iString srcHint;
114 int leftPadding; 120 int leftPadding;
@@ -138,7 +144,26 @@ static void clearUndo_InputWidget_(iInputWidget *d) {
138 clear_Array(&d->undoStack); 144 clear_Array(&d->undoStack);
139} 145}
140 146
147iLocalDef iInt2 padding_(void) {
148 return init_I2(gap_UI / 2, gap_UI / 2);
149}
150
151static iRect contentBounds_InputWidget_(const iInputWidget *d) {
152 const iWidget *w = constAs_Widget(d);
153 // const iRect widgetBounds = bounds_Widget(w);
154 iRect bounds = adjusted_Rect(bounds_Widget(w),
155 addX_I2(padding_(), d->leftPadding),
156 neg_I2(addX_I2(padding_(), d->rightPadding)));
157 shrink_Rect(&bounds, init_I2(gap_UI * (flags_Widget(w) & tight_WidgetFlag ? 1 : 2), 0));
158 bounds.pos.y += padding_().y / 2;
159 if (flags_Widget(w) & extraPadding_WidgetFlag) {
160 bounds.pos.y += gap_UI;
161 }
162 return bounds;
163}
164
141static void updateCursorLine_InputWidget_(iInputWidget *d) { 165static void updateCursorLine_InputWidget_(iInputWidget *d) {
166 iWidget *w = as_Widget(d);
142 d->cursorLine = 0; 167 d->cursorLine = 0;
143 iConstForEach(Array, i, &d->lines) { 168 iConstForEach(Array, i, &d->lines) {
144 const iInputLine *line = i.value; 169 const iInputLine *line = i.value;
@@ -147,6 +172,20 @@ static void updateCursorLine_InputWidget_(iInputWidget *d) {
147 } 172 }
148 d->cursorLine = index_ArrayConstIterator(&i); 173 d->cursorLine = index_ArrayConstIterator(&i);
149 } 174 }
175 /* May need to scroll to keep the cursor visible. */
176 iWidget *flow = findOverflowScrollable_Widget(w);
177 if (flow) {
178 const iRect rootRect = { rect_Root(w->root).pos, visibleSize_Root(w->root) };
179 int yCursor = contentBounds_InputWidget_(d).pos.y +
180 lineHeight_Text(d->font) * (int) d->cursorLine;
181 const int margin = lineHeight_Text(d->font) * 3;
182 if (yCursor < top_Rect(rootRect) + margin) {
183 scrollOverflow_Widget(flow, top_Rect(rootRect) + margin - yCursor);
184 }
185 else if (yCursor > bottom_Rect(rootRect) - margin * 3 / 2) {
186 scrollOverflow_Widget(flow, bottom_Rect(rootRect) - margin * 3 / 2 - yCursor);
187 }
188 }
150} 189}
151 190
152static void showCursor_InputWidget_(iInputWidget *d) { 191static void showCursor_InputWidget_(iInputWidget *d) {
@@ -161,21 +200,6 @@ static void invalidateBuffered_InputWidget_(iInputWidget *d) {
161 } 200 }
162} 201}
163 202
164iLocalDef iInt2 padding_(void) {
165 return init_I2(gap_UI / 2, gap_UI / 2);
166}
167
168static iRect contentBounds_InputWidget_(const iInputWidget *d) {
169 const iWidget *w = constAs_Widget(d);
170 const iRect widgetBounds = bounds_Widget(w);
171 iRect bounds = adjusted_Rect(bounds_Widget(w),
172 addX_I2(padding_(), d->leftPadding),
173 neg_I2(addX_I2(padding_(), d->rightPadding)));
174 shrink_Rect(&bounds, init_I2(gap_UI * (flags_Widget(w) & tight_WidgetFlag ? 1 : 2), 0));
175 bounds.pos.y += padding_().y / 2;
176 return bounds;
177}
178
179static void updateSizeForFixedLength_InputWidget_(iInputWidget *d) { 203static void updateSizeForFixedLength_InputWidget_(iInputWidget *d) {
180 if (d->maxLen) { 204 if (d->maxLen) {
181 /* Set a fixed size based on maximum possible width of the text. */ 205 /* Set a fixed size based on maximum possible width of the text. */
@@ -219,6 +243,7 @@ static void clearLines_InputWidget_(iInputWidget *d) {
219} 243}
220 244
221static void updateLines_InputWidget_(iInputWidget *d) { 245static void updateLines_InputWidget_(iInputWidget *d) {
246 d->lastUpdateWidth = d->widget.rect.size.x;
222 clearLines_InputWidget_(d); 247 clearLines_InputWidget_(d);
223 if (d->maxLen) { 248 if (d->maxLen) {
224 /* Everything on a single line. */ 249 /* Everything on a single line. */
@@ -226,6 +251,7 @@ static void updateLines_InputWidget_(iInputWidget *d) {
226 init_InputLine(&line); 251 init_InputLine(&line);
227 iString *u8 = visText_InputWidget_(d); 252 iString *u8 = visText_InputWidget_(d);
228 set_String(&line.text, u8); 253 set_String(&line.text, u8);
254 line.len = length_String(u8);
229 delete_String(u8); 255 delete_String(u8);
230 pushBack_Array(&d->lines, &line); 256 pushBack_Array(&d->lines, &line);
231 updateCursorLine_InputWidget_(d); 257 updateCursorLine_InputWidget_(d);
@@ -249,14 +275,16 @@ static void updateLines_InputWidget_(iInputWidget *d) {
249 init_InputLine(&line); 275 init_InputLine(&line);
250 setRange_String(&line.text, part); 276 setRange_String(&line.text, part);
251 line.offset = charPos; 277 line.offset = charPos;
278 line.len = length_String(&line.text);
252 pushBack_Array(&d->lines, &line); 279 pushBack_Array(&d->lines, &line);
253 charPos += length_String(&line.text); 280 charPos += line.len;
254 content.start = endPos; 281 content.start = endPos;
255 } 282 }
256 if (isEmpty_Array(&d->lines) || endsWith_String(u8, "\n")) { 283 if (isEmpty_Array(&d->lines) || endsWith_String(u8, "\n")) {
257 /* Always at least one empty line. */ 284 /* Always at least one empty line. */
258 iInputLine line; 285 iInputLine line;
259 init_InputLine(&line); 286 init_InputLine(&line);
287 line.offset = charPos;
260 pushBack_Array(&d->lines, &line); 288 pushBack_Array(&d->lines, &line);
261 } 289 }
262 else { 290 else {
@@ -278,7 +306,7 @@ static void updateMetrics_InputWidget_(iInputWidget *d) {
278 iWidget *w = as_Widget(d); 306 iWidget *w = as_Widget(d);
279 updateSizeForFixedLength_InputWidget_(d); 307 updateSizeForFixedLength_InputWidget_(d);
280 /* Caller must arrange the width, but the height is fixed. */ 308 /* Caller must arrange the width, but the height is fixed. */
281 w->rect.size.y = contentHeight_InputWidget_(d, iTrue) + 3 * padding_().y; /* TODO: Why 3x? */ 309 w->rect.size.y = contentHeight_InputWidget_(d, iTrue) + 3.0f * padding_().y; /* TODO: Why 3x? */
282 if (flags_Widget(w) & extraPadding_WidgetFlag) { 310 if (flags_Widget(w) & extraPadding_WidgetFlag) {
283 w->rect.size.y += 2 * gap_UI; 311 w->rect.size.y += 2 * gap_UI;
284 } 312 }
@@ -316,6 +344,7 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) {
316 d->cursor = 0; 344 d->cursor = 0;
317 d->lastCursor = 0; 345 d->lastCursor = 0;
318 d->cursorLine = 0; 346 d->cursorLine = 0;
347 d->lastUpdateWidth = 0;
319 d->verticalMoveX = -1; /* TODO: Use this. */ 348 d->verticalMoveX = -1; /* TODO: Use this. */
320 d->inFlags = eatEscape_InputWidgetFlag | enterKeyEnabled_InputWidgetFlag; 349 d->inFlags = eatEscape_InputWidgetFlag | enterKeyEnabled_InputWidgetFlag;
321 iZap(d->mark); 350 iZap(d->mark);
@@ -455,31 +484,46 @@ void setContentPadding_InputWidget(iInputWidget *d, int left, int right) {
455 refresh_Widget(d); 484 refresh_Widget(d);
456} 485}
457 486
487static iBool isHintVisible_InputWidget_(const iInputWidget *d) {
488 return !isEmpty_String(&d->hint) && size_Array(&d->lines) == 1 &&
489 isEmpty_String(&line_InputWidget_(d, 0)->text);
490}
491
458static void updateBuffered_InputWidget_(iInputWidget *d) { 492static void updateBuffered_InputWidget_(iInputWidget *d) {
459 invalidateBuffered_InputWidget_(d); 493 invalidateBuffered_InputWidget_(d);
460 iString *bufText = NULL; 494 if (isHintVisible_InputWidget_(d)) {
495 d->buffered = new_TextBuf(d->font, uiAnnotation_ColorId, cstr_String(&d->hint));
496 }
497 else {
498 iString *bufText = NULL;
461#if 0 499#if 0
462 if (d->inFlags & isUrl_InputWidgetFlag && as_Widget(d)->root == win->keyRoot) { 500 if (d->inFlags & isUrl_InputWidgetFlag && as_Widget(d)->root == win->keyRoot) {
463 /* TODO: Move this omitting to `updateLines_`? */ 501 /* TODO: Move this omitting to `updateLines_`? */
464 /* Highlight the host name. */ 502 /* Highlight the host name. */
465 iUrl parts; 503 iUrl parts;
466 const iString *text = collect_String(utf32toUtf8_InputWidget_(d)); 504 const iString *text = collect_String(utf32toUtf8_InputWidget_(d));
467 init_Url(&parts, text); 505 init_Url(&parts, text);
468 if (!isEmpty_Range(&parts.host)) { 506 if (!isEmpty_Range(&parts.host)) {
469 bufText = new_String(); 507 bufText = new_String();
470 appendRange_String(bufText, (iRangecc){ constBegin_String(text), parts.host.start }); 508 appendRange_String(bufText, (iRangecc){ constBegin_String(text), parts.host.start });
471 appendCStr_String(bufText, uiTextStrong_ColorEscape); 509 appendCStr_String(bufText, uiTextStrong_ColorEscape);
472 appendRange_String(bufText, parts.host); 510 appendRange_String(bufText, parts.host);
473 appendCStr_String(bufText, restore_ColorEscape); 511 appendCStr_String(bufText, restore_ColorEscape);
474 appendRange_String(bufText, (iRangecc){ parts.host.end, constEnd_String(text) }); 512 appendRange_String(bufText, (iRangecc){ parts.host.end, constEnd_String(text) });
513 }
475 } 514 }
476 }
477#endif 515#endif
478 if (!bufText) { 516 if (!bufText) {
479 bufText = visText_InputWidget_(d); 517 bufText = visText_InputWidget_(d);
518 }
519 const int maxWidth = contentBounds_InputWidget_(d).size.x;
520 const int fg = uiInputText_ColorId;
521 const char *text = cstr_String(bufText);
522 d->buffered =
523 (d->inFlags & isUrl_InputWidgetFlag ? newBound_TextBuf(d->font, fg, maxWidth, text)
524 : newWrap_TextBuf (d->font, fg, maxWidth, text));
525 delete_String(bufText);
480 } 526 }
481 d->buffered = new_TextBuf(d->font, uiInputText_ColorId, cstr_String(bufText));
482 delete_String(bufText);
483 d->inFlags &= ~needUpdateBuffer_InputWidgetFlag; 527 d->inFlags &= ~needUpdateBuffer_InputWidgetFlag;
484} 528}
485 529
@@ -563,7 +607,11 @@ void begin_InputWidget(iInputWidget *d) {
563 } 607 }
564 updateCursorLine_InputWidget_(d); 608 updateCursorLine_InputWidget_(d);
565 SDL_StartTextInput(); 609 SDL_StartTextInput();
566 setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag, iTrue); 610 setFlags_Widget(w, selected_WidgetFlag, iTrue);
611 if (d->maxLayoutLines != iInvalidSize) {
612 /* This will extend beyond the arranged region. */
613 setFlags_Widget(w, keepOnTop_WidgetFlag, iTrue);
614 }
567 showCursor_InputWidget_(d); 615 showCursor_InputWidget_(d);
568 refresh_Widget(w); 616 refresh_Widget(w);
569 d->timer = SDL_AddTimer(refreshInterval_InputWidget_, cursorTimer_, d); 617 d->timer = SDL_AddTimer(refreshInterval_InputWidget_, cursorTimer_, d);
@@ -657,6 +705,10 @@ void setCursor_InputWidget(iInputWidget *d, size_t pos) {
657 showCursor_InputWidget_(d); 705 showCursor_InputWidget_(d);
658} 706}
659 707
708iLocalDef iBool isLastLine_InputWidget_(const iInputWidget *d, const iInputLine *line) {
709 return (const void *) line == constAt_Array(&d->lines, size_Array(&d->lines) - 1);
710}
711
660static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const iInputLine *line) { 712static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const iInputLine *line) {
661 size_t index = line->offset; 713 size_t index = line->offset;
662 if (x <= 0) { 714 if (x <= 0) {
@@ -665,7 +717,7 @@ static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const
665 const char *endPos; 717 const char *endPos;
666 tryAdvanceNoWrap_Text(d->font, range_String(&line->text), x, &endPos); 718 tryAdvanceNoWrap_Text(d->font, range_String(&line->text), x, &endPos);
667 if (endPos == constEnd_String(&line->text)) { 719 if (endPos == constEnd_String(&line->text)) {
668 index += length_String(&line->text); 720 index += line->len;
669 } 721 }
670 else { 722 else {
671 /* Need to know the actual character index. */ 723 /* Need to know the actual character index. */
@@ -675,6 +727,9 @@ static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const
675 index++; 727 index++;
676 } 728 }
677 } 729 }
730 if (!isLastLine_InputWidget_(d, line) && index == line->offset + line->len) {
731 index = iMax(index - 1, line->offset);
732 }
678 return index; 733 return index;
679} 734}
680 735
@@ -690,11 +745,6 @@ static iBool moveCursorByLine_InputWidget_(iInputWidget *d, int dir) {
690 newCursor = indexForRelativeX_InputWidget_(d, xPos, ++line); 745 newCursor = indexForRelativeX_InputWidget_(d, xPos, ++line);
691 } 746 }
692 if (newCursor != iInvalidPos) { 747 if (newCursor != iInvalidPos) {
693 /* Clamp it to the current line. */
694 newCursor = iMin(newCursor, line->offset + length_String(&line->text) -
695 /* last line is allowed to go to the cursorMax */
696 ((const void *) line < constAt_Array(&d->lines, numLines - 1) ? 1 : 0));
697 newCursor = iMax(newCursor, line->offset);
698 setCursor_InputWidget(d, newCursor); 748 setCursor_InputWidget(d, newCursor);
699 return iTrue; 749 return iTrue;
700 } 750 }
@@ -707,6 +757,7 @@ void setSensitiveContent_InputWidget(iInputWidget *d, iBool isSensitive) {
707 757
708void setUrlContent_InputWidget(iInputWidget *d, iBool isUrl) { 758void setUrlContent_InputWidget(iInputWidget *d, iBool isUrl) {
709 iChangeFlags(d->inFlags, isUrl_InputWidgetFlag, isUrl); 759 iChangeFlags(d->inFlags, isUrl_InputWidgetFlag, isUrl);
760 d->inFlags |= needUpdateBuffer_InputWidgetFlag;
710} 761}
711 762
712void setSelectAllOnFocus_InputWidget(iInputWidget *d, iBool selectAllOnFocus) { 763void setSelectAllOnFocus_InputWidget(iInputWidget *d, iBool selectAllOnFocus) {
@@ -826,7 +877,8 @@ static iInt2 textOrigin_InputWidget_(const iInputWidget *d) { //}, const char *v
826 877
827static size_t coordIndex_InputWidget_(const iInputWidget *d, iInt2 coord) { 878static size_t coordIndex_InputWidget_(const iInputWidget *d, iInt2 coord) {
828 const iInt2 pos = sub_I2(coord, contentBounds_InputWidget_(d).pos); 879 const iInt2 pos = sub_I2(coord, contentBounds_InputWidget_(d).pos);
829 const size_t lineNumber = iMin(pos.y / lineHeight_Text(d->font), (int) size_Array(&d->lines) - 1); 880 const size_t lineNumber = iMin(iMax(0, pos.y) / lineHeight_Text(d->font),
881 (int) size_Array(&d->lines) - 1);
830 const iInputLine *line = line_InputWidget_(d, lineNumber); 882 const iInputLine *line = line_InputWidget_(d, lineNumber);
831 const char *endPos; 883 const char *endPos;
832 tryAdvanceNoWrap_Text(d->font, range_String(&line->text), pos.x, &endPos); 884 tryAdvanceNoWrap_Text(d->font, range_String(&line->text), pos.x, &endPos);
@@ -880,7 +932,7 @@ static iRanges lineRange_InputWidget_(const iInputWidget *d) {
880 return (iRanges){ 0, 0 }; 932 return (iRanges){ 0, 0 };
881 } 933 }
882 const iInputLine *line = line_InputWidget_(d, d->cursorLine); 934 const iInputLine *line = line_InputWidget_(d, d->cursorLine);
883 return (iRanges){ line->offset, line->offset + length_String(&line->text) }; 935 return (iRanges){ line->offset, line->offset + line->len };
884} 936}
885 937
886static void extendRange_InputWidget_(iInputWidget *d, size_t *pos, int dir) { 938static void extendRange_InputWidget_(iInputWidget *d, size_t *pos, int dir) {
@@ -907,6 +959,9 @@ static iRect bounds_InputWidget_(const iInputWidget *d) {
907 return bounds; 959 return bounds;
908 } 960 }
909 bounds.size.y = contentHeight_InputWidget_(d, iFalse) + 3 * padding_().y; 961 bounds.size.y = contentHeight_InputWidget_(d, iFalse) + 3 * padding_().y;
962 if (w->flags & extraPadding_WidgetFlag) {
963 bounds.size.y += 2 * gap_UI;
964 }
910 return bounds; 965 return bounds;
911} 966}
912 967
@@ -958,11 +1013,19 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
958 } 1013 }
959 return iFalse; 1014 return iFalse;
960 } 1015 }
1016 else if (isCommand_UserEvent(ev, "text.insert")) {
1017 pushUndo_InputWidget_(d);
1018 deleteMarked_InputWidget_(d);
1019 insertChar_InputWidget_(d, arg_Command(command_UserEvent(ev)));
1020 contentsWereChanged_InputWidget_(d);
1021 return iTrue;
1022 }
961 else if (isMetricsChange_UserEvent(ev)) { 1023 else if (isMetricsChange_UserEvent(ev)) {
962 updateMetrics_InputWidget_(d); 1024 updateMetrics_InputWidget_(d);
963 updateLinesAndResize_InputWidget_(d); 1025 updateLinesAndResize_InputWidget_(d);
964 } 1026 }
965 else if (isResize_UserEvent(ev)) { 1027 else if (isResize_UserEvent(ev) || d->lastUpdateWidth != w->rect.size.x) {
1028 d->inFlags |= needUpdateBuffer_InputWidgetFlag;
966 if (d->inFlags & isUrl_InputWidgetFlag) { 1029 if (d->inFlags & isUrl_InputWidgetFlag) {
967 /* Restore/omit the default scheme if necessary. */ 1030 /* Restore/omit the default scheme if necessary. */
968 setText_InputWidget(d, text_InputWidget(d)); 1031 setText_InputWidget(d, text_InputWidget(d));
@@ -1077,6 +1140,17 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1077 return iTrue; 1140 return iTrue;
1078 } 1141 }
1079 } 1142 }
1143#if defined (iPlatformApple)
1144 if (mods == KMOD_PRIMARY || mods == (KMOD_PRIMARY | KMOD_SHIFT)) {
1145 switch (key) {
1146 case SDLK_UP:
1147 case SDLK_DOWN:
1148 setCursor_InputWidget(d, key == SDLK_UP ? 0 : curMax);
1149 refresh_Widget(d);
1150 return iTrue;
1151 }
1152 }
1153#endif
1080 d->lastCursor = d->cursor; 1154 d->lastCursor = d->cursor;
1081 switch (key) { 1155 switch (key) {
1082 case SDLK_INSERT: 1156 case SDLK_INSERT:
@@ -1086,7 +1160,8 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1086 return iTrue; 1160 return iTrue;
1087 case SDLK_RETURN: 1161 case SDLK_RETURN:
1088 case SDLK_KP_ENTER: 1162 case SDLK_KP_ENTER:
1089 if (mods == KMOD_SHIFT) { 1163 if (mods == KMOD_SHIFT || (~d->inFlags & isUrl_InputWidgetFlag &&
1164 deviceType_App() != desktop_AppDeviceType)) {
1090 pushUndo_InputWidget_(d); 1165 pushUndo_InputWidget_(d);
1091 deleteMarked_InputWidget_(d); 1166 deleteMarked_InputWidget_(d);
1092 insertChar_InputWidget_(d, '\n'); 1167 insertChar_InputWidget_(d, '\n');
@@ -1170,7 +1245,12 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1170 break; 1245 break;
1171 case SDLK_HOME: 1246 case SDLK_HOME:
1172 case SDLK_END: 1247 case SDLK_END:
1173 setCursor_InputWidget(d, key == SDLK_HOME ? lineFirst : lineLast); 1248 if (mods == KMOD_PRIMARY || mods == (KMOD_PRIMARY | KMOD_SHIFT)) {
1249 setCursor_InputWidget(d, key == SDLK_HOME ? 0 : curMax);
1250 }
1251 else {
1252 setCursor_InputWidget(d, key == SDLK_HOME ? lineFirst : lineLast);
1253 }
1174 refresh_Widget(w); 1254 refresh_Widget(w);
1175 return iTrue; 1255 return iTrue;
1176 case SDLK_a: 1256 case SDLK_a:
@@ -1216,19 +1296,20 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1216 /* Allow focus switching. */ 1296 /* Allow focus switching. */
1217 return processEvent_Widget(as_Widget(d), ev); 1297 return processEvent_Widget(as_Widget(d), ev);
1218 case SDLK_UP: 1298 case SDLK_UP:
1219 if (moveCursorByLine_InputWidget_(d, -1)) {
1220 refresh_Widget(d);
1221 return iTrue;
1222 }
1223 /* For moving to lookup from url entry. */
1224 return processEvent_Widget(as_Widget(d), ev);
1225 case SDLK_DOWN: 1299 case SDLK_DOWN:
1226 if (moveCursorByLine_InputWidget_(d, +1)) { 1300 if (moveCursorByLine_InputWidget_(d, key == SDLK_UP ? -1 : +1)) {
1227 refresh_Widget(d); 1301 refresh_Widget(d);
1228 return iTrue; 1302 return iTrue;
1229 } 1303 }
1230 /* For moving to lookup from url entry. */ 1304 /* For moving to lookup from url entry. */
1231 return processEvent_Widget(as_Widget(d), ev); 1305 return processEvent_Widget(as_Widget(d), ev);
1306 case SDLK_PAGEUP:
1307 case SDLK_PAGEDOWN:
1308 for (int count = 0; count < 5; count++) {
1309 moveCursorByLine_InputWidget_(d, key == SDLK_PAGEUP ? -1 : +1);
1310 }
1311 refresh_Widget(d);
1312 return iTrue;
1232 } 1313 }
1233 if (mods & (KMOD_PRIMARY | KMOD_SECONDARY)) { 1314 if (mods & (KMOD_PRIMARY | KMOD_SECONDARY)) {
1234 return iFalse; 1315 return iFalse;
@@ -1248,6 +1329,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1248 return processEvent_Widget(w, ev); 1329 return processEvent_Widget(w, ev);
1249} 1330}
1250 1331
1332#if 0
1251static iBool isWhite_(const iString *str) { 1333static iBool isWhite_(const iString *str) {
1252 iConstForEach(String, i, str) { 1334 iConstForEach(String, i, str) {
1253 if (!isSpace_Char(i.value)) { 1335 if (!isSpace_Char(i.value)) {
@@ -1256,11 +1338,12 @@ static iBool isWhite_(const iString *str) {
1256 } 1338 }
1257 return iTrue; 1339 return iTrue;
1258} 1340}
1341#endif
1259 1342
1260static void draw_InputWidget_(const iInputWidget *d) { 1343static void draw_InputWidget_(const iInputWidget *d) {
1261 const iWidget *w = constAs_Widget(d); 1344 const iWidget *w = constAs_Widget(d);
1262 iRect bounds = adjusted_Rect(bounds_InputWidget_(d), padding_(), neg_I2(padding_())); 1345 iRect bounds = adjusted_Rect(bounds_InputWidget_(d), padding_(), neg_I2(padding_()));
1263 iBool isHint = iFalse; 1346 iBool isHint = isHintVisible_InputWidget_(d);
1264 const iBool isFocused = isFocused_Widget(w); 1347 const iBool isFocused = isFocused_Widget(w);
1265 const iBool isHover = isHover_Widget(w) && 1348 const iBool isHover = isHover_Widget(w) &&
1266 contains_InputWidget_(d, mouseCoord_Window(get_Window())); 1349 contains_InputWidget_(d, mouseCoord_Window(get_Window()));
@@ -1283,40 +1366,52 @@ static void draw_InputWidget_(const iInputWidget *d) {
1283 isFocused ? gap_UI / 4 : 1, 1366 isFocused ? gap_UI / 4 : 1,
1284 isFocused ? uiInputFrameFocused_ColorId 1367 isFocused ? uiInputFrameFocused_ColorId
1285 : isHover ? uiInputFrameHover_ColorId : uiInputFrame_ColorId); 1368 : isHover ? uiInputFrameHover_ColorId : uiInputFrame_ColorId);
1286 setClip_Paint(&p, adjusted_Rect(bounds, init_I2(d->leftPadding, 0), init_I2(-d->rightPadding, 0))); 1369 setClip_Paint(&p, adjusted_Rect(bounds, init_I2(d->leftPadding, 0),
1370 init_I2(-d->rightPadding, w->flags & extraPadding_WidgetFlag ? -gap_UI / 2 : 0)));
1287 const iRect contentBounds = contentBounds_InputWidget_(d); 1371 const iRect contentBounds = contentBounds_InputWidget_(d);
1288// const iInt2 textOrigin = textOrigin_InputWidget_(d); //, cstr_String(text)); 1372// const iInt2 textOrigin = textOrigin_InputWidget_(d); //, cstr_String(text));
1289 iInt2 drawPos = topLeft_Rect(contentBounds); 1373 iInt2 drawPos = topLeft_Rect(contentBounds);
1290 const int fg = isHint ? uiAnnotation_ColorId 1374 const int fg = isHint ? uiAnnotation_ColorId
1291 : isFocused && !isEmpty_Array(&d->text) ? uiInputTextFocused_ColorId 1375 : isFocused && !isEmpty_Array(&d->text) ? uiInputTextFocused_ColorId
1292 : uiInputText_ColorId; 1376 : uiInputText_ColorId;
1293 /* TODO: If buffered, just draw the buffered copy. */ 1377 /* If buffered, just draw the buffered copy. */
1294 iConstForEach(Array, i, &d->lines) { 1378 if (d->buffered && !isFocused) { //&& !isFocused/* && !isHint*/) {
1295 const iInputLine *line = i.value; 1379 /* Most input widgets will use this, since only one is focused at a time. */
1296 const iBool isLast = index_ArrayConstIterator(&i) == size_Array(&d->lines) - 1; 1380 draw_TextBuf(d->buffered, topLeft_Rect(contentBounds), white_ColorId);
1297 const iInputLine *nextLine = isLast ? NULL : (line + 1); 1381 }
1298 const iRanges lineRange = { line->offset, 1382 else if (isHint) {
1299 nextLine ? nextLine->offset : size_Array(&d->text) }; 1383 drawRange_Text(d->font, topLeft_Rect(contentBounds), uiAnnotation_ColorId,
1300 if (isFocused && !isEmpty_Range(&d->mark)) { 1384 range_String(&d->hint));
1301 /* Draw the selected range. */ 1385 }
1302 const iRanges mark = mark_InputWidget_(d); 1386 else {
1303 if (mark.start < lineRange.end && mark.end > lineRange.start) { 1387 iConstForEach(Array, i, &d->lines) {
1304 const int m1 = advanceN_Text(d->font, 1388 const iInputLine *line = i.value;
1305 cstr_String(&line->text), 1389 const iBool isLast = index_ArrayConstIterator(&i) == size_Array(&d->lines) - 1;
1306 iMax(lineRange.start, mark.start) - line->offset) 1390 const iInputLine *nextLine = isLast ? NULL : (line + 1);
1307 .x; 1391 const iRanges lineRange = { line->offset,
1308 const int m2 = advanceN_Text(d->font, 1392 nextLine ? nextLine->offset : size_Array(&d->text) };
1309 cstr_String(&line->text), 1393 if (isFocused && !isEmpty_Range(&d->mark)) {
1310 iMin(lineRange.end, mark.end) - line->offset) 1394 /* Draw the selected range. */
1311 .x; 1395 const iRanges mark = mark_InputWidget_(d);
1312 fillRect_Paint(&p, 1396 if (mark.start < lineRange.end && mark.end > lineRange.start) {
1313 (iRect){ addX_I2(drawPos, iMin(m1, m2)), 1397 const int m1 = advanceN_Text(d->font,
1314 init_I2(iMax(gap_UI / 3, iAbs(m2 - m1)), lineHeight_Text(d->font)) }, 1398 cstr_String(&line->text),
1315 uiMarked_ColorId); 1399 iMax(lineRange.start, mark.start) - line->offset)
1400 .x;
1401 const int m2 = advanceN_Text(d->font,
1402 cstr_String(&line->text),
1403 iMin(lineRange.end, mark.end) - line->offset)
1404 .x;
1405 fillRect_Paint(&p,
1406 (iRect){ addX_I2(drawPos, iMin(m1, m2)),
1407 init_I2(iMax(gap_UI / 3, iAbs(m2 - m1)),
1408 lineHeight_Text(d->font)) },
1409 uiMarked_ColorId);
1410 }
1316 } 1411 }
1412 drawRange_Text(d->font, drawPos, fg, range_String(&line->text));
1413 drawPos.y += lineHeight_Text(d->font);
1317 } 1414 }
1318 drawRange_Text(d->font, drawPos, fg, range_String(&line->text));
1319 drawPos.y += lineHeight_Text(d->font);
1320 } 1415 }
1321// if (d->buffered && !isFocused && !isHint) { 1416// if (d->buffered && !isFocused && !isHint) {
1322// /* Most input widgets will use this, since only one is focused at a time. */ 1417// /* Most input widgets will use this, since only one is focused at a time. */
@@ -1378,13 +1473,7 @@ static void draw_InputWidget_(const iInputWidget *d) {
1378 drawChildren_Widget(w); 1473 drawChildren_Widget(w);
1379} 1474}
1380 1475
1381//static void sizeChanged_InputWidget_(iInputWidget *d) {
1382// printf("[InputWidget] %p: size changed, updating layout\n", d);
1383// updateLinesAndResize_InputWidget_(d, iFalse);
1384//}
1385
1386iBeginDefineSubclass(InputWidget, Widget) 1476iBeginDefineSubclass(InputWidget, Widget)
1387 .processEvent = (iAny *) processEvent_InputWidget_, 1477 .processEvent = (iAny *) processEvent_InputWidget_,
1388 .draw = (iAny *) draw_InputWidget_, 1478 .draw = (iAny *) draw_InputWidget_,
1389// .sizeChanged = (iAny *) sizeChanged_InputWidget_,
1390iEndDefineSubclass(InputWidget) 1479iEndDefineSubclass(InputWidget)