diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-05-11 18:14:33 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-05-11 18:14:33 +0300 |
commit | 15c5ab9ec7563fb6fcb49657e075729c18461c3b (patch) | |
tree | ac203b1966879f9793055f11d86967f49ff6256e /src/ui | |
parent | a13543aa922647ea2f8cc40cb9f3f797df8758df (diff) |
InputWidget: Word wrapping, inserting newlines
Diffstat (limited to 'src/ui')
-rw-r--r-- | src/ui/inputwidget.c | 433 | ||||
-rw-r--r-- | src/ui/root.c | 16 | ||||
-rw-r--r-- | src/ui/root.h | 4 | ||||
-rw-r--r-- | src/ui/text.c | 7 |
4 files changed, 357 insertions, 103 deletions
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c index a1635128..22dd4c92 100644 --- a/src/ui/inputwidget.c +++ b/src/ui/inputwidget.c | |||
@@ -77,6 +77,28 @@ enum iInputWidgetFlag { | |||
77 | needUpdateBuffer_InputWidgetFlag = iBit(9), | 77 | needUpdateBuffer_InputWidgetFlag = iBit(9), |
78 | }; | 78 | }; |
79 | 79 | ||
80 | /*----------------------------------------------------------------------------------------------*/ | ||
81 | |||
82 | iDeclareType(InputLine) | ||
83 | |||
84 | struct Impl_InputLine { | ||
85 | size_t offset; /* character position from the beginning */ | ||
86 | iString text; /* UTF-8 */ | ||
87 | }; | ||
88 | |||
89 | static void init_InputLine(iInputLine *d) { | ||
90 | d->offset = 0; | ||
91 | init_String(&d->text); | ||
92 | } | ||
93 | |||
94 | static void deinit_InputLine(iInputLine *d) { | ||
95 | deinit_String(&d->text); | ||
96 | } | ||
97 | |||
98 | iDefineTypeConstruction(InputLine) | ||
99 | |||
100 | /*----------------------------------------------------------------------------------------------*/ | ||
101 | |||
80 | struct Impl_InputWidget { | 102 | struct Impl_InputWidget { |
81 | iWidget widget; | 103 | iWidget widget; |
82 | enum iInputMode mode; | 104 | enum iInputMode mode; |
@@ -84,12 +106,15 @@ struct Impl_InputWidget { | |||
84 | size_t maxLen; | 106 | size_t maxLen; |
85 | iArray text; /* iChar[] */ | 107 | iArray text; /* iChar[] */ |
86 | iArray oldText; /* iChar[] */ | 108 | iArray oldText; /* iChar[] */ |
109 | iArray lines; | ||
87 | iString hint; | 110 | iString hint; |
88 | iString srcHint; | 111 | iString srcHint; |
89 | int leftPadding; | 112 | int leftPadding; |
90 | int rightPadding; | 113 | int rightPadding; |
91 | size_t cursor; | 114 | size_t cursor; /* offset from beginning */ |
92 | size_t lastCursor; | 115 | size_t lastCursor; |
116 | size_t cursorLine; | ||
117 | int verticalMoveX; | ||
93 | iRanges mark; | 118 | iRanges mark; |
94 | iRanges initialMark; | 119 | iRanges initialMark; |
95 | iArray undoStack; | 120 | iArray undoStack; |
@@ -109,8 +134,20 @@ static void clearUndo_InputWidget_(iInputWidget *d) { | |||
109 | clear_Array(&d->undoStack); | 134 | clear_Array(&d->undoStack); |
110 | } | 135 | } |
111 | 136 | ||
137 | static void updateCursorLine_InputWidget_(iInputWidget *d) { | ||
138 | d->cursorLine = 0; | ||
139 | iConstForEach(Array, i, &d->lines) { | ||
140 | const iInputLine *line = i.value; | ||
141 | if (line->offset > d->cursor) { | ||
142 | break; | ||
143 | } | ||
144 | d->cursorLine = index_ArrayConstIterator(&i); | ||
145 | } | ||
146 | } | ||
147 | |||
112 | static void showCursor_InputWidget_(iInputWidget *d) { | 148 | static void showCursor_InputWidget_(iInputWidget *d) { |
113 | d->cursorVis = 2; | 149 | d->cursorVis = 2; |
150 | updateCursorLine_InputWidget_(d); | ||
114 | } | 151 | } |
115 | 152 | ||
116 | static void invalidateBuffered_InputWidget_(iInputWidget *d) { | 153 | static void invalidateBuffered_InputWidget_(iInputWidget *d) { |
@@ -120,6 +157,20 @@ static void invalidateBuffered_InputWidget_(iInputWidget *d) { | |||
120 | } | 157 | } |
121 | } | 158 | } |
122 | 159 | ||
160 | iLocalDef iInt2 padding_(void) { | ||
161 | return init_I2(gap_UI / 2, gap_UI / 2); | ||
162 | } | ||
163 | |||
164 | static iRect contentBounds_InputWidget_(const iInputWidget *d) { | ||
165 | const iWidget *w = constAs_Widget(d); | ||
166 | iRect bounds = adjusted_Rect(bounds_Widget(w), | ||
167 | addX_I2(padding_(), d->leftPadding), | ||
168 | neg_I2(addX_I2(padding_(), d->rightPadding))); | ||
169 | shrink_Rect(&bounds, init_I2(gap_UI * (flags_Widget(w) & tight_WidgetFlag ? 1 : 2), 0)); | ||
170 | bounds.pos.y += padding_().y / 2; | ||
171 | return bounds; | ||
172 | } | ||
173 | |||
123 | static void updateSizeForFixedLength_InputWidget_(iInputWidget *d) { | 174 | static void updateSizeForFixedLength_InputWidget_(iInputWidget *d) { |
124 | if (d->maxLen) { | 175 | if (d->maxLen) { |
125 | /* Set a fixed size based on maximum possible width of the text. */ | 176 | /* Set a fixed size based on maximum possible width of the text. */ |
@@ -135,17 +186,97 @@ static void updateSizeForFixedLength_InputWidget_(iInputWidget *d) { | |||
135 | } | 186 | } |
136 | } | 187 | } |
137 | 188 | ||
189 | static const iChar sensitiveChar_ = 0x25cf; /* black circle */ | ||
190 | |||
191 | static iString *utf32toUtf8_InputWidget_(const iInputWidget *d) { | ||
192 | return newUnicodeN_String(constData_Array(&d->text), size_Array(&d->text)); | ||
193 | } | ||
194 | |||
195 | static iString *visText_InputWidget_(const iInputWidget *d) { | ||
196 | iString *text; | ||
197 | if (~d->inFlags & isSensitive_InputWidgetFlag) { | ||
198 | text = utf32toUtf8_InputWidget_(d); | ||
199 | } | ||
200 | else { | ||
201 | text = new_String(); | ||
202 | for (size_t i = 0; i < size_Array(&d->text); ++i) { | ||
203 | appendChar_String(text, sensitiveChar_); | ||
204 | } | ||
205 | } | ||
206 | return text; | ||
207 | } | ||
208 | |||
209 | static void clearLines_InputWidget_(iInputWidget *d) { | ||
210 | iForEach(Array, i, &d->lines) { | ||
211 | deinit_InputLine(i.value); | ||
212 | } | ||
213 | clear_Array(&d->lines); | ||
214 | } | ||
215 | |||
216 | static void updateLines_InputWidget_(iInputWidget *d) { | ||
217 | clearLines_InputWidget_(d); | ||
218 | if (d->maxLen) { | ||
219 | /* Everything on a single line. */ | ||
220 | iInputLine line; | ||
221 | init_InputLine(&line); | ||
222 | iString *u8 = visText_InputWidget_(d); | ||
223 | set_String(&line.text, u8); | ||
224 | delete_String(u8); | ||
225 | pushBack_Array(&d->lines, &line); | ||
226 | updateCursorLine_InputWidget_(d); | ||
227 | return; | ||
228 | } | ||
229 | /* Word-wrapped lines. */ | ||
230 | iString *u8 = visText_InputWidget_(d); | ||
231 | size_t charPos = 0; | ||
232 | iRangecc content = range_String(u8); | ||
233 | const int wrapWidth = contentBounds_InputWidget_(d).size.x; | ||
234 | while (wrapWidth > 0 && content.end != content.start) { | ||
235 | const char *endPos; | ||
236 | tryAdvance_Text(d->font, content, wrapWidth, &endPos); | ||
237 | const iRangecc part = (iRangecc){ content.start, endPos }; | ||
238 | iInputLine line; | ||
239 | init_InputLine(&line); | ||
240 | setRange_String(&line.text, part); | ||
241 | line.offset = charPos; | ||
242 | pushBack_Array(&d->lines, &line); | ||
243 | charPos += length_String(&line.text); | ||
244 | content.start = endPos; | ||
245 | } | ||
246 | if (isEmpty_Array(&d->lines) || endsWith_String(u8, "\n")) { | ||
247 | /* Always at least one empty line. */ | ||
248 | iInputLine line; | ||
249 | init_InputLine(&line); | ||
250 | pushBack_Array(&d->lines, &line); | ||
251 | } | ||
252 | else { | ||
253 | iAssert(charPos == length_String(u8)); | ||
254 | } | ||
255 | delete_String(u8); | ||
256 | updateCursorLine_InputWidget_(d); | ||
257 | } | ||
258 | |||
259 | static int contentHeight_InputWidget_(const iInputWidget *d) { | ||
260 | return iMax(1, size_Array(&d->lines)) * lineHeight_Text(d->font); | ||
261 | } | ||
262 | |||
138 | static void updateMetrics_InputWidget_(iInputWidget *d) { | 263 | static void updateMetrics_InputWidget_(iInputWidget *d) { |
139 | iWidget *w = as_Widget(d); | 264 | iWidget *w = as_Widget(d); |
140 | updateSizeForFixedLength_InputWidget_(d); | 265 | updateSizeForFixedLength_InputWidget_(d); |
141 | /* Caller must arrange the width, but the height is fixed. */ | 266 | /* Caller must arrange the width, but the height is fixed. */ |
142 | w->rect.size.y = lineHeight_Text(d->font) * 1.3f; | 267 | w->rect.size.y = contentHeight_InputWidget_(d) + 3 * padding_().y; /* TODO: Why 3x? */ |
143 | if (flags_Widget(w) & extraPadding_WidgetFlag) { | 268 | if (flags_Widget(w) & extraPadding_WidgetFlag) { |
144 | w->rect.size.y += 2 * gap_UI; | 269 | w->rect.size.y += 2 * gap_UI; |
145 | } | 270 | } |
146 | invalidateBuffered_InputWidget_(d); | 271 | invalidateBuffered_InputWidget_(d); |
147 | if (parent_Widget(w)) { | 272 | postCommand_Widget(d, "input.resized"); |
148 | arrange_Widget(w); | 273 | } |
274 | |||
275 | static void updateLinesAndResize_InputWidget_(iInputWidget *d) { | ||
276 | const size_t oldCount = size_Array(&d->lines); | ||
277 | updateLines_InputWidget_(d); | ||
278 | if (oldCount != size_Array(&d->lines)) { | ||
279 | updateMetrics_InputWidget_(d); | ||
149 | } | 280 | } |
150 | } | 281 | } |
151 | 282 | ||
@@ -158,6 +289,7 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) { | |||
158 | #endif | 289 | #endif |
159 | init_Array(&d->text, sizeof(iChar)); | 290 | init_Array(&d->text, sizeof(iChar)); |
160 | init_Array(&d->oldText, sizeof(iChar)); | 291 | init_Array(&d->oldText, sizeof(iChar)); |
292 | init_Array(&d->lines, sizeof(iInputLine)); | ||
161 | init_String(&d->hint); | 293 | init_String(&d->hint); |
162 | init_String(&d->srcHint); | 294 | init_String(&d->srcHint); |
163 | init_Array(&d->undoStack, sizeof(iInputUndo)); | 295 | init_Array(&d->undoStack, sizeof(iInputUndo)); |
@@ -166,6 +298,8 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) { | |||
166 | d->rightPadding = 0; | 298 | d->rightPadding = 0; |
167 | d->cursor = 0; | 299 | d->cursor = 0; |
168 | d->lastCursor = 0; | 300 | d->lastCursor = 0; |
301 | d->cursorLine = 0; | ||
302 | d->verticalMoveX = -1; | ||
169 | d->inFlags = eatEscape_InputWidgetFlag; | 303 | d->inFlags = eatEscape_InputWidgetFlag; |
170 | iZap(d->mark); | 304 | iZap(d->mark); |
171 | setMaxLen_InputWidget(d, maxLen); | 305 | setMaxLen_InputWidget(d, maxLen); |
@@ -174,10 +308,12 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) { | |||
174 | d->timer = 0; | 308 | d->timer = 0; |
175 | d->cursorVis = 0; | 309 | d->cursorVis = 0; |
176 | d->buffered = NULL; | 310 | d->buffered = NULL; |
311 | updateLines_InputWidget_(d); | ||
177 | updateMetrics_InputWidget_(d); | 312 | updateMetrics_InputWidget_(d); |
178 | } | 313 | } |
179 | 314 | ||
180 | void deinit_InputWidget(iInputWidget *d) { | 315 | void deinit_InputWidget(iInputWidget *d) { |
316 | clearLines_InputWidget_(d); | ||
181 | if (isSelected_Widget(d)) { | 317 | if (isSelected_Widget(d)) { |
182 | SDL_StopTextInput(); | 318 | SDL_StopTextInput(); |
183 | enableEditorKeysInMenus_(iTrue); | 319 | enableEditorKeysInMenus_(iTrue); |
@@ -190,6 +326,7 @@ void deinit_InputWidget(iInputWidget *d) { | |||
190 | } | 326 | } |
191 | deinit_String(&d->srcHint); | 327 | deinit_String(&d->srcHint); |
192 | deinit_String(&d->hint); | 328 | deinit_String(&d->hint); |
329 | deinit_Array(&d->lines); | ||
193 | deinit_Array(&d->oldText); | 330 | deinit_Array(&d->oldText); |
194 | deinit_Array(&d->text); | 331 | deinit_Array(&d->text); |
195 | } | 332 | } |
@@ -199,6 +336,11 @@ void setFont_InputWidget(iInputWidget *d, int fontId) { | |||
199 | updateMetrics_InputWidget_(d); | 336 | updateMetrics_InputWidget_(d); |
200 | } | 337 | } |
201 | 338 | ||
339 | static const iInputLine *line_InputWidget_(const iInputWidget *d, size_t index) { | ||
340 | iAssert(!isEmpty_Array(&d->lines)); | ||
341 | return constAt_Array(&d->lines, index); | ||
342 | } | ||
343 | |||
202 | static void pushUndo_InputWidget_(iInputWidget *d) { | 344 | static void pushUndo_InputWidget_(iInputWidget *d) { |
203 | iInputUndo undo; | 345 | iInputUndo undo; |
204 | init_InputUndo_(&undo, &d->text, d->cursor); | 346 | init_InputUndo_(&undo, &d->text, d->cursor); |
@@ -241,10 +383,6 @@ static const iString *omitDefaultScheme_(iString *url) { | |||
241 | return url; | 383 | return url; |
242 | } | 384 | } |
243 | 385 | ||
244 | static iString *utf32toUtf8_InputWidget_(const iInputWidget *d) { | ||
245 | return newUnicodeN_String(constData_Array(&d->text), size_Array(&d->text)); | ||
246 | } | ||
247 | |||
248 | const iString *text_InputWidget(const iInputWidget *d) { | 386 | const iString *text_InputWidget(const iInputWidget *d) { |
249 | if (d) { | 387 | if (d) { |
250 | iString *text = collect_String(utf32toUtf8_InputWidget_(d)); | 388 | iString *text = collect_String(utf32toUtf8_InputWidget_(d)); |
@@ -285,27 +423,13 @@ void setContentPadding_InputWidget(iInputWidget *d, int left, int right) { | |||
285 | refresh_Widget(d); | 423 | refresh_Widget(d); |
286 | } | 424 | } |
287 | 425 | ||
288 | static const iChar sensitiveChar_ = 0x25cf; /* black circle */ | ||
289 | |||
290 | static iString *visText_InputWidget_(const iInputWidget *d) { | ||
291 | iString *text; | ||
292 | if (~d->inFlags & isSensitive_InputWidgetFlag) { | ||
293 | text = newUnicodeN_String(constData_Array(&d->text), size_Array(&d->text)); | ||
294 | } | ||
295 | else { | ||
296 | text = new_String(); | ||
297 | for (size_t i = 0; i < size_Array(&d->text); ++i) { | ||
298 | appendChar_String(text, sensitiveChar_); | ||
299 | } | ||
300 | } | ||
301 | return text; | ||
302 | } | ||
303 | |||
304 | static void updateBuffered_InputWidget_(iInputWidget *d) { | 426 | static void updateBuffered_InputWidget_(iInputWidget *d) { |
305 | iWindow *win = get_Window(); | 427 | // iWindow *win = get_Window(); |
306 | invalidateBuffered_InputWidget_(d); | 428 | invalidateBuffered_InputWidget_(d); |
307 | iString *bufText = NULL; | 429 | iString *bufText = NULL; |
430 | #if 0 | ||
308 | if (d->inFlags & isUrl_InputWidgetFlag && as_Widget(d)->root == win->keyRoot) { | 431 | if (d->inFlags & isUrl_InputWidgetFlag && as_Widget(d)->root == win->keyRoot) { |
432 | /* TODO: Move this omitting to `updateLines_`? */ | ||
309 | /* Highlight the host name. */ | 433 | /* Highlight the host name. */ |
310 | iUrl parts; | 434 | iUrl parts; |
311 | const iString *text = collect_String(utf32toUtf8_InputWidget_(d)); | 435 | const iString *text = collect_String(utf32toUtf8_InputWidget_(d)); |
@@ -319,6 +443,7 @@ static void updateBuffered_InputWidget_(iInputWidget *d) { | |||
319 | appendRange_String(bufText, (iRangecc){ parts.host.end, constEnd_String(text) }); | 443 | appendRange_String(bufText, (iRangecc){ parts.host.end, constEnd_String(text) }); |
320 | } | 444 | } |
321 | } | 445 | } |
446 | #endif | ||
322 | if (!bufText) { | 447 | if (!bufText) { |
323 | bufText = visText_InputWidget_(d); | 448 | bufText = visText_InputWidget_(d); |
324 | } | 449 | } |
@@ -350,7 +475,7 @@ void setText_InputWidget(iInputWidget *d, const iString *text) { | |||
350 | } | 475 | } |
351 | if (isFocused_Widget(d)) { | 476 | if (isFocused_Widget(d)) { |
352 | d->cursor = size_Array(&d->text); | 477 | d->cursor = size_Array(&d->text); |
353 | selectAll_InputWidget(d); | 478 | // selectAll_InputWidget(d); |
354 | } | 479 | } |
355 | else { | 480 | else { |
356 | d->cursor = iMin(d->cursor, size_Array(&d->text)); | 481 | d->cursor = iMin(d->cursor, size_Array(&d->text)); |
@@ -359,6 +484,7 @@ void setText_InputWidget(iInputWidget *d, const iString *text) { | |||
359 | if (!isFocused_Widget(d)) { | 484 | if (!isFocused_Widget(d)) { |
360 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; | 485 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; |
361 | } | 486 | } |
487 | updateLinesAndResize_InputWidget_(d); | ||
362 | refresh_Widget(as_Widget(d)); | 488 | refresh_Widget(as_Widget(d)); |
363 | } | 489 | } |
364 | 490 | ||
@@ -404,6 +530,7 @@ void begin_InputWidget(iInputWidget *d) { | |||
404 | else { | 530 | else { |
405 | d->cursor = iMin(size_Array(&d->text), d->maxLen - 1); | 531 | d->cursor = iMin(size_Array(&d->text), d->maxLen - 1); |
406 | } | 532 | } |
533 | updateCursorLine_InputWidget_(d); | ||
407 | SDL_StartTextInput(); | 534 | SDL_StartTextInput(); |
408 | setFlags_Widget(w, selected_WidgetFlag, iTrue); | 535 | setFlags_Widget(w, selected_WidgetFlag, iTrue); |
409 | showCursor_InputWidget_(d); | 536 | showCursor_InputWidget_(d); |
@@ -436,6 +563,7 @@ void end_InputWidget(iInputWidget *d, iBool accept) { | |||
436 | setFlags_Widget(w, selected_WidgetFlag, iFalse); | 563 | setFlags_Widget(w, selected_WidgetFlag, iFalse); |
437 | const char *id = cstr_String(id_Widget(as_Widget(d))); | 564 | const char *id = cstr_String(id_Widget(as_Widget(d))); |
438 | if (!*id) id = "_"; | 565 | if (!*id) id = "_"; |
566 | updateLinesAndResize_InputWidget_(d); | ||
439 | refresh_Widget(w); | 567 | refresh_Widget(w); |
440 | postCommand_Widget(w, | 568 | postCommand_Widget(w, |
441 | "input.ended id:%s enter:%d arg:%d", | 569 | "input.ended id:%s enter:%d arg:%d", |
@@ -476,7 +604,6 @@ iLocalDef iBool isMarking_(void) { | |||
476 | } | 604 | } |
477 | 605 | ||
478 | void setCursor_InputWidget(iInputWidget *d, size_t pos) { | 606 | void setCursor_InputWidget(iInputWidget *d, size_t pos) { |
479 | showCursor_InputWidget_(d); | ||
480 | if (isEmpty_Array(&d->text)) { | 607 | if (isEmpty_Array(&d->text)) { |
481 | d->cursor = 0; | 608 | d->cursor = 0; |
482 | } | 609 | } |
@@ -496,6 +623,51 @@ void setCursor_InputWidget(iInputWidget *d, size_t pos) { | |||
496 | else { | 623 | else { |
497 | iZap(d->mark); | 624 | iZap(d->mark); |
498 | } | 625 | } |
626 | showCursor_InputWidget_(d); | ||
627 | } | ||
628 | |||
629 | static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const iInputLine *line) { | ||
630 | if (x <= 0) { | ||
631 | return line->offset; | ||
632 | } | ||
633 | const char *endPos; | ||
634 | tryAdvanceNoWrap_Text(d->font, range_String(&line->text), x, &endPos); | ||
635 | size_t index = line->offset; | ||
636 | if (endPos == constEnd_String(&line->text)) { | ||
637 | index += length_String(&line->text); | ||
638 | } | ||
639 | else { | ||
640 | /* Need to know the actual character index. */ | ||
641 | /* TODO: tryAdvance could tell us this directly with an extra return value */ | ||
642 | iConstForEach(String, i, &line->text) { | ||
643 | if (i.pos >= endPos) break; | ||
644 | index++; | ||
645 | } | ||
646 | } | ||
647 | return index; | ||
648 | } | ||
649 | |||
650 | static iBool moveCursorByLine_InputWidget_(iInputWidget *d, int dir) { | ||
651 | const iInputLine *line = line_InputWidget_(d, d->cursorLine); | ||
652 | int xPos = advanceN_Text(d->font, cstr_String(&line->text), d->cursor - line->offset).x; | ||
653 | size_t newCursor = iInvalidPos; | ||
654 | const size_t numLines = size_Array(&d->lines); | ||
655 | if (dir < 0 && d->cursorLine > 0) { | ||
656 | newCursor = indexForRelativeX_InputWidget_(d, xPos, --line); | ||
657 | } | ||
658 | else if (dir > 0 && d->cursorLine < numLines - 1) { | ||
659 | newCursor = indexForRelativeX_InputWidget_(d, xPos, ++line); | ||
660 | } | ||
661 | if (newCursor != iInvalidPos) { | ||
662 | /* Clamp it to the current line. */ | ||
663 | newCursor = iMax(newCursor, line->offset); | ||
664 | newCursor = iMin(newCursor, line->offset + length_String(&line->text) - | ||
665 | /* last line is allowed to go to the cursorMax */ | ||
666 | ((const void *) line < constAt_Array(&d->lines, numLines - 1) ? 1 : 0)); | ||
667 | setCursor_InputWidget(d, newCursor); | ||
668 | return iTrue; | ||
669 | } | ||
670 | return iFalse; | ||
499 | } | 671 | } |
500 | 672 | ||
501 | void setSensitiveContent_InputWidget(iInputWidget *d, iBool isSensitive) { | 673 | void setSensitiveContent_InputWidget(iInputWidget *d, iBool isSensitive) { |
@@ -526,6 +698,7 @@ static iRanges mark_InputWidget_(const iInputWidget *d) { | |||
526 | } | 698 | } |
527 | 699 | ||
528 | static void contentsWereChanged_InputWidget_(iInputWidget *d) { | 700 | static void contentsWereChanged_InputWidget_(iInputWidget *d) { |
701 | updateLinesAndResize_InputWidget_(d); | ||
529 | if (d->inFlags & notifyEdits_InputWidgetFlag) { | 702 | if (d->inFlags & notifyEdits_InputWidgetFlag) { |
530 | postCommand_Widget(d, "input.edited id:%s", cstr_String(id_Widget(constAs_Widget(d)))); | 703 | postCommand_Widget(d, "input.edited id:%s", cstr_String(id_Widget(constAs_Widget(d)))); |
531 | } | 704 | } |
@@ -591,21 +764,18 @@ static size_t skipWord_InputWidget_(const iInputWidget *d, size_t pos, int dir) | |||
591 | return pos; | 764 | return pos; |
592 | } | 765 | } |
593 | 766 | ||
594 | iLocalDef iInt2 padding_(void) { | 767 | #if 0 |
595 | return init_I2(gap_UI / 2, gap_UI / 2); | 768 | static iInt2 textOrigin_InputWidget_(const iInputWidget *d) { //}, const char *visText) { |
596 | } | 769 | // const iWidget *w = constAs_Widget(d); |
597 | 770 | iRect bounds = contentBounds_InputWidget_(d);/* adjusted_Rect(bounds_Widget(w), | |
598 | static iInt2 textOrigin_InputWidget_(const iInputWidget *d, const char *visText) { | ||
599 | const iWidget *w = constAs_Widget(d); | ||
600 | iRect bounds = adjusted_Rect(bounds_Widget(w), | ||
601 | addX_I2(padding_(), d->leftPadding), | 771 | addX_I2(padding_(), d->leftPadding), |
602 | neg_I2(addX_I2(padding_(), d->rightPadding))); | 772 | neg_I2(addX_I2(padding_(), d->rightPadding)));*/ |
603 | const iInt2 emSize = advance_Text(d->font, "M"); | 773 | // const iInt2 emSize = advance_Text(d->font, "M"); |
604 | const int textWidth = advance_Text(d->font, visText).x; | 774 | // const int textWidth = advance_Text(d->font, visText).x; |
605 | const int cursorX = advanceN_Text(d->font, visText, d->cursor).x; | 775 | // const int cursorX = advanceN_Text(d->font, visText, d->cursor).x; |
606 | int xOff = 0; | 776 | // int xOff = 0; |
607 | shrink_Rect(&bounds, init_I2(gap_UI * (flags_Widget(w) & tight_WidgetFlag ? 1 : 2), 0)); | 777 | // shrink_Rect(&bounds, init_I2(gap_UI * (flags_Widget(w) & tight_WidgetFlag ? 1 : 2), 0)); |
608 | if (d->maxLen == 0) { | 778 | /* if (d->maxLen == 0) { |
609 | if (textWidth > width_Rect(bounds) - emSize.x) { | 779 | if (textWidth > width_Rect(bounds) - emSize.x) { |
610 | xOff = width_Rect(bounds) - emSize.x - textWidth; | 780 | xOff = width_Rect(bounds) - emSize.x - textWidth; |
611 | } | 781 | } |
@@ -613,32 +783,20 @@ static iInt2 textOrigin_InputWidget_(const iInputWidget *d, const char *visText) | |||
613 | xOff = width_Rect(bounds) / 2 - cursorX; | 783 | xOff = width_Rect(bounds) / 2 - cursorX; |
614 | } | 784 | } |
615 | xOff = iMin(xOff, 0); | 785 | xOff = iMin(xOff, 0); |
616 | } | 786 | }*/ |
617 | const int yOff = (height_Rect(bounds) - lineHeight_Text(d->font)) / 2; | 787 | // const int yOff = 0.3f * lineHeight_Text(d->font); // (height_Rect(bounds) - lineHeight_Text(d->font)) / 2; |
618 | return add_I2(topLeft_Rect(bounds), init_I2(xOff, yOff)); | 788 | // return addY_I2(topLeft_Rect(bounds), yOff); |
789 | |||
619 | } | 790 | } |
791 | #endif | ||
620 | 792 | ||
621 | static size_t coordIndex_InputWidget_(const iInputWidget *d, iInt2 coord) { | 793 | static size_t coordIndex_InputWidget_(const iInputWidget *d, iInt2 coord) { |
622 | iString *visText = visText_InputWidget_(d); | 794 | const iInt2 pos = sub_I2(coord, contentBounds_InputWidget_(d).pos); |
623 | iInt2 pos = sub_I2(coord, textOrigin_InputWidget_(d, cstr_String(visText))); | 795 | const size_t lineNumber = iMin(pos.y / lineHeight_Text(d->font), (int) size_Array(&d->lines) - 1); |
624 | size_t index = 0; | 796 | const iInputLine *line = line_InputWidget_(d, lineNumber); |
625 | if (pos.x > 0) { | 797 | const char *endPos; |
626 | const char *endPos; | 798 | tryAdvanceNoWrap_Text(d->font, range_String(&line->text), pos.x, &endPos); |
627 | tryAdvanceNoWrap_Text(d->font, range_String(visText), pos.x, &endPos); | 799 | return indexForRelativeX_InputWidget_(d, pos.x, line); |
628 | if (endPos == constEnd_String(visText)) { | ||
629 | index = cursorMax_InputWidget_(d); | ||
630 | } | ||
631 | else { | ||
632 | /* Need to know the actual character index. */ | ||
633 | /* TODO: tryAdvance could tell us this directly with an extra return value */ | ||
634 | iConstForEach(String, i, visText) { | ||
635 | if (i.pos >= endPos) break; | ||
636 | index++; | ||
637 | } | ||
638 | } | ||
639 | } | ||
640 | delete_String(visText); | ||
641 | return index; | ||
642 | } | 800 | } |
643 | 801 | ||
644 | static iBool copy_InputWidget_(iInputWidget *d, iBool doCut) { | 802 | static iBool copy_InputWidget_(iInputWidget *d, iBool doCut) { |
@@ -683,6 +841,14 @@ static iChar at_InputWidget_(const iInputWidget *d, size_t pos) { | |||
683 | return *(const iChar *) constAt_Array(&d->text, pos); | 841 | return *(const iChar *) constAt_Array(&d->text, pos); |
684 | } | 842 | } |
685 | 843 | ||
844 | static iRanges lineRange_InputWidget_(const iInputWidget *d) { | ||
845 | if (isEmpty_Array(&d->lines)) { | ||
846 | return (iRanges){ 0, 0 }; | ||
847 | } | ||
848 | const iInputLine *line = line_InputWidget_(d, d->cursorLine); | ||
849 | return (iRanges){ line->offset, line->offset + length_String(&line->text) }; | ||
850 | } | ||
851 | |||
686 | static void extendRange_InputWidget_(iInputWidget *d, size_t *pos, int dir) { | 852 | static void extendRange_InputWidget_(iInputWidget *d, size_t *pos, int dir) { |
687 | const size_t textLen = size_Array(&d->text); | 853 | const size_t textLen = size_Array(&d->text); |
688 | if (dir < 0 && *pos > 0) { | 854 | if (dir < 0 && *pos > 0) { |
@@ -746,12 +912,14 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
746 | } | 912 | } |
747 | else if (isMetricsChange_UserEvent(ev)) { | 913 | else if (isMetricsChange_UserEvent(ev)) { |
748 | updateMetrics_InputWidget_(d); | 914 | updateMetrics_InputWidget_(d); |
915 | updateLinesAndResize_InputWidget_(d); | ||
749 | } | 916 | } |
750 | else if (isResize_UserEvent(ev)) { | 917 | else if (isResize_UserEvent(ev)) { |
751 | if (d->inFlags & isUrl_InputWidgetFlag) { | 918 | if (d->inFlags & isUrl_InputWidgetFlag) { |
752 | /* Restore/omit the default scheme if necessary. */ | 919 | /* Restore/omit the default scheme if necessary. */ |
753 | setText_InputWidget(d, text_InputWidget(d)); | 920 | setText_InputWidget(d, text_InputWidget(d)); |
754 | } | 921 | } |
922 | updateLinesAndResize_InputWidget_(d); | ||
755 | } | 923 | } |
756 | else if (isFocused_Widget(d) && isCommand_UserEvent(ev, "copy")) { | 924 | else if (isFocused_Widget(d) && isCommand_UserEvent(ev, "copy")) { |
757 | copy_InputWidget_(d, iFalse); | 925 | copy_InputWidget_(d, iFalse); |
@@ -790,8 +958,8 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
790 | d->inFlags &= ~isMarking_InputWidgetFlag; | 958 | d->inFlags &= ~isMarking_InputWidgetFlag; |
791 | return iTrue; | 959 | return iTrue; |
792 | case drag_ClickResult: | 960 | case drag_ClickResult: |
793 | showCursor_InputWidget_(d); | ||
794 | d->cursor = coordIndex_InputWidget_(d, pos_Click(&d->click)); | 961 | d->cursor = coordIndex_InputWidget_(d, pos_Click(&d->click)); |
962 | showCursor_InputWidget_(d); | ||
795 | if (~d->inFlags & isMarking_InputWidgetFlag) { | 963 | if (~d->inFlags & isMarking_InputWidgetFlag) { |
796 | d->inFlags |= isMarking_InputWidgetFlag; | 964 | d->inFlags |= isMarking_InputWidgetFlag; |
797 | d->mark.start = d->cursor; | 965 | d->mark.start = d->cursor; |
@@ -822,7 +990,10 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
822 | if (ev->type == SDL_KEYUP && isFocused_Widget(w)) { | 990 | if (ev->type == SDL_KEYUP && isFocused_Widget(w)) { |
823 | return iTrue; | 991 | return iTrue; |
824 | } | 992 | } |
825 | const size_t curMax = cursorMax_InputWidget_(d); | 993 | const size_t curMax = cursorMax_InputWidget_(d); |
994 | const iRanges lineRange = lineRange_InputWidget_(d); | ||
995 | const size_t lineFirst = lineRange.start; | ||
996 | const size_t lineLast = lineRange.end == curMax ? curMax : iMax(lineRange.start, lineRange.end - 1); | ||
826 | if (ev->type == SDL_KEYDOWN && isFocused_Widget(w)) { | 997 | if (ev->type == SDL_KEYDOWN && isFocused_Widget(w)) { |
827 | const int key = ev->key.keysym.sym; | 998 | const int key = ev->key.keysym.sym; |
828 | const int mods = keyMods_Sym(ev->key.keysym.mod); | 999 | const int mods = keyMods_Sym(ev->key.keysym.mod); |
@@ -852,6 +1023,13 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
852 | return iTrue; | 1023 | return iTrue; |
853 | case SDLK_RETURN: | 1024 | case SDLK_RETURN: |
854 | case SDLK_KP_ENTER: | 1025 | case SDLK_KP_ENTER: |
1026 | if (mods == KMOD_SHIFT) { | ||
1027 | pushUndo_InputWidget_(d); | ||
1028 | deleteMarked_InputWidget_(d); | ||
1029 | insertChar_InputWidget_(d, '\n'); | ||
1030 | contentsWereChanged_InputWidget_(d); | ||
1031 | return iTrue; | ||
1032 | } | ||
855 | d->inFlags |= enterPressed_InputWidgetFlag; | 1033 | d->inFlags |= enterPressed_InputWidgetFlag; |
856 | setFocus_Widget(NULL); | 1034 | setFocus_Widget(NULL); |
857 | return iTrue; | 1035 | return iTrue; |
@@ -927,7 +1105,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
927 | break; | 1105 | break; |
928 | case SDLK_HOME: | 1106 | case SDLK_HOME: |
929 | case SDLK_END: | 1107 | case SDLK_END: |
930 | setCursor_InputWidget(d, key == SDLK_HOME ? 0 : curMax); | 1108 | setCursor_InputWidget(d, key == SDLK_HOME ? lineFirst : lineLast); |
931 | refresh_Widget(w); | 1109 | refresh_Widget(w); |
932 | return iTrue; | 1110 | return iTrue; |
933 | case SDLK_a: | 1111 | case SDLK_a: |
@@ -944,7 +1122,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
944 | /* fall through for Emacs-style Home/End */ | 1122 | /* fall through for Emacs-style Home/End */ |
945 | case SDLK_e: | 1123 | case SDLK_e: |
946 | if (mods == KMOD_CTRL || mods == (KMOD_CTRL | KMOD_SHIFT)) { | 1124 | if (mods == KMOD_CTRL || mods == (KMOD_CTRL | KMOD_SHIFT)) { |
947 | setCursor_InputWidget(d, key == 'a' ? 0 : curMax); | 1125 | setCursor_InputWidget(d, key == 'a' ? lineFirst : lineLast); |
948 | refresh_Widget(w); | 1126 | refresh_Widget(w); |
949 | return iTrue; | 1127 | return iTrue; |
950 | } | 1128 | } |
@@ -953,7 +1131,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
953 | case SDLK_RIGHT: { | 1131 | case SDLK_RIGHT: { |
954 | const int dir = (key == SDLK_LEFT ? -1 : +1); | 1132 | const int dir = (key == SDLK_LEFT ? -1 : +1); |
955 | if (mods & byLine_KeyModifier) { | 1133 | if (mods & byLine_KeyModifier) { |
956 | setCursor_InputWidget(d, dir < 0 ? 0 : curMax); | 1134 | setCursor_InputWidget(d, dir < 0 ? lineFirst : lineLast); |
957 | } | 1135 | } |
958 | else if (mods & byWord_KeyModifier) { | 1136 | else if (mods & byWord_KeyModifier) { |
959 | setCursor_InputWidget(d, skipWord_InputWidget_(d, d->cursor, dir)); | 1137 | setCursor_InputWidget(d, skipWord_InputWidget_(d, d->cursor, dir)); |
@@ -970,9 +1148,21 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
970 | return iTrue; | 1148 | return iTrue; |
971 | } | 1149 | } |
972 | case SDLK_TAB: | 1150 | case SDLK_TAB: |
973 | case SDLK_DOWN: /* for moving to lookup from url entry */ | ||
974 | /* Allow focus switching. */ | 1151 | /* Allow focus switching. */ |
975 | return processEvent_Widget(as_Widget(d), ev); | 1152 | return processEvent_Widget(as_Widget(d), ev); |
1153 | case SDLK_UP: | ||
1154 | if (moveCursorByLine_InputWidget_(d, -1)) { | ||
1155 | refresh_Widget(d); | ||
1156 | return iTrue; | ||
1157 | } | ||
1158 | break; | ||
1159 | case SDLK_DOWN: | ||
1160 | if (moveCursorByLine_InputWidget_(d, +1)) { | ||
1161 | refresh_Widget(d); | ||
1162 | return iTrue; | ||
1163 | } | ||
1164 | /* For moving to lookup from url entry. */ | ||
1165 | return processEvent_Widget(as_Widget(d), ev); | ||
976 | } | 1166 | } |
977 | if (mods & (KMOD_PRIMARY | KMOD_SECONDARY)) { | 1167 | if (mods & (KMOD_PRIMARY | KMOD_SECONDARY)) { |
978 | return iFalse; | 1168 | return iFalse; |
@@ -1013,11 +1203,13 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
1013 | } | 1203 | } |
1014 | iPaint p; | 1204 | iPaint p; |
1015 | init_Paint(&p); | 1205 | init_Paint(&p); |
1016 | iString *text = visText_InputWidget_(d); | 1206 | /* `lines` is already up to date and ready for drawing. */ |
1017 | if (isWhite_(text) && !isEmpty_String(&d->hint)) { | 1207 | /* TODO: If empty, draw the hint. */ |
1018 | set_String(text, &d->hint); | 1208 | // iString *text = visText_InputWidget_(d); |
1019 | isHint = iTrue; | 1209 | // if (isWhite_(text) && !isEmpty_String(&d->hint)) { |
1020 | } | 1210 | // set_String(text, &d->hint); |
1211 | // isHint = iTrue; | ||
1212 | // } | ||
1021 | fillRect_Paint( | 1213 | fillRect_Paint( |
1022 | &p, bounds, isFocused ? uiInputBackgroundFocused_ColorId : uiInputBackground_ColorId); | 1214 | &p, bounds, isFocused ? uiInputBackgroundFocused_ColorId : uiInputBackground_ColorId); |
1023 | drawRectThickness_Paint(&p, | 1215 | drawRectThickness_Paint(&p, |
@@ -1026,29 +1218,53 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
1026 | isFocused ? uiInputFrameFocused_ColorId | 1218 | isFocused ? uiInputFrameFocused_ColorId |
1027 | : isHover ? uiInputFrameHover_ColorId : uiInputFrame_ColorId); | 1219 | : isHover ? uiInputFrameHover_ColorId : uiInputFrame_ColorId); |
1028 | setClip_Paint(&p, adjusted_Rect(bounds, init_I2(d->leftPadding, 0), init_I2(-d->rightPadding, 0))); | 1220 | setClip_Paint(&p, adjusted_Rect(bounds, init_I2(d->leftPadding, 0), init_I2(-d->rightPadding, 0))); |
1029 | const iInt2 textOrigin = textOrigin_InputWidget_(d, cstr_String(text)); | 1221 | const iRect contentBounds = contentBounds_InputWidget_(d); |
1030 | if (isFocused && !isEmpty_Range(&d->mark)) { | 1222 | // const iInt2 textOrigin = textOrigin_InputWidget_(d); //, cstr_String(text)); |
1031 | /* Draw the selected range. */ | 1223 | iInt2 drawPos = topLeft_Rect(contentBounds); |
1032 | const int m1 = advanceN_Text(d->font, cstr_String(text), d->mark.start).x; | 1224 | const int fg = isHint ? uiAnnotation_ColorId |
1033 | const int m2 = advanceN_Text(d->font, cstr_String(text), d->mark.end).x; | 1225 | : isFocused && !isEmpty_Array(&d->text) ? uiInputTextFocused_ColorId |
1034 | fillRect_Paint(&p, | 1226 | : uiInputText_ColorId; |
1035 | (iRect){ addX_I2(textOrigin, iMin(m1, m2)), | 1227 | /* TODO: If buffered, just draw the buffered copy. */ |
1036 | init_I2(iAbs(m2 - m1), lineHeight_Text(d->font)) }, | 1228 | iConstForEach(Array, i, &d->lines) { |
1037 | uiMarked_ColorId); | 1229 | const iInputLine *line = i.value; |
1038 | } | 1230 | const iBool isLast = index_ArrayConstIterator(&i) == size_Array(&d->lines) - 1; |
1039 | if (d->buffered && !isFocused && !isHint) { | 1231 | const iInputLine *nextLine = isLast ? NULL : (line + 1); |
1040 | /* Most input widgets will use this, since only one is focused at a time. */ | 1232 | const iRanges lineRange = { line->offset, |
1041 | draw_TextBuf(d->buffered, textOrigin, white_ColorId); | 1233 | nextLine ? nextLine->offset : size_Array(&d->text) }; |
1042 | } | 1234 | if (isFocused && !isEmpty_Range(&d->mark)) { |
1043 | else { | 1235 | /* Draw the selected range. */ |
1044 | draw_Text(d->font, | 1236 | const iRanges mark = mark_InputWidget_(d); |
1045 | textOrigin, | 1237 | if (mark.start < lineRange.end && mark.end > lineRange.start) { |
1046 | isHint ? uiAnnotation_ColorId | 1238 | const int m1 = advanceN_Text(d->font, |
1047 | : isFocused && !isEmpty_Array(&d->text) ? uiInputTextFocused_ColorId | 1239 | cstr_String(&line->text), |
1048 | : uiInputText_ColorId, | 1240 | iMax(lineRange.start, mark.start) - line->offset) |
1049 | "%s", | 1241 | .x; |
1050 | cstr_String(text)); | 1242 | const int m2 = advanceN_Text(d->font, |
1051 | } | 1243 | cstr_String(&line->text), |
1244 | iMin(lineRange.end, mark.end) - line->offset) | ||
1245 | .x; | ||
1246 | fillRect_Paint(&p, | ||
1247 | (iRect){ addX_I2(drawPos, iMin(m1, m2)), | ||
1248 | init_I2(iAbs(m2 - m1), lineHeight_Text(d->font)) }, | ||
1249 | uiMarked_ColorId); | ||
1250 | } | ||
1251 | } | ||
1252 | drawRange_Text(d->font, drawPos, fg, range_String(&line->text)); | ||
1253 | drawPos.y += lineHeight_Text(d->font); | ||
1254 | } | ||
1255 | // if (d->buffered && !isFocused && !isHint) { | ||
1256 | // /* Most input widgets will use this, since only one is focused at a time. */ | ||
1257 | // draw_TextBuf(d->buffered, textOrigin, white_ColorId); | ||
1258 | // } | ||
1259 | // else { | ||
1260 | // draw_Text(d->font, | ||
1261 | // textOrigin, | ||
1262 | // isHint ? uiAnnotation_ColorId | ||
1263 | // : isFocused && !isEmpty_Array(&d->text) ? uiInputTextFocused_ColorId | ||
1264 | // : uiInputText_ColorId, | ||
1265 | // "%s", | ||
1266 | // cstr_String(text)); | ||
1267 | // } | ||
1052 | unsetClip_Paint(&p); | 1268 | unsetClip_Paint(&p); |
1053 | /* Cursor blinking. */ | 1269 | /* Cursor blinking. */ |
1054 | if (isFocused && d->cursorVis) { | 1270 | if (isFocused && d->cursorVis) { |
@@ -1073,23 +1289,36 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
1073 | /* Bar cursor. */ | 1289 | /* Bar cursor. */ |
1074 | curSize = init_I2(gap_UI / 2, lineHeight_Text(d->font)); | 1290 | curSize = init_I2(gap_UI / 2, lineHeight_Text(d->font)); |
1075 | } | 1291 | } |
1292 | const iInputLine *curLine = line_InputWidget_(d, d->cursorLine); | ||
1293 | const iString * text = &curLine->text; | ||
1076 | /* The `gap_UI` offsets below are a hack. They are used because for some reason the | 1294 | /* The `gap_UI` offsets below are a hack. They are used because for some reason the |
1077 | cursor rect and the glyph inside don't quite position like during `run_Text_()`. */ | 1295 | cursor rect and the glyph inside don't quite position like during `run_Text_()`. */ |
1078 | const iInt2 prefixSize = advanceN_Text(d->font, cstr_String(text), d->cursor); | 1296 | const iInt2 prefixSize = advanceN_Text(d->font, cstr_String(text), d->cursor - curLine->offset); |
1079 | const iInt2 curPos = addX_I2(textOrigin, prefixSize.x + | 1297 | const iInt2 curPos = addX_I2(addY_I2(contentBounds.pos, lineHeight_Text(d->font) * d->cursorLine), |
1298 | prefixSize.x + | ||
1080 | (d->mode == insert_InputMode ? -curSize.x / 2 : 0)); | 1299 | (d->mode == insert_InputMode ? -curSize.x / 2 : 0)); |
1081 | const iRect curRect = { curPos, curSize }; | 1300 | const iRect curRect = { curPos, curSize }; |
1082 | fillRect_Paint(&p, curRect, uiInputCursor_ColorId); | 1301 | fillRect_Paint(&p, curRect, uiInputCursor_ColorId); |
1083 | if (d->mode == overwrite_InputMode) { | 1302 | if (d->mode == overwrite_InputMode) { |
1084 | draw_Text(d->font, addX_I2(curPos, iMin(1, gap_UI / 8)), uiInputCursorText_ColorId, "%s", cstr_String(&cur)); | 1303 | draw_Text(d->font, |
1304 | addX_I2(curPos, iMin(1, gap_UI / 8)), | ||
1305 | uiInputCursorText_ColorId, | ||
1306 | "%s", | ||
1307 | cstr_String(&cur)); | ||
1085 | deinit_String(&cur); | 1308 | deinit_String(&cur); |
1086 | } | 1309 | } |
1087 | } | 1310 | } |
1088 | delete_String(text); | 1311 | // delete_String(text); |
1089 | drawChildren_Widget(w); | 1312 | drawChildren_Widget(w); |
1090 | } | 1313 | } |
1091 | 1314 | ||
1315 | //static void sizeChanged_InputWidget_(iInputWidget *d) { | ||
1316 | // printf("[InputWidget] %p: size changed, updating layout\n", d); | ||
1317 | // updateLinesAndResize_InputWidget_(d, iFalse); | ||
1318 | //} | ||
1319 | |||
1092 | iBeginDefineSubclass(InputWidget, Widget) | 1320 | iBeginDefineSubclass(InputWidget, Widget) |
1093 | .processEvent = (iAny *) processEvent_InputWidget_, | 1321 | .processEvent = (iAny *) processEvent_InputWidget_, |
1094 | .draw = (iAny *) draw_InputWidget_, | 1322 | .draw = (iAny *) draw_InputWidget_, |
1323 | // .sizeChanged = (iAny *) sizeChanged_InputWidget_, | ||
1095 | iEndDefineSubclass(InputWidget) | 1324 | iEndDefineSubclass(InputWidget) |
diff --git a/src/ui/root.c b/src/ui/root.c index 43fadbfa..8db8f7af 100644 --- a/src/ui/root.c +++ b/src/ui/root.c | |||
@@ -285,6 +285,16 @@ void destroyPending_Root(iRoot *d) { | |||
285 | setCurrent_Root(NULL); | 285 | setCurrent_Root(NULL); |
286 | } | 286 | } |
287 | 287 | ||
288 | void postArrange_Root(iRoot *d) { | ||
289 | if (!d->pendingArrange) { | ||
290 | d->pendingArrange = iTrue; | ||
291 | SDL_Event ev = { .type = SDL_USEREVENT }; | ||
292 | ev.user.code = arrange_UserEventCode; | ||
293 | ev.user.data2 = d; | ||
294 | SDL_PushEvent(&ev); | ||
295 | } | ||
296 | } | ||
297 | |||
288 | iPtrArray *onTop_Root(iRoot *d) { | 298 | iPtrArray *onTop_Root(iRoot *d) { |
289 | if (!d->onTop) { | 299 | if (!d->onTop) { |
290 | d->onTop = new_PtrArray(); | 300 | d->onTop = new_PtrArray(); |
@@ -337,6 +347,12 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) { | |||
337 | setFocus_Widget(findWidget_App(cstr_Rangecc(range_Command(cmd, "id")))); | 347 | setFocus_Widget(findWidget_App(cstr_Rangecc(range_Command(cmd, "id")))); |
338 | return iTrue; | 348 | return iTrue; |
339 | } | 349 | } |
350 | else if (equal_Command(cmd, "input.resized")) { | ||
351 | /* No parent handled this, so do a full rearrangement. */ | ||
352 | arrange_Widget(root); | ||
353 | postRefresh_App(); | ||
354 | return iTrue; | ||
355 | } | ||
340 | else if (equal_Command(cmd, "window.focus.lost")) { | 356 | else if (equal_Command(cmd, "window.focus.lost")) { |
341 | #if !defined (iPlatformMobile) /* apps don't share input focus on mobile */ | 357 | #if !defined (iPlatformMobile) /* apps don't share input focus on mobile */ |
342 | setFocus_Widget(NULL); | 358 | setFocus_Widget(NULL); |
diff --git a/src/ui/root.h b/src/ui/root.h index 00555224..96864a15 100644 --- a/src/ui/root.h +++ b/src/ui/root.h | |||
@@ -11,6 +11,7 @@ struct Impl_Root { | |||
11 | iWidget * widget; | 11 | iWidget * widget; |
12 | iPtrArray *onTop; /* order is important; last one is topmost */ | 12 | iPtrArray *onTop; /* order is important; last one is topmost */ |
13 | iPtrSet * pendingDestruction; | 13 | iPtrSet * pendingDestruction; |
14 | iBool pendingArrange; | ||
14 | int loadAnimTimer; | 15 | int loadAnimTimer; |
15 | iColor tmPalette[tmMax_ColorId]; /* theme-specific palette */ | 16 | iColor tmPalette[tmMax_ColorId]; /* theme-specific palette */ |
16 | }; | 17 | }; |
@@ -28,6 +29,7 @@ iAnyObject *findWidget_Root (const char *id); /* under curre | |||
28 | 29 | ||
29 | iPtrArray * onTop_Root (iRoot *); | 30 | iPtrArray * onTop_Root (iRoot *); |
30 | void destroyPending_Root (iRoot *); | 31 | void destroyPending_Root (iRoot *); |
32 | void postArrange_Root (iRoot *); | ||
31 | 33 | ||
32 | void updateMetrics_Root (iRoot *); | 34 | void updateMetrics_Root (iRoot *); |
33 | void updatePadding_Root (iRoot *); /* TODO: is part of metrics? */ | 35 | void updatePadding_Root (iRoot *); /* TODO: is part of metrics? */ |
@@ -39,4 +41,4 @@ iRect rect_Root (const iRoot *); | |||
39 | iRect safeRect_Root (const iRoot *); | 41 | iRect safeRect_Root (const iRoot *); |
40 | iInt2 visibleSize_Root (const iRoot *); /* may be obstructed by software keyboard */ | 42 | iInt2 visibleSize_Root (const iRoot *); /* may be obstructed by software keyboard */ |
41 | iBool isNarrow_Root (const iRoot *); | 43 | iBool isNarrow_Root (const iRoot *); |
42 | int appIconSize_Root (void); \ No newline at end of file | 44 | int appIconSize_Root (void); |
diff --git a/src/ui/text.c b/src/ui/text.c index 9532e35a..aacc8d3d 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -923,6 +923,13 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
923 | } | 923 | } |
924 | /* TODO: Check out if `uc_wordbreak_property()` from libunistring can be used here. */ | 924 | /* TODO: Check out if `uc_wordbreak_property()` from libunistring can be used here. */ |
925 | if (ch == '\n') { | 925 | if (ch == '\n') { |
926 | if (args->xposLimit > 0 && ~mode & noWrapFlag_RunMode) { | ||
927 | /* Stop the line here, this is a hard warp. */ | ||
928 | if (args->continueFrom_out) { | ||
929 | *args->continueFrom_out = chPos; | ||
930 | } | ||
931 | break; | ||
932 | } | ||
926 | xpos = xposExtend = orig.x; | 933 | xpos = xposExtend = orig.x; |
927 | ypos += d->height; | 934 | ypos += d->height; |
928 | prevCh = ch; | 935 | prevCh = ch; |