diff options
Diffstat (limited to 'src/ui/inputwidget.c')
-rw-r--r-- | src/ui/inputwidget.c | 267 |
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 | ||
43 | static void enableEditorKeysInMenus_(iBool enable) { | 43 | static 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 | ||
85 | struct Impl_InputLine { | 89 | struct 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 | ||
147 | iLocalDef iInt2 padding_(void) { | ||
148 | return init_I2(gap_UI / 2, gap_UI / 2); | ||
149 | } | ||
150 | |||
151 | static 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 | |||
141 | static void updateCursorLine_InputWidget_(iInputWidget *d) { | 165 | static 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 | ||
152 | static void showCursor_InputWidget_(iInputWidget *d) { | 191 | static void showCursor_InputWidget_(iInputWidget *d) { |
@@ -161,21 +200,6 @@ static void invalidateBuffered_InputWidget_(iInputWidget *d) { | |||
161 | } | 200 | } |
162 | } | 201 | } |
163 | 202 | ||
164 | iLocalDef iInt2 padding_(void) { | ||
165 | return init_I2(gap_UI / 2, gap_UI / 2); | ||
166 | } | ||
167 | |||
168 | static 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 | |||
179 | static void updateSizeForFixedLength_InputWidget_(iInputWidget *d) { | 203 | static 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 | ||
221 | static void updateLines_InputWidget_(iInputWidget *d) { | 245 | static 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 | ||
487 | static 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 | |||
458 | static void updateBuffered_InputWidget_(iInputWidget *d) { | 492 | static 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 | ||
708 | iLocalDef 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 | |||
660 | static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const iInputLine *line) { | 712 | static 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 | ||
708 | void setUrlContent_InputWidget(iInputWidget *d, iBool isUrl) { | 758 | void 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 | ||
712 | void setSelectAllOnFocus_InputWidget(iInputWidget *d, iBool selectAllOnFocus) { | 763 | void setSelectAllOnFocus_InputWidget(iInputWidget *d, iBool selectAllOnFocus) { |
@@ -826,7 +877,8 @@ static iInt2 textOrigin_InputWidget_(const iInputWidget *d) { //}, const char *v | |||
826 | 877 | ||
827 | static size_t coordIndex_InputWidget_(const iInputWidget *d, iInt2 coord) { | 878 | static 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 | ||
886 | static void extendRange_InputWidget_(iInputWidget *d, size_t *pos, int dir) { | 938 | static 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 | ||
1251 | static iBool isWhite_(const iString *str) { | 1333 | static 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 | ||
1260 | static void draw_InputWidget_(const iInputWidget *d) { | 1343 | static 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 | |||
1386 | iBeginDefineSubclass(InputWidget, Widget) | 1476 | iBeginDefineSubclass(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_, | ||
1390 | iEndDefineSubclass(InputWidget) | 1479 | iEndDefineSubclass(InputWidget) |