summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-07-20 09:01:25 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-07-20 09:01:25 +0300
commitdc1528e89d48947780e00fc1a49ce57cccdfbfe5 (patch)
tree4bd888f70dcf7bdd3ad166b43176232c42920594
parent3ccdfae64b82d9716de1f94f7d81de9c8765b607 (diff)
Revising InputWidget
`InputWidget` needs to be better at handling multiple lines. The previous implementation assumed that the content was short enough to be fully redrawn each frame, which is not a great idea when you have thousands of lines.
-rw-r--r--src/ui/inputwidget.c776
-rw-r--r--src/ui/inputwidget.h4
-rw-r--r--src/ui/root.c2
-rw-r--r--src/ui/text.c27
-rw-r--r--src/ui/text.h2
-rw-r--r--src/ui/uploadwidget.c1
6 files changed, 605 insertions, 207 deletions
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index 29003ad5..3aec99c9 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -53,20 +53,106 @@ static void enableEditorKeysInMenus_(iBool enable) {
53#endif 53#endif
54} 54}
55 55
56/*----------------------------------------------------------------------------------------------*/
57
58iDeclareType(InputLine)
59
60struct Impl_InputLine {
61 //size_t offset; /* character position from the beginning */
62 //size_t len; /* length as characters */
63 iRanges range; /* byte offset inside the entire content; for marking */
64 iString text; /* UTF-8 */
65 int numVisLines; /* wrapped lines */
66};
67
68static void init_InputLine(iInputLine *d) {
69 iZap(d->range);
70 init_String(&d->text);
71 d->numVisLines = 1;
72}
73
74static void deinit_InputLine(iInputLine *d) {
75 deinit_String(&d->text);
76}
77
78static void clearInputLines_(iArray *inputLines) {
79 iForEach(Array, i, inputLines) {
80 deinit_InputLine(i.value);
81 }
82 clear_Array(inputLines);
83}
84
85static void splitToLines_(const iString *text, iArray *inputLines) {
86 clearInputLines_(inputLines);
87 if (isEmpty_String(text)) {
88 iInputLine line;
89 init_InputLine(&line);
90 pushBack_Array(inputLines, &line);
91 return;
92 }
93 size_t index = 0;
94 iRangecc seg = iNullRange;
95 while (nextSplit_Rangecc(range_String(text), "\n", &seg)) {
96 iInputLine line;
97 init_InputLine(&line);
98 setRange_String(&line.text, seg);
99 appendCStr_String(&line.text, "\n");
100 line.range = (iRanges){ index, index + size_String(&line.text) };
101 pushBack_Array(inputLines, &line);
102 index = line.range.end;
103 }
104 if (!endsWith_String(text, "\n")) {
105 iInputLine *last = back_Array(inputLines);
106 removeEnd_String(&last->text, 1);
107 last->range.end--;
108 }
109 iAssert(((iInputLine *) back_Array(inputLines))->range.end == size_String(text));
110}
111
112static void mergeLinesRange_(const iArray *inputLines, iRanges range, iString *merged) {
113 clear_String(merged);
114 iConstForEach(Array, i, inputLines) {
115 const iInputLine *line = i.value;
116 const char *text = constBegin_String(&line->text);
117 if (line->range.end <= range.start || line->range.start >= range.end) {
118 continue; /* outside */
119 }
120 if (line->range.start >= range.start && line->range.end <= range.end) {
121 append_String(merged, &line->text); /* complete */
122 }
123 else if (line->range.start < range.start) {
124 appendRange_String(merged, (iRangecc){ text, text + range.end - line->range.start });
125 }
126 else {
127 appendRange_String(merged, (iRangecc){ text + range.start - line->range.start,
128 text + size_Range(&line->range) });
129 }
130 }
131}
132
133static void mergeLines_(const iArray *inputLines, iString *merged) {
134 mergeLinesRange_(inputLines, (iRanges){ 0, iInvalidSize }, merged);
135}
136
137iDefineTypeConstruction(InputLine)
138
139/*----------------------------------------------------------------------------------------------*/
140
56iDeclareType(InputUndo) 141iDeclareType(InputUndo)
57 142
58struct Impl_InputUndo { 143struct Impl_InputUndo {
59 iArray text; 144 iString text;
60 size_t cursor; 145 iInt2 cursor;
61}; 146};
62 147
63static void init_InputUndo_(iInputUndo *d, const iArray *text, size_t cursor) { 148static void init_InputUndo_(iInputUndo *d, const iArray *lines, iInt2 cursor) {
64 initCopy_Array(&d->text, text); 149 init_String(&d->text);
150 mergeLines_(lines, &d->text);
65 d->cursor = cursor; 151 d->cursor = cursor;
66} 152}
67 153
68static void deinit_InputUndo_(iInputUndo *d) { 154static void deinit_InputUndo_(iInputUndo *d) {
69 deinit_Array(&d->text); 155 deinit_String(&d->text);
70} 156}
71 157
72enum iInputWidgetFlag { 158enum iInputWidgetFlag {
@@ -85,45 +171,32 @@ enum iInputWidgetFlag {
85 171
86/*----------------------------------------------------------------------------------------------*/ 172/*----------------------------------------------------------------------------------------------*/
87 173
88iDeclareType(InputLine)
89
90struct Impl_InputLine {
91 size_t offset; /* character position from the beginning */
92 size_t len; /* length as characters */
93 iString text; /* UTF-8 */
94};
95
96static void init_InputLine(iInputLine *d) {
97 d->offset = 0;
98 init_String(&d->text);
99}
100
101static void deinit_InputLine(iInputLine *d) {
102 deinit_String(&d->text);
103}
104
105iDefineTypeConstruction(InputLine)
106
107/*----------------------------------------------------------------------------------------------*/
108
109struct Impl_InputWidget { 174struct Impl_InputWidget {
110 iWidget widget; 175 iWidget widget;
111 enum iInputMode mode; 176 enum iInputMode mode;
112 int inFlags; 177 int inFlags;
113 size_t maxLen; 178 size_t maxLen; /* characters */
114 size_t maxLayoutLines; 179// size_t maxLayoutLines;
115 iArray text; /* iChar[] */ 180// iArray text; /* iChar[] */
116 iArray oldText; /* iChar[] */ 181// iArray oldText; /* iChar[] */
117 iArray lines; 182 size_t length; /* current length in characters */
183 iArray lines; /* iInputLine[] */
184 //iArray oldLines; /* iInputLine[], for restoring if edits cancelled */
185 iString oldText; /* for restoring if edits cancelled */
118 int lastUpdateWidth; 186 int lastUpdateWidth;
119 iString hint;
120 iString srcHint; 187 iString srcHint;
188 iString hint;
121 int leftPadding; 189 int leftPadding;
122 int rightPadding; 190 int rightPadding;
123 size_t cursor; /* offset from beginning */ 191// size_t cursor; /* offset from beginning */
124 size_t lastCursor; 192// size_t lastCursor;
125 size_t cursorLine; 193// size_t cursorLine;
126 int verticalMoveX; 194 iInt2 cursor; /* cursor position: x = byte offset, y = line index */
195 iInt2 prevCursor; /* previous cursor position */
196// int verticalMoveX;
197 iRanges visLines; /* which lines are current visible */
198 int visLineOffsetY; /* vertical offset of first visible line */
199 int minVisLines, maxVisLines; /* min/max number of visible lines allowed */
127 iRanges mark; 200 iRanges mark;
128 iRanges initialMark; 201 iRanges initialMark;
129 iArray undoStack; 202 iArray undoStack;
@@ -131,13 +204,13 @@ struct Impl_InputWidget {
131 iClick click; 204 iClick click;
132 int cursorVis; 205 int cursorVis;
133 uint32_t timer; 206 uint32_t timer;
134 iTextBuf * buffered; 207 iTextBuf * buffered; /* pre-rendered static text */
135 iInputWidgetValidatorFunc validator; 208 iInputWidgetValidatorFunc validator;
136 void * validatorContext; 209 void * validatorContext;
137}; 210};
138 211
139iDefineObjectConstructionArgs(InputWidget, (size_t maxLen), maxLen) 212iDefineObjectConstructionArgs(InputWidget, (size_t maxLen), maxLen)
140 213
141static void clearUndo_InputWidget_(iInputWidget *d) { 214static void clearUndo_InputWidget_(iInputWidget *d) {
142 iForEach(Array, i, &d->undoStack) { 215 iForEach(Array, i, &d->undoStack) {
143 deinit_InputUndo_(i.value); 216 deinit_InputUndo_(i.value);
@@ -152,8 +225,7 @@ iLocalDef iInt2 padding_(void) {
152#define extraPaddingHeight_ (1.25f * gap_UI) 225#define extraPaddingHeight_ (1.25f * gap_UI)
153 226
154static iRect contentBounds_InputWidget_(const iInputWidget *d) { 227static iRect contentBounds_InputWidget_(const iInputWidget *d) {
155 const iWidget *w = constAs_Widget(d); 228 const iWidget *w = constAs_Widget(d);
156 // const iRect widgetBounds = bounds_Widget(w);
157 iRect bounds = adjusted_Rect(bounds_Widget(w), 229 iRect bounds = adjusted_Rect(bounds_Widget(w),
158 addX_I2(padding_(), d->leftPadding), 230 addX_I2(padding_(), d->leftPadding),
159 neg_I2(addX_I2(padding_(), d->rightPadding))); 231 neg_I2(addX_I2(padding_(), d->rightPadding)));
@@ -165,6 +237,7 @@ static iRect contentBounds_InputWidget_(const iInputWidget *d) {
165 return bounds; 237 return bounds;
166} 238}
167 239
240#if 0
168static void updateCursorLine_InputWidget_(iInputWidget *d) { 241static void updateCursorLine_InputWidget_(iInputWidget *d) {
169 iWidget *w = as_Widget(d); 242 iWidget *w = as_Widget(d);
170 d->cursorLine = 0; 243 d->cursorLine = 0;
@@ -190,10 +263,70 @@ static void updateCursorLine_InputWidget_(iInputWidget *d) {
190 } 263 }
191 } 264 }
192} 265}
266#endif
267
268static int numContentLines_InputWidget_(const iInputWidget *d) {
269 int num = 0;
270 iConstForEach(Array, i, &d->lines) {
271 const iInputLine *line = i.value;
272 num += line->numVisLines;
273 }
274 return num;
275}
276
277static const iInputLine *line_InputWidget_(const iInputWidget *d, size_t index) {
278 iAssert(!isEmpty_Array(&d->lines));
279 return constAt_Array(&d->lines, index);
280}
281
282iLocalDef iBool isLastLine_InputWidget_(const iInputWidget *d, const iInputLine *line) {
283 return (const void *) line == constBack_Array(&d->lines);
284}
285
286static int endX_InputWidget_(const iInputWidget *d, int y) {
287 /* The last line is not required to have an newline at the end. */
288 const iInputLine *line = line_InputWidget_(d, y);
289 return line->range.end - (isLastLine_InputWidget_(d, line) ? 0 : 1) - line->range.start;
290}
291
292static void scrollVisLines_InputWidget_(iInputWidget *d, int delta) {
293 const int numContentLines = numContentLines_InputWidget_(d);
294 d->visLines.start += delta;
295 d->visLines.end += delta;
296 if (d->visLines.end > numContentLines) {
297 const int over = d->visLines.end - numContentLines;
298 d->visLines.start -= over;
299 d->visLines.end = numContentLines;
300 }
301 if (d->visLines.start < 0) {
302 d->visLines.end -= d->visLines.start;
303 d->visLines.start = 0;
304 }
305}
306
307static void updateVisible_InputWidget_(iInputWidget *d) {
308 const int numContentLines = numContentLines_InputWidget_(d);
309 /* Expand or shrink the number of visible lines depending on how much content is available. */
310 d->visLines.end = d->visLines.start + iClamp(numContentLines, d->minVisLines, d->maxVisLines);
311 if (d->visLines.end > numContentLines) {
312 const int avail = d->visLines.start;
313 int offset = iMin(avail, d->visLines.end - numContentLines);
314 d->visLines.start -= offset;
315 d->visLines.end -= offset;
316 }
317 /* Keep the cursor visible. */
318 if (d->cursor.y < d->visLines.start) {
319 scrollVisLines_InputWidget_(d, d->cursor.y - d->visLines.start);
320 }
321 if (d->cursor.y > d->visLines.end - 1) {
322 scrollVisLines_InputWidget_(d, d->cursor.y - d->visLines.end - 1);
323 }
324}
193 325
194static void showCursor_InputWidget_(iInputWidget *d) { 326static void showCursor_InputWidget_(iInputWidget *d) {
195 d->cursorVis = 2; 327 d->cursorVis = 2;
196 updateCursorLine_InputWidget_(d); 328// updateCursorLine_InputWidget_(d);
329 updateVisible_InputWidget_(d);
197} 330}
198 331
199static void invalidateBuffered_InputWidget_(iInputWidget *d) { 332static void invalidateBuffered_InputWidget_(iInputWidget *d) {
@@ -218,33 +351,49 @@ static void updateSizeForFixedLength_InputWidget_(iInputWidget *d) {
218 } 351 }
219} 352}
220 353
221static const iChar sensitiveChar_ = 0x25cf; /* black circle */ 354static iString *text_InputWidget_(const iInputWidget *d) {
355 //return newUnicodeN_String(constData_Array(&d->text), size_Array(&d->text));
356 iString *text = new_String();
357 mergeLines_(&d->lines, text);
358 return text;
359}
222 360
223static iString *utf32toUtf8_InputWidget_(const iInputWidget *d) { 361static size_t length_InputWidget_(const iInputWidget *d) {
224 return newUnicodeN_String(constData_Array(&d->text), size_Array(&d->text)); 362 /* Note: `d->length` is kept up to date, so don't call this normally. */
363 size_t len = 0;
364 iConstForEach(Array, i, &d->lines) {
365 const iInputLine *line = i.value;
366 len += length_String(&line->text);
367 }
368 return len;
225} 369}
226 370
227static iString *visText_InputWidget_(const iInputWidget *d) { 371static iString *visText_InputWidget_(const iInputWidget *d) {
372 static const iChar sensitiveChar_ = 0x25cf; /* black circle */
228 iString *text; 373 iString *text;
229 if (~d->inFlags & isSensitive_InputWidgetFlag) { 374 if (~d->inFlags & isSensitive_InputWidgetFlag) {
230 text = utf32toUtf8_InputWidget_(d); 375 text = text_InputWidget_(d);
231 } 376 }
232 else { 377 else {
233 text = new_String(); 378 text = new_String();
234 for (size_t i = 0; i < size_Array(&d->text); ++i) { 379 iAssert(d->length == length_InputWidget_(d));
380 for (size_t i = 0; i < d->length; i++) {
235 appendChar_String(text, sensitiveChar_); 381 appendChar_String(text, sensitiveChar_);
236 } 382 }
237 } 383 }
238 return text; 384 return text;
239} 385}
240 386
387#if 0
241static void clearLines_InputWidget_(iInputWidget *d) { 388static void clearLines_InputWidget_(iInputWidget *d) {
242 iForEach(Array, i, &d->lines) { 389 iForEach(Array, i, &d->lines) {
243 deinit_InputLine(i.value); 390 deinit_InputLine(i.value);
244 } 391 }
245 clear_Array(&d->lines); 392 clear_Array(&d->lines);
246} 393}
394#endif
247 395
396#if 0
248static void updateLines_InputWidget_(iInputWidget *d) { 397static void updateLines_InputWidget_(iInputWidget *d) {
249 d->lastUpdateWidth = d->widget.rect.size.x; 398 d->lastUpdateWidth = d->widget.rect.size.x;
250 clearLines_InputWidget_(d); 399 clearLines_InputWidget_(d);
@@ -296,32 +445,59 @@ static void updateLines_InputWidget_(iInputWidget *d) {
296 delete_String(u8); 445 delete_String(u8);
297 updateCursorLine_InputWidget_(d); 446 updateCursorLine_InputWidget_(d);
298} 447}
448#endif
299 449
450static int contentHeight_InputWidget_(const iInputWidget *d) {
451 return size_Range(&d->visLines) * lineHeight_Text(d->font);
452}
453
454#if 0
300static int contentHeight_InputWidget_(const iInputWidget *d, iBool forLayout) { 455static int contentHeight_InputWidget_(const iInputWidget *d, iBool forLayout) {
301 size_t numLines = iMax(1, size_Array(&d->lines)); 456 int numLines = d->curVisLines;
302 if (forLayout) { 457 if (forLayout) {
303 numLines = iMin(numLines, d->maxLayoutLines); 458 numLines = iMin(numLines, d->numLayoutLines);
304 } 459 }
305 return (int) numLines * lineHeight_Text(d->font); 460 return numLines * lineHeight_Text(d->font);
306} 461}
462#endif
307 463
308static void updateMetrics_InputWidget_(iInputWidget *d) { 464static void updateMetrics_InputWidget_(iInputWidget *d) {
309 iWidget *w = as_Widget(d); 465 iWidget *w = as_Widget(d);
310 updateSizeForFixedLength_InputWidget_(d); 466 updateSizeForFixedLength_InputWidget_(d);
311 /* Caller must arrange the width, but the height is fixed. */ 467 /* Caller must arrange the width, but the height is set here. */
312 w->rect.size.y = contentHeight_InputWidget_(d, iTrue) + 3.0f * padding_().y; /* TODO: Why 3x? */ 468 const int oldHeight = height_Rect(w->rect);
469 w->rect.size.y = contentHeight_InputWidget_(d) + 3.0f * padding_().y; /* TODO: Why 3x? */
313 if (flags_Widget(w) & extraPadding_WidgetFlag) { 470 if (flags_Widget(w) & extraPadding_WidgetFlag) {
314 w->rect.size.y += extraPaddingHeight_; 471 w->rect.size.y += extraPaddingHeight_;
315 } 472 }
316 invalidateBuffered_InputWidget_(d); 473 invalidateBuffered_InputWidget_(d);
317 postCommand_Widget(d, "input.resized"); 474 if (height_Rect(w->rect) != oldHeight) {
475 postCommand_Widget(d, "input.resized");
476 }
477}
478
479static void updateLine_InputWidget_(iInputWidget *d, iInputLine *line) {
480 iWrapText wrapText = {
481 .text = range_String(&line->text),
482 .maxWidth = width_Rect(contentBounds_InputWidget_(d)),
483 .mode = (d->inFlags & isUrl_InputWidgetFlag ? anyCharacter_WrapTextMode
484 : word_WrapTextMode),
485 };
486 if (wrapText.maxWidth <= 0) {
487 line->numVisLines = 1;
488 return;
489 }
490 const iTextMetrics tm = measure_WrapText(&wrapText, d->font);
491 line->numVisLines = height_Rect(tm.bounds) / lineHeight_Text(d->font);
318} 492}
319 493
320static void updateLinesAndResize_InputWidget_(iInputWidget *d) { 494static void updateAllLinesAndResizeHeight_InputWidget_(iInputWidget *d) {
321 const size_t oldCount = size_Array(&d->lines); 495 const int oldCount = numContentLines_InputWidget_(d);
322 updateLines_InputWidget_(d); 496 iForEach(Array, i, &d->lines) {
323 if (oldCount != size_Array(&d->lines)) { 497 updateLine_InputWidget_(d, i.value); /* count number of visible lines */
324 d->click.minHeight = contentHeight_InputWidget_(d, iFalse); 498 }
499 if (oldCount != numContentLines_InputWidget_(d)) {
500// d->click.minHeight = contentHeight_InputWidget_(d, iFalse);
325 updateMetrics_InputWidget_(d); 501 updateMetrics_InputWidget_(d);
326 } 502 }
327} 503}
@@ -335,38 +511,46 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) {
335#if defined (iPlatformMobile) 511#if defined (iPlatformMobile)
336 setFlags_Widget(w, extraPadding_WidgetFlag, iTrue); 512 setFlags_Widget(w, extraPadding_WidgetFlag, iTrue);
337#endif 513#endif
338 init_Array(&d->text, sizeof(iChar));
339 init_Array(&d->oldText, sizeof(iChar));
340 init_Array(&d->lines, sizeof(iInputLine)); 514 init_Array(&d->lines, sizeof(iInputLine));
341 init_String(&d->hint); 515 //init_Array(&d->oldText, sizeof(iChar));
516 init_String(&d->oldText);
517 init_Array(&d->lines, sizeof(iInputLine));
342 init_String(&d->srcHint); 518 init_String(&d->srcHint);
519 init_String(&d->hint);
343 init_Array(&d->undoStack, sizeof(iInputUndo)); 520 init_Array(&d->undoStack, sizeof(iInputUndo));
344 d->font = uiInput_FontId | alwaysVariableFlag_FontId; 521 d->font = uiInput_FontId | alwaysVariableFlag_FontId;
345 d->leftPadding = 0; 522 d->leftPadding = 0;
346 d->rightPadding = 0; 523 d->rightPadding = 0;
347 d->cursor = 0; 524 d->cursor = zero_I2();
348 d->lastCursor = 0; 525 d->prevCursor = zero_I2();
349 d->cursorLine = 0; 526 //d->lastCursor = 0;
527 //d->cursorLine = 0;
350 d->lastUpdateWidth = 0; 528 d->lastUpdateWidth = 0;
351 d->verticalMoveX = -1; /* TODO: Use this. */ 529 //d->verticalMoveX = -1; /* TODO: Use this. */
352 d->inFlags = eatEscape_InputWidgetFlag | enterKeyEnabled_InputWidgetFlag; 530 d->inFlags = eatEscape_InputWidgetFlag | enterKeyEnabled_InputWidgetFlag;
353 if (deviceType_App() != desktop_AppDeviceType) { 531 if (deviceType_App() != desktop_AppDeviceType) {
354 d->inFlags |= enterKeyInsertsLineFeed_InputWidgetFlag; 532 d->inFlags |= enterKeyInsertsLineFeed_InputWidgetFlag;
355 } 533 }
356 iZap(d->mark); 534 iZap(d->mark);
357 setMaxLen_InputWidget(d, maxLen); 535 setMaxLen_InputWidget(d, maxLen);
358 d->maxLayoutLines = iInvalidSize; 536 d->visLines.start = 0;
359 setFlags_Widget(w, fixedHeight_WidgetFlag, iTrue); 537 d->visLines.end = 1;
538 d->visLineOffsetY = 0;
539 d->maxVisLines = maxLen > 0 ? 1 : 20; /* TODO: Choose maximum dynamically? */
540 d->minVisLines = 1;
541 splitToLines_(&iStringLiteral(""), &d->lines);
542 //d->maxLayoutLines = iInvalidSize;
543 setFlags_Widget(w, fixedHeight_WidgetFlag, iTrue); /* resizes its own height */
360 init_Click(&d->click, d, SDL_BUTTON_LEFT); 544 init_Click(&d->click, d, SDL_BUTTON_LEFT);
361 d->timer = 0; 545 d->timer = 0;
362 d->cursorVis = 0; 546 d->cursorVis = 0;
363 d->buffered = NULL; 547 d->buffered = NULL;
364 updateLines_InputWidget_(d); 548 //updateLines_InputWidget_(d);
365 updateMetrics_InputWidget_(d); 549 updateMetrics_InputWidget_(d);
366} 550}
367 551
368void deinit_InputWidget(iInputWidget *d) { 552void deinit_InputWidget(iInputWidget *d) {
369 clearLines_InputWidget_(d); 553 clearInputLines_(&d->lines);
370 if (isSelected_Widget(d)) { 554 if (isSelected_Widget(d)) {
371 SDL_StopTextInput(); 555 SDL_StopTextInput();
372 enableEditorKeysInMenus_(iTrue); 556 enableEditorKeysInMenus_(iTrue);
@@ -379,9 +563,8 @@ void deinit_InputWidget(iInputWidget *d) {
379 } 563 }
380 deinit_String(&d->srcHint); 564 deinit_String(&d->srcHint);
381 deinit_String(&d->hint); 565 deinit_String(&d->hint);
566 deinit_String(&d->oldText);
382 deinit_Array(&d->lines); 567 deinit_Array(&d->lines);
383 deinit_Array(&d->oldText);
384 deinit_Array(&d->text);
385} 568}
386 569
387void setFont_InputWidget(iInputWidget *d, int fontId) { 570void setFont_InputWidget(iInputWidget *d, int fontId) {
@@ -389,14 +572,9 @@ void setFont_InputWidget(iInputWidget *d, int fontId) {
389 updateMetrics_InputWidget_(d); 572 updateMetrics_InputWidget_(d);
390} 573}
391 574
392static const iInputLine *line_InputWidget_(const iInputWidget *d, size_t index) {
393 iAssert(!isEmpty_Array(&d->lines));
394 return constAt_Array(&d->lines, index);
395}
396
397static void pushUndo_InputWidget_(iInputWidget *d) { 575static void pushUndo_InputWidget_(iInputWidget *d) {
398 iInputUndo undo; 576 iInputUndo undo;
399 init_InputUndo_(&undo, &d->text, d->cursor); 577 init_InputUndo_(&undo, &d->lines, d->cursor);
400 pushBack_Array(&d->undoStack, &undo); 578 pushBack_Array(&d->undoStack, &undo);
401 if (size_Array(&d->undoStack) > maxUndo_InputWidget_) { 579 if (size_Array(&d->undoStack) > maxUndo_InputWidget_) {
402 deinit_InputUndo_(front_Array(&d->undoStack)); 580 deinit_InputUndo_(front_Array(&d->undoStack));
@@ -407,7 +585,8 @@ static void pushUndo_InputWidget_(iInputWidget *d) {
407static iBool popUndo_InputWidget_(iInputWidget *d) { 585static iBool popUndo_InputWidget_(iInputWidget *d) {
408 if (!isEmpty_Array(&d->undoStack)) { 586 if (!isEmpty_Array(&d->undoStack)) {
409 iInputUndo *undo = back_Array(&d->undoStack); 587 iInputUndo *undo = back_Array(&d->undoStack);
410 setCopy_Array(&d->text, &undo->text); 588 //setCopy_Array(&d->text, &undo->text);
589 splitToLines_(&undo->text, &d->lines);
411 d->cursor = undo->cursor; 590 d->cursor = undo->cursor;
412 deinit_InputUndo_(undo); 591 deinit_InputUndo_(undo);
413 popBack_Array(&d->undoStack); 592 popBack_Array(&d->undoStack);
@@ -421,6 +600,7 @@ void setMode_InputWidget(iInputWidget *d, enum iInputMode mode) {
421 d->mode = mode; 600 d->mode = mode;
422} 601}
423 602
603#if 0
424static void restoreDefaultScheme_(iString *url) { 604static void restoreDefaultScheme_(iString *url) {
425 iUrl parts; 605 iUrl parts;
426 init_Url(&parts, url); 606 init_Url(&parts, url);
@@ -435,14 +615,17 @@ static const iString *omitDefaultScheme_(iString *url) {
435 } 615 }
436 return url; 616 return url;
437} 617}
618#endif
438 619
439const iString *text_InputWidget(const iInputWidget *d) { 620const iString *text_InputWidget(const iInputWidget *d) {
440 if (d) { 621 if (d) {
441 iString *text = collect_String(utf32toUtf8_InputWidget_(d)); 622 iString *text = collect_String(text_InputWidget_(d));
623#if 0
442 if (d->inFlags & isUrl_InputWidgetFlag) { 624 if (d->inFlags & isUrl_InputWidgetFlag) {
443 /* Add the "gemini" scheme back if one is omitted. */ 625 /* Add the "gemini" scheme back if one is omitted. */
444 restoreDefaultScheme_(text); 626 restoreDefaultScheme_(text);
445 } 627 }
628#endif
446 return text; 629 return text;
447 } 630 }
448 return collectNew_String(); 631 return collectNew_String();
@@ -458,8 +641,10 @@ void setMaxLen_InputWidget(iInputWidget *d, size_t maxLen) {
458 updateSizeForFixedLength_InputWidget_(d); 641 updateSizeForFixedLength_InputWidget_(d);
459} 642}
460 643
461void setMaxLayoutLines_InputWidget(iInputWidget *d, size_t maxLayoutLines) { 644void setLineLimits_InputWidget(iInputWidget *d, int minVis, int maxVis) {
462 d->maxLayoutLines = maxLayoutLines; 645 d->minVisLines = minVis;
646 d->maxVisLines = maxVis;
647 updateVisible_InputWidget_(d);
463 updateMetrics_InputWidget_(d); 648 updateMetrics_InputWidget_(d);
464} 649}
465 650
@@ -495,34 +680,18 @@ void setContentPadding_InputWidget(iInputWidget *d, int left, int right) {
495} 680}
496 681
497static iBool isHintVisible_InputWidget_(const iInputWidget *d) { 682static iBool isHintVisible_InputWidget_(const iInputWidget *d) {
498 return !isEmpty_String(&d->hint) && size_Array(&d->lines) == 1 && 683 return !isEmpty_String(&d->hint) && isEmpty_Array(&d->lines);/* size_Array(&d->lines) <= 1 &&
499 isEmpty_String(&line_InputWidget_(d, 0)->text); 684 isEmpty_String(&line_InputWidget_(d, 0)->text);*/
500} 685}
501 686
502static void updateBuffered_InputWidget_(iInputWidget *d) { 687static void updateBuffered_InputWidget_(iInputWidget *d) {
503 invalidateBuffered_InputWidget_(d); 688 invalidateBuffered_InputWidget_(d);
689#if 0
504 if (isHintVisible_InputWidget_(d)) { 690 if (isHintVisible_InputWidget_(d)) {
505 d->buffered = new_TextBuf(d->font, uiAnnotation_ColorId, cstr_String(&d->hint)); 691 d->buffered = new_TextBuf(d->font, uiAnnotation_ColorId, cstr_String(&d->hint));
506 } 692 }
507 else { 693 else {
508 iString *bufText = NULL; 694 iString *bufText = NULL;
509#if 0
510 if (d->inFlags & isUrl_InputWidgetFlag && as_Widget(d)->root == win->keyRoot) {
511 /* TODO: Move this omitting to `updateLines_`? */
512 /* Highlight the host name. */
513 iUrl parts;
514 const iString *text = collect_String(utf32toUtf8_InputWidget_(d));
515 init_Url(&parts, text);
516 if (!isEmpty_Range(&parts.host)) {
517 bufText = new_String();
518 appendRange_String(bufText, (iRangecc){ constBegin_String(text), parts.host.start });
519 appendCStr_String(bufText, uiTextStrong_ColorEscape);
520 appendRange_String(bufText, parts.host);
521 appendCStr_String(bufText, restore_ColorEscape);
522 appendRange_String(bufText, (iRangecc){ parts.host.end, constEnd_String(text) });
523 }
524 }
525#endif
526 if (!bufText) { 695 if (!bufText) {
527 bufText = visText_InputWidget_(d); 696 bufText = visText_InputWidget_(d);
528 } 697 }
@@ -534,9 +703,28 @@ static void updateBuffered_InputWidget_(iInputWidget *d) {
534 : newWrap_TextBuf (d->font, fg, maxWidth, text)); 703 : newWrap_TextBuf (d->font, fg, maxWidth, text));
535 delete_String(bufText); 704 delete_String(bufText);
536 } 705 }
706#endif
537 d->inFlags &= ~needUpdateBuffer_InputWidgetFlag; 707 d->inFlags &= ~needUpdateBuffer_InputWidgetFlag;
538} 708}
539 709
710iLocalDef iInputLine *cursorLine_InputWidget_(iInputWidget *d) {
711 return at_Array(&d->lines, d->cursor.y);
712}
713
714iLocalDef const iInputLine *constCursorLine_InputWidget_(const iInputWidget *d) {
715 return constAt_Array(&d->lines, d->cursor.y);
716}
717
718iLocalDef const iInputLine *lastLine_InputWidget_(const iInputWidget *d) {
719 iAssert(!isEmpty_Array(&d->lines));
720 return constBack_Array(&d->lines);
721}
722
723iLocalDef iInt2 cursorMax_InputWidget_(const iInputWidget *d) {
724 //return iMin(size_Array(&d->text), d->maxLen - 1);
725 return init_I2(lastLine_InputWidget_(d)->range.end, size_Array(&d->lines) - 1);
726}
727
540void setText_InputWidget(iInputWidget *d, const iString *text) { 728void setText_InputWidget(iInputWidget *d, const iString *text) {
541 if (!d) return; 729 if (!d) return;
542 if (d->inFlags & isUrl_InputWidgetFlag) { 730 if (d->inFlags & isUrl_InputWidgetFlag) {
@@ -548,30 +736,38 @@ void setText_InputWidget(iInputWidget *d, const iString *text) {
548 punyEncodeUrlHost_String(enc); 736 punyEncodeUrlHost_String(enc);
549 text = enc; 737 text = enc;
550 } 738 }
739#if 0
551 /* Omit the default (Gemini) scheme if there isn't much space. */ 740 /* Omit the default (Gemini) scheme if there isn't much space. */
552 if (isNarrow_Root(as_Widget(d)->root)) { 741 if (isNarrow_Root(as_Widget(d)->root)) {
553 text = omitDefaultScheme_(collect_String(copy_String(text))); 742 text = omitDefaultScheme_(collect_String(copy_String(text)));
554 } 743 }
744#endif
555 } 745 }
556 clearUndo_InputWidget_(d); 746 clearUndo_InputWidget_(d);
557 clear_Array(&d->text); 747 //clear_Array(&d->text);
558 iString *nfcText = collect_String(copy_String(text)); 748 iString *nfcText = collect_String(copy_String(text));
559 normalize_String(nfcText); 749 normalize_String(nfcText);
560 iConstForEach(String, i, nfcText) { 750// iConstForEach(String, i, nfcText) {
561 pushBack_Array(&d->text, &i.value); 751// pushBack_Array(&d->text, &i.value);
752// }
753 splitToLines_(nfcText, &d->lines);
754 iAssert(!isEmpty_Array(&d->lines));
755 iForEach(Array, i, &d->lines) {
756 updateLine_InputWidget_(d, i.value); /* count number of visible lines */
562 } 757 }
563 if (isFocused_Widget(d)) { 758 if (isFocused_Widget(d)) {
564 d->cursor = size_Array(&d->text); 759 d->cursor = cursorMax_InputWidget_(d);
565// selectAll_InputWidget(d);
566 } 760 }
567 else { 761 else {
568 d->cursor = iMin(d->cursor, size_Array(&d->text)); 762 d->cursor.y = iMin(d->cursor.y, (int) size_Array(&d->lines) - 1);
763 d->cursor.x = iMin(d->cursor.x, size_String(&cursorLine_InputWidget_(d)->text));
569 iZap(d->mark); 764 iZap(d->mark);
570 } 765 }
571 if (!isFocused_Widget(d)) { 766 if (!isFocused_Widget(d)) {
572 d->inFlags |= needUpdateBuffer_InputWidgetFlag; 767 d->inFlags |= needUpdateBuffer_InputWidgetFlag;
573 } 768 }
574 updateLinesAndResize_InputWidget_(d); 769// updateLinesAndResize_InputWidget_(d);
770 updateVisible_InputWidget_(d);
575 refresh_Widget(as_Widget(d)); 771 refresh_Widget(as_Widget(d));
576} 772}
577 773
@@ -593,8 +789,33 @@ static uint32_t cursorTimer_(uint32_t interval, void *w) {
593 return interval; 789 return interval;
594} 790}
595 791
792static size_t cursorToIndex_InputWidget_(const iInputWidget *d, iInt2 pos) {
793 /* TODO: Lazy linear position updates. */
794 if (pos.y < 0) {
795 return 0;
796 }
797 if (pos.y >= size_Array(&d->lines)) {
798 return lastLine_InputWidget_(d)->range.end;
799 }
800 const iInputLine *line = line_InputWidget_(d, pos.y);
801 pos.x = iClamp(pos.x, 0, endX_InputWidget_(d, pos.y));
802 return line->range.start + pos.x;
803}
804
805static iInt2 indexToCursor_InputWidget_(const iInputWidget *d, size_t index) {
806 /* TODO: Lazy linear position updates. */
807 /* TODO: The lines are sorted; this could use a binary search. */
808 iConstForEach(Array, i, &d->lines) {
809 const iInputLine *line = i.value;
810 if (contains_Range(&line->range, index)) {
811 return init_I2(index - line->range.start, index_ArrayConstIterator(&i));
812 }
813 }
814 return cursorMax_InputWidget_(d);
815}
816
596void selectAll_InputWidget(iInputWidget *d) { 817void selectAll_InputWidget(iInputWidget *d) {
597 d->mark = (iRanges){ 0, size_Array(&d->text) }; 818 d->mark = (iRanges){ 0, lastLine_InputWidget_(d)->range.end };
598 refresh_Widget(as_Widget(d)); 819 refresh_Widget(as_Widget(d));
599} 820}
600 821
@@ -610,26 +831,31 @@ void begin_InputWidget(iInputWidget *d) {
610 } 831 }
611 invalidateBuffered_InputWidget_(d); 832 invalidateBuffered_InputWidget_(d);
612 setFlags_Widget(w, hidden_WidgetFlag | disabled_WidgetFlag, iFalse); 833 setFlags_Widget(w, hidden_WidgetFlag | disabled_WidgetFlag, iFalse);
613 setCopy_Array(&d->oldText, &d->text); 834 //setCopy_Array(&d->oldText, &d->text);
835 mergeLines_(&d->lines, &d->oldText);
614 if (d->mode == overwrite_InputMode) { 836 if (d->mode == overwrite_InputMode) {
615 d->cursor = 0; 837 d->cursor = zero_I2();
616 } 838 }
617 else { 839 else {
618 d->cursor = iMin(size_Array(&d->text), d->maxLen - 1); 840 d->cursor.y = iMin(d->cursor.y, size_Array(&d->lines) - 1);
841 d->cursor.x = iMin(d->cursor.x, cursorLine_InputWidget_(d)->range.end);
619 } 842 }
620 updateCursorLine_InputWidget_(d); 843// updateCursorLine_InputWidget_(d);
621 SDL_StartTextInput(); 844 SDL_StartTextInput();
622 setFlags_Widget(w, selected_WidgetFlag, iTrue); 845 setFlags_Widget(w, selected_WidgetFlag, iTrue);
846#if 0
623 if (d->maxLayoutLines != iInvalidSize) { 847 if (d->maxLayoutLines != iInvalidSize) {
624 /* This will extend beyond the arranged region. */ 848 /* This will extend beyond the arranged region. */
625 setFlags_Widget(w, keepOnTop_WidgetFlag, iTrue); 849 setFlags_Widget(w, keepOnTop_WidgetFlag, iTrue);
626 } 850 }
851#endif
627 showCursor_InputWidget_(d); 852 showCursor_InputWidget_(d);
628 refresh_Widget(w); 853 refresh_Widget(w);
629 d->timer = SDL_AddTimer(refreshInterval_InputWidget_, cursorTimer_, d); 854 d->timer = SDL_AddTimer(refreshInterval_InputWidget_, cursorTimer_, d);
630 d->inFlags &= ~enterPressed_InputWidgetFlag; 855 d->inFlags &= ~enterPressed_InputWidgetFlag;
631 if (d->inFlags & selectAllOnFocus_InputWidgetFlag) { 856 if (d->inFlags & selectAllOnFocus_InputWidgetFlag) {
632 d->mark = (iRanges){ 0, size_Array(&d->text) }; 857 d->mark = (iRanges){ 0, lastLine_InputWidget_(d)->range.end };
858 d->cursor = cursorMax_InputWidget_(d);
633 } 859 }
634 else { 860 else {
635 iZap(d->mark); 861 iZap(d->mark);
@@ -645,7 +871,9 @@ void end_InputWidget(iInputWidget *d, iBool accept) {
645 } 871 }
646 enableEditorKeysInMenus_(iTrue); 872 enableEditorKeysInMenus_(iTrue);
647 if (!accept) { 873 if (!accept) {
648 setCopy_Array(&d->text, &d->oldText); 874 //setCopy_Array(&d->text, &d->oldText);
875 /* Overwrite the edited lines. */
876 splitToLines_(&d->oldText, &d->lines);
649 } 877 }
650 d->inFlags |= needUpdateBuffer_InputWidgetFlag; 878 d->inFlags |= needUpdateBuffer_InputWidgetFlag;
651 SDL_RemoveTimer(d->timer); 879 SDL_RemoveTimer(d->timer);
@@ -654,7 +882,7 @@ void end_InputWidget(iInputWidget *d, iBool accept) {
654 setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag, iFalse); 882 setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag, iFalse);
655 const char *id = cstr_String(id_Widget(as_Widget(d))); 883 const char *id = cstr_String(id_Widget(as_Widget(d)));
656 if (!*id) id = "_"; 884 if (!*id) id = "_";
657 updateLinesAndResize_InputWidget_(d); 885// updateLinesAndResize_InputWidget_(d);
658 refresh_Widget(w); 886 refresh_Widget(w);
659 postCommand_Widget(w, 887 postCommand_Widget(w,
660 "input.ended id:%s enter:%d arg:%d", 888 "input.ended id:%s enter:%d arg:%d",
@@ -663,12 +891,16 @@ void end_InputWidget(iInputWidget *d, iBool accept) {
663 accept ? 1 : 0); 891 accept ? 1 : 0);
664} 892}
665 893
666static void insertChar_InputWidget_(iInputWidget *d, iChar chr) { 894static void insertRange_InputWidget_(iInputWidget *d, iRangecc range) {
667 iWidget *w = as_Widget(d); 895 iWidget *w = as_Widget(d);
896 /* TODO: If there's a newline, we'll need to break and insert a new line. */
897 iAssert(lastIndexOfCStr_Rangecc(range, "\n") == iInvalidPos);
898 iInputLine *line = cursorLine_InputWidget_(d);
668 if (d->mode == insert_InputMode) { 899 if (d->mode == insert_InputMode) {
669 insert_Array(&d->text, d->cursor, &chr); 900 insertData_Block(&line->text.chars, d->cursor.x, range.start, size_Range(&range));
670 d->cursor++; 901 d->cursor.x += size_Range(&range);
671 } 902 }
903#if 0
672 else if (d->maxLen == 0 || d->cursor < d->maxLen) { 904 else if (d->maxLen == 0 || d->cursor < d->maxLen) {
673 if (d->cursor >= size_Array(&d->text)) { 905 if (d->cursor >= size_Array(&d->text)) {
674 resize_Array(&d->text, d->cursor + 1); 906 resize_Array(&d->text, d->cursor + 1);
@@ -682,33 +914,33 @@ static void insertChar_InputWidget_(iInputWidget *d, iChar chr) {
682 d->cursor = 0; 914 d->cursor = 0;
683 } 915 }
684 } 916 }
917#endif
685 showCursor_InputWidget_(d); 918 showCursor_InputWidget_(d);
686 refresh_Widget(as_Widget(d)); 919 refresh_Widget(as_Widget(d));
687} 920}
688 921
689iLocalDef size_t cursorMax_InputWidget_(const iInputWidget *d) { 922static void insertChar_InputWidget_(iInputWidget *d, iChar chr) {
690 return iMin(size_Array(&d->text), d->maxLen - 1); 923 iMultibyteChar mb;
924 init_MultibyteChar(&mb, chr);
925 insertRange_InputWidget_(d, range_CStr(mb.bytes));
691} 926}
692 927
693iLocalDef iBool isMarking_(void) { 928iLocalDef iBool isMarking_(void) {
694 return (modState_Keys() & KMOD_SHIFT) != 0; 929 return (modState_Keys() & KMOD_SHIFT) != 0;
695} 930}
696 931
697void setCursor_InputWidget(iInputWidget *d, size_t pos) { 932void setCursor_InputWidget(iInputWidget *d, iInt2 pos) {
698 if (isEmpty_Array(&d->text)) { 933 iAssert(!isEmpty_Array(&d->lines));
699 d->cursor = 0; 934 pos.x = iClamp(pos.x, 0, endX_InputWidget_(d, pos.y));
700 } 935 d->cursor = pos;
701 else {
702 d->cursor = iClamp(pos, 0, cursorMax_InputWidget_(d));
703 }
704 /* Update selection. */ 936 /* Update selection. */
705 if (isMarking_()) { 937 if (isMarking_()) {
706 if (isEmpty_Range(&d->mark)) { 938 if (isEmpty_Range(&d->mark)) {
707 d->mark.start = d->lastCursor; 939 d->mark.start = cursorToIndex_InputWidget_(d, d->prevCursor);
708 d->mark.end = d->cursor; 940 d->mark.end = cursorToIndex_InputWidget_(d, d->cursor);
709 } 941 }
710 else { 942 else {
711 d->mark.end = d->cursor; 943 d->mark.end = cursorToIndex_InputWidget_(d, d->cursor);
712 } 944 }
713 } 945 }
714 else { 946 else {
@@ -717,35 +949,37 @@ void setCursor_InputWidget(iInputWidget *d, size_t pos) {
717 showCursor_InputWidget_(d); 949 showCursor_InputWidget_(d);
718} 950}
719 951
720iLocalDef iBool isLastLine_InputWidget_(const iInputWidget *d, const iInputLine *line) {
721 return (const void *) line == constAt_Array(&d->lines, size_Array(&d->lines) - 1);
722}
723
724static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const iInputLine *line) { 952static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const iInputLine *line) {
725 size_t index = line->offset; 953 size_t index = line->range.start;
726 if (x <= 0) { 954 if (x <= 0) {
727 return index; 955 return index;
728 } 956 }
729 const char *endPos; 957 const char *endPos;
730 tryAdvanceNoWrap_Text(d->font, range_String(&line->text), x, &endPos); 958 tryAdvanceNoWrap_Text(d->font, range_String(&line->text), x, &endPos);
731 if (endPos == constEnd_String(&line->text)) { 959 if (endPos == constEnd_String(&line->text)) {
732 index += line->len; 960 index = line->range.end - 1;
733 } 961 }
734 else { 962 else {
963#if 0
735 /* Need to know the actual character index. */ 964 /* Need to know the actual character index. */
736 /* TODO: tryAdvance could tell us this directly with an extra return value */ 965 /* TODO: tryAdvance could tell us this directly with an extra return value */
737 iConstForEach(String, i, &line->text) { 966 iConstForEach(String, i, &line->text) {
738 if (i.pos >= endPos) break; 967 if (i.pos >= endPos) break;
739 index++; 968 index++;
740 } 969 }
970#endif
971 index = line->range.start + (endPos - constBegin_String(&line->text));
741 } 972 }
973#if 0
742 if (!isLastLine_InputWidget_(d, line) && index == line->offset + line->len) { 974 if (!isLastLine_InputWidget_(d, line) && index == line->offset + line->len) {
743 index = iMax(index - 1, line->offset); 975 index = iMax(index - 1, line->offset);
744 } 976 }
745 return index; 977#endif
978 return iMax(index, line->range.start);
746} 979}
747 980
748static iBool moveCursorByLine_InputWidget_(iInputWidget *d, int dir) { 981static iBool moveCursorByLine_InputWidget_(iInputWidget *d, int dir) {
982#if 0
749 const iInputLine *line = line_InputWidget_(d, d->cursorLine); 983 const iInputLine *line = line_InputWidget_(d, d->cursorLine);
750 int xPos1 = maxWidth_TextMetrics(measureN_Text(d->font, 984 int xPos1 = maxWidth_TextMetrics(measureN_Text(d->font,
751 cstr_String(&line->text), 985 cstr_String(&line->text),
@@ -766,6 +1000,7 @@ static iBool moveCursorByLine_InputWidget_(iInputWidget *d, int dir) {
766 setCursor_InputWidget(d, newCursor); 1000 setCursor_InputWidget(d, newCursor);
767 return iTrue; 1001 return iTrue;
768 } 1002 }
1003#endif
769 return iFalse; 1004 return iFalse;
770} 1005}
771 1006
@@ -792,8 +1027,9 @@ void setEatEscape_InputWidget(iInputWidget *d, iBool eatEscape) {
792 1027
793static iRanges mark_InputWidget_(const iInputWidget *d) { 1028static iRanges mark_InputWidget_(const iInputWidget *d) {
794 iRanges m = { iMin(d->mark.start, d->mark.end), iMax(d->mark.start, d->mark.end) }; 1029 iRanges m = { iMin(d->mark.start, d->mark.end), iMax(d->mark.start, d->mark.end) };
795 m.start = iMin(m.start, size_Array(&d->text)); 1030 const iInputLine *last = lastLine_InputWidget_(d);
796 m.end = iMin(m.end, size_Array(&d->text)); 1031 m.start = iMin(m.start, last->range.end);
1032 m.end = iMin(m.end, last->range.end);
797 return m; 1033 return m;
798} 1034}
799 1035
@@ -801,7 +1037,7 @@ static void contentsWereChanged_InputWidget_(iInputWidget *d) {
801 if (d->validator) { 1037 if (d->validator) {
802 d->validator(d, d->validatorContext); /* this may change the contents */ 1038 d->validator(d, d->validatorContext); /* this may change the contents */
803 } 1039 }
804 updateLinesAndResize_InputWidget_(d); 1040// updateLinesAndResize_InputWidget_(d);
805 if (d->inFlags & notifyEdits_InputWidgetFlag) { 1041 if (d->inFlags & notifyEdits_InputWidgetFlag) {
806 postCommand_Widget(d, "input.edited id:%s", cstr_String(id_Widget(constAs_Widget(d)))); 1042 postCommand_Widget(d, "input.edited id:%s", cstr_String(id_Widget(constAs_Widget(d))));
807 } 1043 }
@@ -810,30 +1046,83 @@ static void contentsWereChanged_InputWidget_(iInputWidget *d) {
810static iBool deleteMarked_InputWidget_(iInputWidget *d) { 1046static iBool deleteMarked_InputWidget_(iInputWidget *d) {
811 const iRanges m = mark_InputWidget_(d); 1047 const iRanges m = mark_InputWidget_(d);
812 if (!isEmpty_Range(&m)) { 1048 if (!isEmpty_Range(&m)) {
1049#if 0
813 removeRange_Array(&d->text, m); 1050 removeRange_Array(&d->text, m);
814 setCursor_InputWidget(d, m.start); 1051#endif
1052 setCursor_InputWidget(d, indexToCursor_InputWidget_(d, m.start));
815 iZap(d->mark); 1053 iZap(d->mark);
816 return iTrue; 1054 return iTrue;
817 } 1055 }
818 return iFalse; 1056 return iFalse;
819} 1057}
820 1058
821static iBool isWordChar_InputWidget_(const iInputWidget *d, size_t pos) { 1059static const iString *lineString_InputWidget_(const iInputWidget *d, int y) {
822 const iChar ch = pos < size_Array(&d->text) ? constValue_Array(&d->text, pos, iChar) : ' '; 1060 return &line_InputWidget_(d, y)->text;
823 return isAlphaNumeric_Char(ch);
824} 1061}
825 1062
826iLocalDef iBool movePos_InputWidget_(const iInputWidget *d, size_t *pos, int dir) { 1063static const char *charPos_InputWidget_(const iInputWidget *d, iInt2 pos) {
827 if (dir < 0) { 1064 return cstr_String(lineString_InputWidget_(d, pos.y)) + pos.x;
828 if (*pos > 0) (*pos)--; else return iFalse; 1065}
1066
1067static iChar at_InputWidget_(const iInputWidget *d, iInt2 pos) {
1068 if (pos.y >= 0 && pos.y < size_Array(&d->lines) &&
1069 pos.x >= 0 && pos.x <= endX_InputWidget_(d, pos.y)) {
1070 iChar ch = 0;
1071 decodeBytes_MultibyteChar(charPos_InputWidget_(d, pos),
1072 constEnd_String(lineString_InputWidget_(d, pos.y)),
1073 &ch);
1074 return ch;
829 } 1075 }
830 else { 1076 return ' ';
831 if (*pos < cursorMax_InputWidget_(d)) (*pos)++; else return iFalse; 1077}
1078
1079static iBool isWordChar_InputWidget_(const iInputWidget *d, iInt2 pos) {
1080 return isAlphaNumeric_Char(at_InputWidget_(d, pos));
1081}
1082
1083static iInt2 movedCursor_InputWidget_(const iInputWidget *d, iInt2 pos, int xDir, int yDir) {
1084 iChar ch;
1085 if (xDir < 0) {
1086 if (pos.x == 0) {
1087 if (pos.y > 0) {
1088 pos.x = endX_InputWidget_(d, --pos.y);
1089 }
1090 }
1091 else {
1092 iAssert(pos.x > 0);
1093 int n = decodePrecedingBytes_MultibyteChar(charPos_InputWidget_(d, pos),
1094 cstr_String(lineString_InputWidget_(d, pos.y)),
1095 &ch);
1096 pos.x -= n;
1097 }
832 } 1098 }
1099 else if (xDir > 0) {
1100 if (pos.x == endX_InputWidget_(d, pos.y)) {
1101 if (pos.y < size_Array(&d->lines) - 1) {
1102 pos.y++;
1103 pos.x = 0;
1104 }
1105 }
1106 else {
1107 int n = decodeBytes_MultibyteChar(charPos_InputWidget_(d, pos),
1108 constEnd_String(lineString_InputWidget_(d, pos.y)),
1109 &ch);
1110 pos.x += n;
1111 }
1112 }
1113 return pos;
1114}
1115
1116iLocalDef iBool movePos_InputWidget_(const iInputWidget *d, iInt2 *pos, int dir) {
1117 iInt2 npos = movedCursor_InputWidget_(d, *pos, dir, 0);
1118 if (isEqual_I2(*pos, npos)) {
1119 return iFalse;
1120 }
1121 *pos = npos;
833 return iTrue; 1122 return iTrue;
834} 1123}
835 1124
836static size_t skipWord_InputWidget_(const iInputWidget *d, size_t pos, int dir) { 1125static iInt2 skipWord_InputWidget_(const iInputWidget *d, iInt2 pos, int dir) {
837 const iBool startedAtNonWord = !isWordChar_InputWidget_(d, pos); 1126 const iBool startedAtNonWord = !isWordChar_InputWidget_(d, pos);
838 if (!movePos_InputWidget_(d, &pos, dir)) { 1127 if (!movePos_InputWidget_(d, &pos, dir)) {
839 return pos; 1128 return pos;
@@ -872,16 +1161,18 @@ static size_t coordIndex_InputWidget_(const iInputWidget *d, iInt2 coord) {
872 const size_t lineNumber = iMin(iMax(0, pos.y) / lineHeight_Text(d->font), 1161 const size_t lineNumber = iMin(iMax(0, pos.y) / lineHeight_Text(d->font),
873 (int) size_Array(&d->lines) - 1); 1162 (int) size_Array(&d->lines) - 1);
874 const iInputLine *line = line_InputWidget_(d, lineNumber); 1163 const iInputLine *line = line_InputWidget_(d, lineNumber);
875 const char *endPos; 1164// const char *endPos;
876 tryAdvanceNoWrap_Text(d->font, range_String(&line->text), pos.x, &endPos); 1165// tryAdvanceNoWrap_Text(d->font, range_String(&line->text), pos.x, &endPos);
877 return indexForRelativeX_InputWidget_(d, pos.x, line); 1166 return indexForRelativeX_InputWidget_(d, pos.x, line);
878} 1167}
879 1168
880static iBool copy_InputWidget_(iInputWidget *d, iBool doCut) { 1169static iBool copy_InputWidget_(iInputWidget *d, iBool doCut) {
881 if (!isEmpty_Range(&d->mark)) { 1170 if (!isEmpty_Range(&d->mark)) {
882 const iRanges m = mark_InputWidget_(d); 1171 const iRanges m = mark_InputWidget_(d);
883 iString *str = collect_String(newUnicodeN_String(constAt_Array(&d->text, m.start), 1172// iString *str = collect_String(newUnicodeN_String(constAt_Array(&d->text, m.start),
884 size_Range(&m))); 1173// size_Range(&m)));
1174 iString *str = collectNew_String();
1175 mergeLinesRange_(&d->lines, m, str);
885 SDL_SetClipboardText( 1176 SDL_SetClipboardText(
886 cstr_String(d->inFlags & isUrl_InputWidgetFlag ? canonicalUrl_String(str) : str)); 1177 cstr_String(d->inFlags & isUrl_InputWidgetFlag ? canonicalUrl_String(str) : str));
887 if (doCut) { 1178 if (doCut) {
@@ -915,10 +1206,7 @@ static void paste_InputWidget_(iInputWidget *d) {
915 } 1206 }
916} 1207}
917 1208
918static iChar at_InputWidget_(const iInputWidget *d, size_t pos) { 1209#if 0
919 return *(const iChar *) constAt_Array(&d->text, pos);
920}
921
922static iRanges lineRange_InputWidget_(const iInputWidget *d) { 1210static iRanges lineRange_InputWidget_(const iInputWidget *d) {
923 if (isEmpty_Array(&d->lines)) { 1211 if (isEmpty_Array(&d->lines)) {
924 return (iRanges){ 0, 0 }; 1212 return (iRanges){ 0, 0 };
@@ -926,8 +1214,10 @@ static iRanges lineRange_InputWidget_(const iInputWidget *d) {
926 const iInputLine *line = line_InputWidget_(d, d->cursorLine); 1214 const iInputLine *line = line_InputWidget_(d, d->cursorLine);
927 return (iRanges){ line->offset, line->offset + line->len }; 1215 return (iRanges){ line->offset, line->offset + line->len };
928} 1216}
1217#endif
929 1218
930static void extendRange_InputWidget_(iInputWidget *d, size_t *pos, int dir) { 1219static void extendRange_InputWidget_(iInputWidget *d, size_t *pos, int dir) {
1220#if 0
931 const size_t textLen = size_Array(&d->text); 1221 const size_t textLen = size_Array(&d->text);
932 if (dir < 0 && *pos > 0) { 1222 if (dir < 0 && *pos > 0) {
933 for ((*pos)--; *pos > 0; (*pos)--) { 1223 for ((*pos)--; *pos > 0; (*pos)--) {
@@ -942,6 +1232,7 @@ static void extendRange_InputWidget_(iInputWidget *d, size_t *pos, int dir) {
942 /* continue */ 1232 /* continue */
943 } 1233 }
944 } 1234 }
1235#endif
945} 1236}
946 1237
947static iRect bounds_InputWidget_(const iInputWidget *d) { 1238static iRect bounds_InputWidget_(const iInputWidget *d) {
@@ -950,7 +1241,8 @@ static iRect bounds_InputWidget_(const iInputWidget *d) {
950 if (!isFocused_Widget(d)) { 1241 if (!isFocused_Widget(d)) {
951 return bounds; 1242 return bounds;
952 } 1243 }
953 bounds.size.y = contentHeight_InputWidget_(d, iFalse) + 3 * padding_().y; 1244 /* There may be more visible lines than fits in the widget bounds. */
1245 bounds.size.y = contentHeight_InputWidget_(d) + 3 * padding_().y;
954 if (w->flags & extraPadding_WidgetFlag) { 1246 if (w->flags & extraPadding_WidgetFlag) {
955 bounds.size.y += extraPaddingHeight_; 1247 bounds.size.y += extraPaddingHeight_;
956 } 1248 }
@@ -961,6 +1253,21 @@ static iBool contains_InputWidget_(const iInputWidget *d, iInt2 coord) {
961 return contains_Rect(bounds_InputWidget_(d), coord); 1253 return contains_Rect(bounds_InputWidget_(d), coord);
962} 1254}
963 1255
1256static void lineTextWasChanged_InputWidget_(iInputWidget *d, iInputLine *line) {
1257 updateLine_InputWidget_(d, line);
1258 /* Update affected ranges. */ {
1259 line->range.end = line->range.start + size_String(&line->text);
1260 for (size_t i = indexOf_Array(&d->lines, line) + 1; i < size_Array(&d->lines); i++) {
1261 iInputLine *next = at_Array(&d->lines, i);
1262 next->range.start = line->range.end;
1263 next->range.end = next->range.start + size_String(&next->text);
1264 line = next;
1265 }
1266 }
1267 updateVisible_InputWidget_(d);
1268 updateMetrics_InputWidget_(d);
1269}
1270
964static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { 1271static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
965 iWidget *w = as_Widget(d); 1272 iWidget *w = as_Widget(d);
966 if (isCommand_Widget(w, ev, "focus.gained")) { 1273 if (isCommand_Widget(w, ev, "focus.gained")) {
@@ -1014,15 +1321,17 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1014 } 1321 }
1015 else if (isMetricsChange_UserEvent(ev)) { 1322 else if (isMetricsChange_UserEvent(ev)) {
1016 updateMetrics_InputWidget_(d); 1323 updateMetrics_InputWidget_(d);
1017 updateLinesAndResize_InputWidget_(d); 1324 // updateLinesAndResize_InputWidget_(d);
1018 } 1325 }
1019 else if (isResize_UserEvent(ev) || d->lastUpdateWidth != w->rect.size.x) { 1326 else if (isResize_UserEvent(ev) || d->lastUpdateWidth != w->rect.size.x) {
1020 d->inFlags |= needUpdateBuffer_InputWidgetFlag; 1327 d->inFlags |= needUpdateBuffer_InputWidgetFlag;
1328#if 0
1021 if (d->inFlags & isUrl_InputWidgetFlag) { 1329 if (d->inFlags & isUrl_InputWidgetFlag) {
1022 /* Restore/omit the default scheme if necessary. */ 1330 /* Restore/omit the default scheme if necessary. */
1023 setText_InputWidget(d, text_InputWidget(d)); 1331 setText_InputWidget(d, text_InputWidget(d));
1024 } 1332 }
1025 updateLinesAndResize_InputWidget_(d); 1333#endif
1334// updateLinesAndResize_InputWidget_(d);
1026 } 1335 }
1027 else if (isFocused_Widget(d) && isCommand_UserEvent(ev, "copy")) { 1336 else if (isFocused_Widget(d) && isCommand_UserEvent(ev, "copy")) {
1028 copy_InputWidget_(d, iFalse); 1337 copy_InputWidget_(d, iFalse);
@@ -1042,6 +1351,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1042 break; 1351 break;
1043 case started_ClickResult: { 1352 case started_ClickResult: {
1044 setFocus_Widget(w); 1353 setFocus_Widget(w);
1354#if 0
1045 const size_t oldCursor = d->cursor; 1355 const size_t oldCursor = d->cursor;
1046 setCursor_InputWidget(d, coordIndex_InputWidget_(d, pos_Click(&d->click))); 1356 setCursor_InputWidget(d, coordIndex_InputWidget_(d, pos_Click(&d->click)));
1047 if (keyMods_Sym(modState_Keys()) == KMOD_SHIFT) { 1357 if (keyMods_Sym(modState_Keys()) == KMOD_SHIFT) {
@@ -1064,12 +1374,14 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1064 selectAll_InputWidget(d); 1374 selectAll_InputWidget(d);
1065 } 1375 }
1066 } 1376 }
1377#endif
1067 return iTrue; 1378 return iTrue;
1068 } 1379 }
1069 case aborted_ClickResult: 1380 case aborted_ClickResult:
1070 d->inFlags &= ~isMarking_InputWidgetFlag; 1381 d->inFlags &= ~isMarking_InputWidgetFlag;
1071 return iTrue; 1382 return iTrue;
1072 case drag_ClickResult: 1383 case drag_ClickResult:
1384#if 0
1073 d->cursor = coordIndex_InputWidget_(d, pos_Click(&d->click)); 1385 d->cursor = coordIndex_InputWidget_(d, pos_Click(&d->click));
1074 showCursor_InputWidget_(d); 1386 showCursor_InputWidget_(d);
1075 if (~d->inFlags & isMarking_InputWidgetFlag) { 1387 if (~d->inFlags & isMarking_InputWidgetFlag) {
@@ -1082,6 +1394,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1082 extendRange_InputWidget_(d, &d->mark.end, isFwd ? +1 : -1); 1394 extendRange_InputWidget_(d, &d->mark.end, isFwd ? +1 : -1);
1083 d->mark.start = isFwd ? d->initialMark.start : d->initialMark.end; 1395 d->mark.start = isFwd ? d->initialMark.start : d->initialMark.end;
1084 } 1396 }
1397#endif
1085 refresh_Widget(w); 1398 refresh_Widget(w);
1086 return iTrue; 1399 return iTrue;
1087 case finished_ClickResult: 1400 case finished_ClickResult:
@@ -1108,10 +1421,14 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1108 if (ev->type == SDL_KEYUP && isFocused_Widget(w)) { 1421 if (ev->type == SDL_KEYUP && isFocused_Widget(w)) {
1109 return iTrue; 1422 return iTrue;
1110 } 1423 }
1424#if 0
1111 const size_t curMax = cursorMax_InputWidget_(d); 1425 const size_t curMax = cursorMax_InputWidget_(d);
1112 const iRanges lineRange = lineRange_InputWidget_(d);
1113 const size_t lineFirst = lineRange.start; 1426 const size_t lineFirst = lineRange.start;
1114 const size_t lineLast = lineRange.end == curMax ? curMax : iMax(lineRange.start, lineRange.end - 1); 1427 const size_t lineLast = lineRange.end == curMax ? curMax : iMax(lineRange.start, lineRange.end - 1);
1428#endif
1429 const iInt2 curMax = cursorMax_InputWidget_(d);
1430 const iInt2 lineFirst = init_I2(0, d->cursor.y);
1431 const iInt2 lineLast = init_I2(endX_InputWidget_(d, d->cursor.y), d->cursor.y);
1115 if (ev->type == SDL_KEYDOWN && isFocused_Widget(w)) { 1432 if (ev->type == SDL_KEYDOWN && isFocused_Widget(w)) {
1116 const int key = ev->key.keysym.sym; 1433 const int key = ev->key.keysym.sym;
1117 const int mods = keyMods_Sym(ev->key.keysym.mod); 1434 const int mods = keyMods_Sym(ev->key.keysym.mod);
@@ -1132,6 +1449,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1132 return iTrue; 1449 return iTrue;
1133 } 1450 }
1134 } 1451 }
1452#if 0
1135#if defined (iPlatformApple) 1453#if defined (iPlatformApple)
1136 if (mods == KMOD_PRIMARY || mods == (KMOD_PRIMARY | KMOD_SHIFT)) { 1454 if (mods == KMOD_PRIMARY || mods == (KMOD_PRIMARY | KMOD_SHIFT)) {
1137 switch (key) { 1455 switch (key) {
@@ -1142,14 +1460,16 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1142 return iTrue; 1460 return iTrue;
1143 } 1461 }
1144 } 1462 }
1145#endif 1463#endif
1146 d->lastCursor = d->cursor; 1464#endif // 0
1465 d->prevCursor = d->cursor;
1147 switch (key) { 1466 switch (key) {
1148 case SDLK_INSERT: 1467 case SDLK_INSERT:
1149 if (mods == KMOD_SHIFT) { 1468 if (mods == KMOD_SHIFT) {
1150 paste_InputWidget_(d); 1469 paste_InputWidget_(d);
1151 } 1470 }
1152 return iTrue; 1471 return iTrue;
1472#if 0
1153 case SDLK_RETURN: 1473 case SDLK_RETURN:
1154 case SDLK_KP_ENTER: 1474 case SDLK_KP_ENTER:
1155 if (mods == KMOD_SHIFT || (d->maxLen == 0 && 1475 if (mods == KMOD_SHIFT || (d->maxLen == 0 &&
@@ -1166,10 +1486,12 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1166 setFocus_Widget(NULL); 1486 setFocus_Widget(NULL);
1167 } 1487 }
1168 return iTrue; 1488 return iTrue;
1489#endif
1169 case SDLK_ESCAPE: 1490 case SDLK_ESCAPE:
1170 end_InputWidget(d, iFalse); 1491 end_InputWidget(d, iFalse);
1171 setFocus_Widget(NULL); 1492 setFocus_Widget(NULL);
1172 return (d->inFlags & eatEscape_InputWidgetFlag) != 0; 1493 return (d->inFlags & eatEscape_InputWidgetFlag) != 0;
1494#if 0
1173 case SDLK_BACKSPACE: 1495 case SDLK_BACKSPACE:
1174 if (!isEmpty_Range(&d->mark)) { 1496 if (!isEmpty_Range(&d->mark)) {
1175 pushUndo_InputWidget_(d); 1497 pushUndo_InputWidget_(d);
@@ -1265,6 +1587,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1265 return iTrue; 1587 return iTrue;
1266 } 1588 }
1267 break; 1589 break;
1590#endif
1268 case SDLK_LEFT: 1591 case SDLK_LEFT:
1269 case SDLK_RIGHT: { 1592 case SDLK_RIGHT: {
1270 const int dir = (key == SDLK_LEFT ? -1 : +1); 1593 const int dir = (key == SDLK_LEFT ? -1 : +1);
@@ -1276,11 +1599,11 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1276 } 1599 }
1277 else if (!isMarking_() && !isEmpty_Range(&d->mark)) { 1600 else if (!isMarking_() && !isEmpty_Range(&d->mark)) {
1278 const iRanges m = mark_InputWidget_(d); 1601 const iRanges m = mark_InputWidget_(d);
1279 setCursor_InputWidget(d, dir < 0 ? m.start : m.end); 1602 setCursor_InputWidget(d, indexToCursor_InputWidget_(d, dir < 0 ? m.start : m.end));
1280 iZap(d->mark); 1603 iZap(d->mark);
1281 } 1604 }
1282 else if ((dir < 0 && d->cursor > 0) || (dir > 0 && d->cursor < curMax)) { 1605 else {
1283 setCursor_InputWidget(d, d->cursor + dir); 1606 setCursor_InputWidget(d, movedCursor_InputWidget_(d, d->cursor, dir, 0));
1284 } 1607 }
1285 refresh_Widget(w); 1608 refresh_Widget(w);
1286 return iTrue; 1609 return iTrue;
@@ -1288,6 +1611,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1288 case SDLK_TAB: 1611 case SDLK_TAB:
1289 /* Allow focus switching. */ 1612 /* Allow focus switching. */
1290 return processEvent_Widget(as_Widget(d), ev); 1613 return processEvent_Widget(as_Widget(d), ev);
1614#if 0
1291 case SDLK_UP: 1615 case SDLK_UP:
1292 case SDLK_DOWN: 1616 case SDLK_DOWN:
1293 if (moveCursorByLine_InputWidget_(d, key == SDLK_UP ? -1 : +1)) { 1617 if (moveCursorByLine_InputWidget_(d, key == SDLK_UP ? -1 : +1)) {
@@ -1302,7 +1626,8 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1302 moveCursorByLine_InputWidget_(d, key == SDLK_PAGEUP ? -1 : +1); 1626 moveCursorByLine_InputWidget_(d, key == SDLK_PAGEUP ? -1 : +1);
1303 } 1627 }
1304 refresh_Widget(d); 1628 refresh_Widget(d);
1305 return iTrue; 1629 return iTrue;
1630#endif
1306 } 1631 }
1307 if (mods & (KMOD_PRIMARY | KMOD_SECONDARY)) { 1632 if (mods & (KMOD_PRIMARY | KMOD_SECONDARY)) {
1308 return iFalse; 1633 return iFalse;
@@ -1312,16 +1637,57 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1312 else if (ev->type == SDL_TEXTINPUT && isFocused_Widget(w)) { 1637 else if (ev->type == SDL_TEXTINPUT && isFocused_Widget(w)) {
1313 pushUndo_InputWidget_(d); 1638 pushUndo_InputWidget_(d);
1314 deleteMarked_InputWidget_(d); 1639 deleteMarked_InputWidget_(d);
1315 const iString *uni = collectNewCStr_String(ev->text.text); 1640 insertRange_InputWidget_(d, range_CStr(ev->text.text));
1316 iConstForEach(String, i, uni) { 1641 lineTextWasChanged_InputWidget_(d, cursorLine_InputWidget_(d));
1317 insertChar_InputWidget_(d, i.value);
1318 }
1319 contentsWereChanged_InputWidget_(d); 1642 contentsWereChanged_InputWidget_(d);
1320 return iTrue; 1643 return iTrue;
1321 } 1644 }
1322 return processEvent_Widget(w, ev); 1645 return processEvent_Widget(w, ev);
1323} 1646}
1324 1647
1648iDeclareType(MarkPainter)
1649
1650struct Impl_MarkPainter {
1651 iPaint *paint;
1652 const iInputWidget *d;
1653 iRect contentBounds;
1654 const iInputLine *line;
1655 iInt2 pos;
1656 iRanges mark;
1657};
1658
1659static iBool draw_MarkPainter_(iWrapText *wrapText, iRangecc wrappedText, int origin, int advance,
1660 iBool isBaseRTL) {
1661 iUnused(isBaseRTL);
1662 iMarkPainter *mp = wrapText->context;
1663 const iRanges mark = mp->mark;
1664 if (isEmpty_Range(&mark)) {
1665 return iTrue; /* nothing marked */
1666 }
1667 const char *cstr = cstr_String(&mp->line->text);
1668 const iRanges lineRange = {
1669 wrappedText.start - cstr + mp->line->range.start,
1670 wrappedText.end - cstr + mp->line->range.start
1671 };
1672 if (mark.end <= lineRange.start || mark.start >= lineRange.end) {
1673 return iTrue; /* outside of mark */
1674 }
1675 iRect rect = { addX_I2(mp->pos, origin), init_I2(advance, lineHeight_Text(mp->d->font)) };
1676 if (mark.end < lineRange.end) {
1677 /* Calculate where the mark ends. */
1678 const iRangecc markedPrefix = { cstr, cstr + mark.end - lineRange.start };
1679 rect.size.x = measureRange_Text(mp->d->font, markedPrefix).advance.x;
1680 }
1681 if (mark.start > lineRange.start) {
1682 /* Calculate where the mark starts. */
1683 const iRangecc unmarkedPrefix = { cstr, cstr + mark.start - lineRange.start };
1684 adjustEdges_Rect(&rect, 0, 0, 0, measureRange_Text(mp->d->font, unmarkedPrefix).advance.x);
1685 }
1686 rect.size.x = iMax(gap_UI / 3, rect.size.x);
1687 fillRect_Paint(mp->paint, rect, uiMarked_ColorId);
1688 return iTrue;
1689}
1690
1325static void draw_InputWidget_(const iInputWidget *d) { 1691static void draw_InputWidget_(const iInputWidget *d) {
1326 const iWidget *w = constAs_Widget(d); 1692 const iWidget *w = constAs_Widget(d);
1327 iRect bounds = adjusted_Rect(bounds_InputWidget_(d), padding_(), neg_I2(padding_())); 1693 iRect bounds = adjusted_Rect(bounds_InputWidget_(d), padding_(), neg_I2(padding_()));
@@ -1346,19 +1712,42 @@ static void draw_InputWidget_(const iInputWidget *d) {
1346 init_I2(-d->rightPadding, w->flags & extraPadding_WidgetFlag ? -gap_UI / 2 : 0))); 1712 init_I2(-d->rightPadding, w->flags & extraPadding_WidgetFlag ? -gap_UI / 2 : 0)));
1347 const iRect contentBounds = contentBounds_InputWidget_(d); 1713 const iRect contentBounds = contentBounds_InputWidget_(d);
1348 iInt2 drawPos = topLeft_Rect(contentBounds); 1714 iInt2 drawPos = topLeft_Rect(contentBounds);
1349 const int fg = isHint ? uiAnnotation_ColorId 1715 const int fg = isHint ? uiAnnotation_ColorId
1350 : isFocused && !isEmpty_Array(&d->text) ? uiInputTextFocused_ColorId 1716 : isFocused /*&& !isEmpty_Array(&d->lines)*/ ? uiInputTextFocused_ColorId
1351 : uiInputText_ColorId; 1717 : uiInputText_ColorId;
1718 iWrapText wrapText = {
1719 .maxWidth = width_Rect(contentBounds),
1720 .mode = (d->inFlags & isUrl_InputWidgetFlag ? anyCharacter_WrapTextMode : word_WrapTextMode)
1721 };
1352 /* If buffered, just draw the buffered copy. */ 1722 /* If buffered, just draw the buffered copy. */
1353 if (d->buffered && !isFocused) { //&& !isFocused/* && !isHint*/) { 1723 if (d->buffered && !isFocused) {
1354 /* Most input widgets will use this, since only one is focused at a time. */ 1724 /* Most input widgets will use this, since only one is focused at a time. */
1355 draw_TextBuf(d->buffered, topLeft_Rect(contentBounds), white_ColorId); 1725 draw_TextBuf(d->buffered, drawPos, white_ColorId);
1356 } 1726 }
1357 else if (isHint) { 1727 else if (isHint) {
1358 drawRange_Text(d->font, topLeft_Rect(contentBounds), uiAnnotation_ColorId, 1728 drawRange_Text(d->font, drawPos, uiAnnotation_ColorId, range_String(&d->hint));
1359 range_String(&d->hint));
1360 } 1729 }
1361 else { 1730 else {
1731 /* TODO: Make a function out of this. */
1732 drawPos.y += d->visLineOffsetY;
1733 iMarkPainter marker = {
1734 .paint = &p,
1735 .d = d,
1736 .contentBounds = contentBounds,
1737 .mark = mark_InputWidget_(d)
1738 };
1739 for (size_t vis = d->visLines.start; vis < d->visLines.end; vis++) {
1740 const iInputLine *line = constAt_Array(&d->lines, vis);
1741 wrapText.text = range_String(&line->text);
1742 wrapText.wrapFunc = isFocused ? draw_MarkPainter_ : NULL; /* mark is drawn under each line of text */
1743 wrapText.context = &marker;
1744 marker.line = line;
1745 marker.pos = drawPos;
1746 addv_I2(&drawPos, draw_WrapText(&wrapText, d->font, drawPos, fg).advance); /* lines end with \n */
1747 }
1748 wrapText.wrapFunc = NULL;
1749 wrapText.context = NULL;
1750#if 0
1362 iConstForEach(Array, i, &d->lines) { 1751 iConstForEach(Array, i, &d->lines) {
1363 const iInputLine *line = i.value; 1752 const iInputLine *line = i.value;
1364 const iBool isLast = index_ArrayConstIterator(&i) == size_Array(&d->lines) - 1; 1753 const iBool isLast = index_ArrayConstIterator(&i) == size_Array(&d->lines) - 1;
@@ -1385,12 +1774,14 @@ static void draw_InputWidget_(const iInputWidget *d) {
1385 drawRange_Text(d->font, drawPos, fg, range_String(&line->text)); 1774 drawRange_Text(d->font, drawPos, fg, range_String(&line->text));
1386 drawPos.y += lineHeight_Text(d->font); 1775 drawPos.y += lineHeight_Text(d->font);
1387 } 1776 }
1777#endif
1388 } 1778 }
1389 unsetClip_Paint(&p); 1779 unsetClip_Paint(&p);
1390 /* Cursor blinking. */ 1780 /* Draw the insertion point. */
1391 if (isFocused && d->cursorVis) { 1781 if (isFocused && d->cursorVis) {
1392 iString cur; 1782 iString cur;
1393 iInt2 curSize; 1783 iInt2 curSize;
1784#if 0
1394 if (d->mode == overwrite_InputMode) { 1785 if (d->mode == overwrite_InputMode) {
1395 /* Block cursor that overlaps a character. */ 1786 /* Block cursor that overlaps a character. */
1396 if (d->cursor < size_Array(&d->text)) { 1787 if (d->cursor < size_Array(&d->text)) {
@@ -1408,20 +1799,29 @@ static void draw_InputWidget_(const iInputWidget *d) {
1408 iMin(2, gap_UI / 4)); 1799 iMin(2, gap_UI / 4));
1409 } 1800 }
1410 else { 1801 else {
1802#endif
1411 /* Bar cursor. */ 1803 /* Bar cursor. */
1412 curSize = init_I2(gap_UI / 2, lineHeight_Text(d->font)); 1804 curSize = init_I2(gap_UI / 2, lineHeight_Text(d->font));
1413 } 1805// }
1414 const iInputLine *curLine = line_InputWidget_(d, d->cursorLine); 1806 const iInputLine *curLine = constCursorLine_InputWidget_(d);
1415 const iString * text = &curLine->text; 1807 const iString * text = &curLine->text;
1416 /* The bounds include visible characters, while advance includes whitespace as well. 1808 /* The bounds include visible characters, while advance includes whitespace as well.
1417 Normally only the advance is needed, but if the cursor is at a newline, the advance 1809 Normally only the advance is needed, but if the cursor is at a newline, the advance
1418 will have reset back to zero. */ 1810 will have reset back to zero. */
1419 const int prefixSize = maxWidth_TextMetrics(measureN_Text(d->font, 1811 wrapText.text = range_String(text);
1420 cstr_String(text), 1812 wrapText.text.end = wrapText.text.start + d->cursor.x;
1421 d->cursor - curLine->offset)); 1813 /* TODO: Count lines above for offset. */
1422 const iInt2 curPos = addX_I2(addY_I2(contentBounds.pos, lineHeight_Text(d->font) * d->cursorLine), 1814 //const int prefixSize = maxWidth_TextMetrics(
1423 prefixSize + 1815 const iTextMetrics tm = measure_WrapText(&wrapText, d->font);
1424 (d->mode == insert_InputMode ? -curSize.x / 2 : 0)); 1816 // const iInt2 curPos = addX_I2(addY_I2(contentBounds.pos, lineHeight_Text(d->font)
1817 // * d->cursorLine),
1818 // prefixSize +
1819 // (d->mode == insert_InputMode ? -curSize.x / 2 :
1820 // 0));
1821 //printf("%d -> tm.advance: %d, %d\n", d->cursor.x, tm.advance.x, tm.advance.y);
1822 const iInt2 curPos = add_I2(addY_I2(topLeft_Rect(contentBounds), d->visLineOffsetY),
1823 addX_I2(tm.advance,
1824 (d->mode == insert_InputMode ? -curSize.x / 2 : 0)));
1425 const iRect curRect = { curPos, curSize }; 1825 const iRect curRect = { curPos, curSize };
1426 fillRect_Paint(&p, curRect, uiInputCursor_ColorId); 1826 fillRect_Paint(&p, curRect, uiInputCursor_ColorId);
1427 if (d->mode == overwrite_InputMode) { 1827 if (d->mode == overwrite_InputMode) {
diff --git a/src/ui/inputwidget.h b/src/ui/inputwidget.h
index c70d9ad6..4fb2c9dc 100644
--- a/src/ui/inputwidget.h
+++ b/src/ui/inputwidget.h
@@ -47,9 +47,9 @@ void setMaxLen_InputWidget (iInputWidget *, size_t maxLen);
47void setText_InputWidget (iInputWidget *, const iString *text); 47void setText_InputWidget (iInputWidget *, const iString *text);
48void setTextCStr_InputWidget (iInputWidget *, const char *cstr); 48void setTextCStr_InputWidget (iInputWidget *, const char *cstr);
49void setFont_InputWidget (iInputWidget *, int fontId); 49void setFont_InputWidget (iInputWidget *, int fontId);
50void setCursor_InputWidget (iInputWidget *, size_t pos); 50//void setCursor_InputWidget (iInputWidget *, size_t pos);
51void setContentPadding_InputWidget (iInputWidget *, int left, int right); /* only affects the text entry */ 51void setContentPadding_InputWidget (iInputWidget *, int left, int right); /* only affects the text entry */
52void setMaxLayoutLines_InputWidget (iInputWidget *, size_t maxLayoutLines); 52void setLineLimits_InputWidget (iInputWidget *, int minVis, int maxVis);
53void setValidator_InputWidget (iInputWidget *, iInputWidgetValidatorFunc validator, void *context); 53void setValidator_InputWidget (iInputWidget *, iInputWidgetValidatorFunc validator, void *context);
54void setEnterInsertsLF_InputWidget (iInputWidget *, iBool enterInsertsLF); 54void setEnterInsertsLF_InputWidget (iInputWidget *, iBool enterInsertsLF);
55void setEnterKeyEnabled_InputWidget (iInputWidget *, iBool enterKeyEnabled); 55void setEnterKeyEnabled_InputWidget (iInputWidget *, iBool enterKeyEnabled);
diff --git a/src/ui/root.c b/src/ui/root.c
index 91077019..9d92c44e 100644
--- a/src/ui/root.c
+++ b/src/ui/root.c
@@ -1076,7 +1076,7 @@ void createUserInterface_Root(iRoot *d) {
1076 setFlags_Widget(as_Widget(url), resizeHeightOfChildren_WidgetFlag, iTrue); 1076 setFlags_Widget(as_Widget(url), resizeHeightOfChildren_WidgetFlag, iTrue);
1077 setSelectAllOnFocus_InputWidget(url, iTrue); 1077 setSelectAllOnFocus_InputWidget(url, iTrue);
1078 setId_Widget(as_Widget(url), "url"); 1078 setId_Widget(as_Widget(url), "url");
1079 setMaxLayoutLines_InputWidget(url, 1); 1079 setLineLimits_InputWidget(url, 1, 1); /* just one line while not focused */
1080 setUrlContent_InputWidget(url, iTrue); 1080 setUrlContent_InputWidget(url, iTrue);
1081 setNotifyEdits_InputWidget(url, iTrue); 1081 setNotifyEdits_InputWidget(url, iTrue);
1082 setTextCStr_InputWidget(url, "gemini://"); 1082 setTextCStr_InputWidget(url, "gemini://");
diff --git a/src/ui/text.c b/src/ui/text.c
index b283d700..49b3f0cd 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -855,18 +855,6 @@ static void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, i
855 run->logical.start = endAt; 855 run->logical.start = endAt;
856} 856}
857 857
858size_t length_Rangecc(const iRangecc d) {
859 size_t n = 0;
860 for (const char *i = d.start; i < d.end; ) {
861 iChar ch;
862 const int chLen = decodeBytes_MultibyteChar(i, d.end, &ch);
863 if (chLen <= 0) break;
864 i += chLen;
865 n++;
866 }
867 return n;
868}
869
870static enum iFontId fontId_Text_(const iFont *font) { 858static enum iFontId fontId_Text_(const iFont *font) {
871 return (enum iFontId) (font - text_.fonts); 859 return (enum iFontId) (font - text_.fonts);
872} 860}
@@ -1436,7 +1424,14 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1436 int wrapResumePos = textLen; /* logical position where next line resumes */ 1424 int wrapResumePos = textLen; /* logical position where next line resumes */
1437 size_t wrapResumeRunIndex = runCount; /* index of run where next line resumes */ 1425 size_t wrapResumeRunIndex = runCount; /* index of run where next line resumes */
1438 const int layoutBound = (args->wrap ? args->wrap->maxWidth : 0); 1426 const int layoutBound = (args->wrap ? args->wrap->maxWidth : 0);
1427 iBool isFirst = iTrue;
1439 while (!isEmpty_Range(&wrapRuns)) { 1428 while (!isEmpty_Range(&wrapRuns)) {
1429 if (isFirst) {
1430 isFirst = iFalse;
1431 }
1432 else {
1433 yCursor += d->height;
1434 }
1440 float wrapAdvance = 0.0f; 1435 float wrapAdvance = 0.0f;
1441 /* First we need to figure out how much text fits on the current line. */ 1436 /* First we need to figure out how much text fits on the current line. */
1442 if (args->wrap && args->wrap->maxWidth > 0) { 1437 if (args->wrap && args->wrap->maxWidth > 0) {
@@ -1701,7 +1696,6 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1701 wrapRuns.end = runCount; 1696 wrapRuns.end = runCount;
1702 wrapPosRange.start = wrapResumePos; 1697 wrapPosRange.start = wrapResumePos;
1703 wrapPosRange.end = textLen; 1698 wrapPosRange.end = textLen;
1704 yCursor += d->height;
1705 } 1699 }
1706 if (args->cursorAdvance_out) { 1700 if (args->cursorAdvance_out) {
1707 *args->cursorAdvance_out = init_I2(xCursor, yCursor); 1701 *args->cursorAdvance_out = init_I2(xCursor, yCursor);
@@ -1962,8 +1956,9 @@ iTextMetrics measure_WrapText(iWrapText *d, int fontId) {
1962 return tm; 1956 return tm;
1963} 1957}
1964 1958
1965void draw_WrapText(iWrapText *d, int fontId, iInt2 pos, int color) { 1959iTextMetrics draw_WrapText(iWrapText *d, int fontId, iInt2 pos, int color) {
1966 run_Font_(font_Text_(fontId), 1960 iTextMetrics tm;
1961 tm.bounds = run_Font_(font_Text_(fontId),
1967 &(iRunArgs){ .mode = draw_RunMode | runFlagsFromId_(fontId) | 1962 &(iRunArgs){ .mode = draw_RunMode | runFlagsFromId_(fontId) |
1968 (color & permanent_ColorId ? permanentColorFlag_RunMode : 0) | 1963 (color & permanent_ColorId ? permanentColorFlag_RunMode : 0) |
1969 (color & fillBackground_ColorId ? fillBackground_RunMode : 0), 1964 (color & fillBackground_ColorId ? fillBackground_RunMode : 0),
@@ -1971,7 +1966,9 @@ void draw_WrapText(iWrapText *d, int fontId, iInt2 pos, int color) {
1971 .pos = pos, 1966 .pos = pos,
1972 .wrap = d, 1967 .wrap = d,
1973 .color = color & mask_ColorId, 1968 .color = color & mask_ColorId,
1969 .cursorAdvance_out = &tm.advance,
1974 }); 1970 });
1971 return tm;
1975} 1972}
1976 1973
1977SDL_Texture *glyphCache_Text(void) { 1974SDL_Texture *glyphCache_Text(void) {
diff --git a/src/ui/text.h b/src/ui/text.h
index d779dd96..d4f820d4 100644
--- a/src/ui/text.h
+++ b/src/ui/text.h
@@ -217,7 +217,7 @@ struct Impl_WrapText {
217}; 217};
218 218
219iTextMetrics measure_WrapText (iWrapText *, int fontId); 219iTextMetrics measure_WrapText (iWrapText *, int fontId);
220void draw_WrapText (iWrapText *, int fontId, iInt2 pos, int color); 220iTextMetrics draw_WrapText (iWrapText *, int fontId, iInt2 pos, int color);
221 221
222SDL_Texture * glyphCache_Text (void); 222SDL_Texture * glyphCache_Text (void);
223 223
diff --git a/src/ui/uploadwidget.c b/src/ui/uploadwidget.c
index f2ddda98..fd363201 100644
--- a/src/ui/uploadwidget.c
+++ b/src/ui/uploadwidget.c
@@ -85,6 +85,7 @@ void init_UploadWidget(iUploadWidget *d) {
85 iWidget *page = new_Widget(); 85 iWidget *page = new_Widget();
86 setFlags_Widget(page, arrangeSize_WidgetFlag, iTrue); 86 setFlags_Widget(page, arrangeSize_WidgetFlag, iTrue);
87 d->input = new_InputWidget(0); 87 d->input = new_InputWidget(0);
88 setFont_InputWidget(d->input, monospace_FontId);
88 setHint_InputWidget(d->input, "${hint.upload.text}"); 89 setHint_InputWidget(d->input, "${hint.upload.text}");
89 setEnterInsertsLF_InputWidget(d->input, iTrue); 90 setEnterInsertsLF_InputWidget(d->input, iTrue);
90 setFixedSize_Widget(as_Widget(d->input), init_I2(120 * gap_UI, -1)); 91 setFixedSize_Widget(as_Widget(d->input), init_I2(120 * gap_UI, -1));