diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-07-20 09:01:25 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-07-20 09:01:25 +0300 |
commit | dc1528e89d48947780e00fc1a49ce57cccdfbfe5 (patch) | |
tree | 4bd888f70dcf7bdd3ad166b43176232c42920594 | |
parent | 3ccdfae64b82d9716de1f94f7d81de9c8765b607 (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.c | 776 | ||||
-rw-r--r-- | src/ui/inputwidget.h | 4 | ||||
-rw-r--r-- | src/ui/root.c | 2 | ||||
-rw-r--r-- | src/ui/text.c | 27 | ||||
-rw-r--r-- | src/ui/text.h | 2 | ||||
-rw-r--r-- | src/ui/uploadwidget.c | 1 |
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 | |||
58 | iDeclareType(InputLine) | ||
59 | |||
60 | struct Impl_InputLine { | ||
61 | //size_t offset; /* character position from the beginning */ | ||
62 | //size_t len; /* length as characters */ | ||
63 | iRanges range; /* byte offset inside the entire content; for marking */ | ||
64 | iString text; /* UTF-8 */ | ||
65 | int numVisLines; /* wrapped lines */ | ||
66 | }; | ||
67 | |||
68 | static void init_InputLine(iInputLine *d) { | ||
69 | iZap(d->range); | ||
70 | init_String(&d->text); | ||
71 | d->numVisLines = 1; | ||
72 | } | ||
73 | |||
74 | static void deinit_InputLine(iInputLine *d) { | ||
75 | deinit_String(&d->text); | ||
76 | } | ||
77 | |||
78 | static void clearInputLines_(iArray *inputLines) { | ||
79 | iForEach(Array, i, inputLines) { | ||
80 | deinit_InputLine(i.value); | ||
81 | } | ||
82 | clear_Array(inputLines); | ||
83 | } | ||
84 | |||
85 | static 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 | |||
112 | static 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 | |||
133 | static void mergeLines_(const iArray *inputLines, iString *merged) { | ||
134 | mergeLinesRange_(inputLines, (iRanges){ 0, iInvalidSize }, merged); | ||
135 | } | ||
136 | |||
137 | iDefineTypeConstruction(InputLine) | ||
138 | |||
139 | /*----------------------------------------------------------------------------------------------*/ | ||
140 | |||
56 | iDeclareType(InputUndo) | 141 | iDeclareType(InputUndo) |
57 | 142 | ||
58 | struct Impl_InputUndo { | 143 | struct Impl_InputUndo { |
59 | iArray text; | 144 | iString text; |
60 | size_t cursor; | 145 | iInt2 cursor; |
61 | }; | 146 | }; |
62 | 147 | ||
63 | static void init_InputUndo_(iInputUndo *d, const iArray *text, size_t cursor) { | 148 | static 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 | ||
68 | static void deinit_InputUndo_(iInputUndo *d) { | 154 | static void deinit_InputUndo_(iInputUndo *d) { |
69 | deinit_Array(&d->text); | 155 | deinit_String(&d->text); |
70 | } | 156 | } |
71 | 157 | ||
72 | enum iInputWidgetFlag { | 158 | enum iInputWidgetFlag { |
@@ -85,45 +171,32 @@ enum iInputWidgetFlag { | |||
85 | 171 | ||
86 | /*----------------------------------------------------------------------------------------------*/ | 172 | /*----------------------------------------------------------------------------------------------*/ |
87 | 173 | ||
88 | iDeclareType(InputLine) | ||
89 | |||
90 | struct 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 | |||
96 | static void init_InputLine(iInputLine *d) { | ||
97 | d->offset = 0; | ||
98 | init_String(&d->text); | ||
99 | } | ||
100 | |||
101 | static void deinit_InputLine(iInputLine *d) { | ||
102 | deinit_String(&d->text); | ||
103 | } | ||
104 | |||
105 | iDefineTypeConstruction(InputLine) | ||
106 | |||
107 | /*----------------------------------------------------------------------------------------------*/ | ||
108 | |||
109 | struct Impl_InputWidget { | 174 | struct 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 | ||
139 | iDefineObjectConstructionArgs(InputWidget, (size_t maxLen), maxLen) | 212 | iDefineObjectConstructionArgs(InputWidget, (size_t maxLen), maxLen) |
140 | 213 | ||
141 | static void clearUndo_InputWidget_(iInputWidget *d) { | 214 | static 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 | ||
154 | static iRect contentBounds_InputWidget_(const iInputWidget *d) { | 227 | static 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 | ||
168 | static void updateCursorLine_InputWidget_(iInputWidget *d) { | 241 | static 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 | |||
268 | static 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 | |||
277 | static 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 | |||
282 | iLocalDef iBool isLastLine_InputWidget_(const iInputWidget *d, const iInputLine *line) { | ||
283 | return (const void *) line == constBack_Array(&d->lines); | ||
284 | } | ||
285 | |||
286 | static 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 | |||
292 | static 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 | |||
307 | static 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 | ||
194 | static void showCursor_InputWidget_(iInputWidget *d) { | 326 | static 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 | ||
199 | static void invalidateBuffered_InputWidget_(iInputWidget *d) { | 332 | static void invalidateBuffered_InputWidget_(iInputWidget *d) { |
@@ -218,33 +351,49 @@ static void updateSizeForFixedLength_InputWidget_(iInputWidget *d) { | |||
218 | } | 351 | } |
219 | } | 352 | } |
220 | 353 | ||
221 | static const iChar sensitiveChar_ = 0x25cf; /* black circle */ | 354 | static 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 | ||
223 | static iString *utf32toUtf8_InputWidget_(const iInputWidget *d) { | 361 | static 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 | ||
227 | static iString *visText_InputWidget_(const iInputWidget *d) { | 371 | static 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 | ||
241 | static void clearLines_InputWidget_(iInputWidget *d) { | 388 | static 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 | ||
248 | static void updateLines_InputWidget_(iInputWidget *d) { | 397 | static 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 | ||
450 | static int contentHeight_InputWidget_(const iInputWidget *d) { | ||
451 | return size_Range(&d->visLines) * lineHeight_Text(d->font); | ||
452 | } | ||
453 | |||
454 | #if 0 | ||
300 | static int contentHeight_InputWidget_(const iInputWidget *d, iBool forLayout) { | 455 | static 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 | ||
308 | static void updateMetrics_InputWidget_(iInputWidget *d) { | 464 | static 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 | |||
479 | static 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 | ||
320 | static void updateLinesAndResize_InputWidget_(iInputWidget *d) { | 494 | static 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 | ||
368 | void deinit_InputWidget(iInputWidget *d) { | 552 | void 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 | ||
387 | void setFont_InputWidget(iInputWidget *d, int fontId) { | 570 | void 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 | ||
392 | static 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 | |||
397 | static void pushUndo_InputWidget_(iInputWidget *d) { | 575 | static 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) { | |||
407 | static iBool popUndo_InputWidget_(iInputWidget *d) { | 585 | static 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 | ||
424 | static void restoreDefaultScheme_(iString *url) { | 604 | static 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 | ||
439 | const iString *text_InputWidget(const iInputWidget *d) { | 620 | const 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 | ||
461 | void setMaxLayoutLines_InputWidget(iInputWidget *d, size_t maxLayoutLines) { | 644 | void 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 | ||
497 | static iBool isHintVisible_InputWidget_(const iInputWidget *d) { | 682 | static 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 | ||
502 | static void updateBuffered_InputWidget_(iInputWidget *d) { | 687 | static 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 | ||
710 | iLocalDef iInputLine *cursorLine_InputWidget_(iInputWidget *d) { | ||
711 | return at_Array(&d->lines, d->cursor.y); | ||
712 | } | ||
713 | |||
714 | iLocalDef const iInputLine *constCursorLine_InputWidget_(const iInputWidget *d) { | ||
715 | return constAt_Array(&d->lines, d->cursor.y); | ||
716 | } | ||
717 | |||
718 | iLocalDef const iInputLine *lastLine_InputWidget_(const iInputWidget *d) { | ||
719 | iAssert(!isEmpty_Array(&d->lines)); | ||
720 | return constBack_Array(&d->lines); | ||
721 | } | ||
722 | |||
723 | iLocalDef iInt2 cursorMax_InputWidget_(const iInputWidget *d) { | ||
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 | |||
540 | void setText_InputWidget(iInputWidget *d, const iString *text) { | 728 | void 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 | ||
792 | static 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 | |||
805 | static 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 | |||
596 | void selectAll_InputWidget(iInputWidget *d) { | 817 | void 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 | ||
666 | static void insertChar_InputWidget_(iInputWidget *d, iChar chr) { | 894 | static 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 | ||
689 | iLocalDef size_t cursorMax_InputWidget_(const iInputWidget *d) { | 922 | static 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 | ||
693 | iLocalDef iBool isMarking_(void) { | 928 | iLocalDef iBool isMarking_(void) { |
694 | return (modState_Keys() & KMOD_SHIFT) != 0; | 929 | return (modState_Keys() & KMOD_SHIFT) != 0; |
695 | } | 930 | } |
696 | 931 | ||
697 | void setCursor_InputWidget(iInputWidget *d, size_t pos) { | 932 | void 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 | ||
720 | iLocalDef 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 | |||
724 | static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const iInputLine *line) { | 952 | static 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 | ||
748 | static iBool moveCursorByLine_InputWidget_(iInputWidget *d, int dir) { | 981 | static 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 | ||
793 | static iRanges mark_InputWidget_(const iInputWidget *d) { | 1028 | static 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) { | |||
810 | static iBool deleteMarked_InputWidget_(iInputWidget *d) { | 1046 | static 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 | ||
821 | static iBool isWordChar_InputWidget_(const iInputWidget *d, size_t pos) { | 1059 | static 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 | ||
826 | iLocalDef iBool movePos_InputWidget_(const iInputWidget *d, size_t *pos, int dir) { | 1063 | static 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 | |||
1067 | static 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 | |||
1079 | static iBool isWordChar_InputWidget_(const iInputWidget *d, iInt2 pos) { | ||
1080 | return isAlphaNumeric_Char(at_InputWidget_(d, pos)); | ||
1081 | } | ||
1082 | |||
1083 | static 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 | |||
1116 | iLocalDef 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 | ||
836 | static size_t skipWord_InputWidget_(const iInputWidget *d, size_t pos, int dir) { | 1125 | static 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 | ||
880 | static iBool copy_InputWidget_(iInputWidget *d, iBool doCut) { | 1169 | static 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 | ||
918 | static iChar at_InputWidget_(const iInputWidget *d, size_t pos) { | 1209 | #if 0 |
919 | return *(const iChar *) constAt_Array(&d->text, pos); | ||
920 | } | ||
921 | |||
922 | static iRanges lineRange_InputWidget_(const iInputWidget *d) { | 1210 | static 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 | ||
930 | static void extendRange_InputWidget_(iInputWidget *d, size_t *pos, int dir) { | 1219 | static 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 | ||
947 | static iRect bounds_InputWidget_(const iInputWidget *d) { | 1238 | static 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 | ||
1256 | static 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 | |||
964 | static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | 1271 | static 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 | ||
1648 | iDeclareType(MarkPainter) | ||
1649 | |||
1650 | struct Impl_MarkPainter { | ||
1651 | iPaint *paint; | ||
1652 | const iInputWidget *d; | ||
1653 | iRect contentBounds; | ||
1654 | const iInputLine *line; | ||
1655 | iInt2 pos; | ||
1656 | iRanges mark; | ||
1657 | }; | ||
1658 | |||
1659 | static 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 | |||
1325 | static void draw_InputWidget_(const iInputWidget *d) { | 1691 | static 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 = ▮ | ||
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); | |||
47 | void setText_InputWidget (iInputWidget *, const iString *text); | 47 | void setText_InputWidget (iInputWidget *, const iString *text); |
48 | void setTextCStr_InputWidget (iInputWidget *, const char *cstr); | 48 | void setTextCStr_InputWidget (iInputWidget *, const char *cstr); |
49 | void setFont_InputWidget (iInputWidget *, int fontId); | 49 | void setFont_InputWidget (iInputWidget *, int fontId); |
50 | void setCursor_InputWidget (iInputWidget *, size_t pos); | 50 | //void setCursor_InputWidget (iInputWidget *, size_t pos); |
51 | void setContentPadding_InputWidget (iInputWidget *, int left, int right); /* only affects the text entry */ | 51 | void setContentPadding_InputWidget (iInputWidget *, int left, int right); /* only affects the text entry */ |
52 | void setMaxLayoutLines_InputWidget (iInputWidget *, size_t maxLayoutLines); | 52 | void setLineLimits_InputWidget (iInputWidget *, int minVis, int maxVis); |
53 | void setValidator_InputWidget (iInputWidget *, iInputWidgetValidatorFunc validator, void *context); | 53 | void setValidator_InputWidget (iInputWidget *, iInputWidgetValidatorFunc validator, void *context); |
54 | void setEnterInsertsLF_InputWidget (iInputWidget *, iBool enterInsertsLF); | 54 | void setEnterInsertsLF_InputWidget (iInputWidget *, iBool enterInsertsLF); |
55 | void setEnterKeyEnabled_InputWidget (iInputWidget *, iBool enterKeyEnabled); | 55 | void 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 | ||
858 | size_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 | |||
870 | static enum iFontId fontId_Text_(const iFont *font) { | 858 | static 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 | ||
1965 | void draw_WrapText(iWrapText *d, int fontId, iInt2 pos, int color) { | 1959 | iTextMetrics 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 | ||
1977 | SDL_Texture *glyphCache_Text(void) { | 1974 | SDL_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 | ||
219 | iTextMetrics measure_WrapText (iWrapText *, int fontId); | 219 | iTextMetrics measure_WrapText (iWrapText *, int fontId); |
220 | void draw_WrapText (iWrapText *, int fontId, iInt2 pos, int color); | 220 | iTextMetrics draw_WrapText (iWrapText *, int fontId, iInt2 pos, int color); |
221 | 221 | ||
222 | SDL_Texture * glyphCache_Text (void); | 222 | SDL_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)); |