summaryrefslogtreecommitdiff
path: root/src/ui/inputwidget.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/inputwidget.c')
-rw-r--r--src/ui/inputwidget.c587
1 files changed, 366 insertions, 221 deletions
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index ad45ed92..65710373 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -20,6 +20,10 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
20(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 20(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ 21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22 22
23/* InputWidget supports both fully custom and system-provided text editing.
24 The primary source of complexity is the handling of wrapped text content
25 in the custom text editor. */
26
23#include "inputwidget.h" 27#include "inputwidget.h"
24#include "command.h" 28#include "command.h"
25#include "paint.h" 29#include "paint.h"
@@ -42,13 +46,19 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
42 46
43#if defined (iPlatformAppleMobile) 47#if defined (iPlatformAppleMobile)
44# include "ios.h" 48# include "ios.h"
45# define LAGRANGE_ENABLE_SYSTEM_INPUT 1 49# define LAGRANGE_USE_SYSTEM_TEXT_INPUT 1 /* System-provided UI control almost handles everything. */
50#else
51# define LAGRANGE_USE_SYSTEM_TEXT_INPUT 0
52iDeclareType(SystemTextInput)
46#endif 53#endif
47 54
48static const int refreshInterval_InputWidget_ = 512; 55static const int refreshInterval_InputWidget_ = 512;
49static const size_t maxUndo_InputWidget_ = 64; 56static const size_t maxUndo_InputWidget_ = 64;
50static const int unlimitedWidth_InputWidget_ = 1000000; /* TODO: WrapText disables some functionality if maxWidth==0 */ 57static const int unlimitedWidth_InputWidget_ = 1000000; /* TODO: WrapText disables some functionality if maxWidth==0 */
51 58
59static const iChar sensitiveChar_ = 0x25cf; /* black circle */
60static const char * sensitive_ = "\u25cf";
61
52static void enableEditorKeysInMenus_(iBool enable) { 62static void enableEditorKeysInMenus_(iBool enable) {
53#if defined (iPlatformAppleDesktop) 63#if defined (iPlatformAppleDesktop)
54 enableMenuItemsByKey_MacOS(SDLK_LEFT, KMOD_PRIMARY, enable); 64 enableMenuItemsByKey_MacOS(SDLK_LEFT, KMOD_PRIMARY, enable);
@@ -62,7 +72,10 @@ static void enableEditorKeysInMenus_(iBool enable) {
62#endif 72#endif
63} 73}
64 74
75static void updateMetrics_InputWidget_(iInputWidget *);
76
65/*----------------------------------------------------------------------------------------------*/ 77/*----------------------------------------------------------------------------------------------*/
78#if !LAGRANGE_USE_SYSTEM_TEXT_INPUT
66 79
67iDeclareType(InputLine) 80iDeclareType(InputLine)
68 81
@@ -183,6 +196,8 @@ static void deinit_InputUndo_(iInputUndo *d) {
183 deinit_String(&d->text); 196 deinit_String(&d->text);
184} 197}
185 198
199#endif /* USE_SYSTEM_TEXT_INPUT */
200
186enum iInputWidgetFlag { 201enum iInputWidgetFlag {
187 isSensitive_InputWidgetFlag = iBit(1), 202 isSensitive_InputWidgetFlag = iBit(1),
188 isUrl_InputWidgetFlag = iBit(2), /* affected by decoding preference */ 203 isUrl_InputWidgetFlag = iBit(2), /* affected by decoding preference */
@@ -208,43 +223,45 @@ enum iInputWidgetFlag {
208struct Impl_InputWidget { 223struct Impl_InputWidget {
209 iWidget widget; 224 iWidget widget;
210 enum iInputMode mode; 225 enum iInputMode mode;
226 int font;
211 int inFlags; 227 int inFlags;
212 size_t maxLen; /* characters */ 228 size_t maxLen; /* characters */
213 iArray lines; /* iInputLine[] */
214 iString oldText; /* for restoring if edits cancelled */
215 int lastUpdateWidth;
216 iString srcHint; 229 iString srcHint;
217 iString hint; 230 iString hint;
218 int leftPadding; 231 int leftPadding;
219 int rightPadding; 232 int rightPadding;
233 int minWrapLines, maxWrapLines; /* min/max number of visible lines allowed */
234 iRangei visWrapLines; /* which wrap lines are current visible */
235 iClick click;
236 int wheelAccum;
237 iTextBuf * buffered; /* pre-rendered static text */
238 iInputWidgetValidatorFunc validator;
239 void * validatorContext;
240 iString * backupPath;
241 int backupTimer;
242 iString oldText; /* for restoring if edits cancelled */
243 int lastUpdateWidth;
244 iSystemTextInput *sysCtrl;
245#if LAGRANGE_USE_SYSTEM_TEXT_INPUT
246 iString text;
247#else
248 iArray lines; /* iInputLine[] */
220 iInt2 cursor; /* cursor position: x = byte offset, y = line index */ 249 iInt2 cursor; /* cursor position: x = byte offset, y = line index */
221 iInt2 prevCursor; /* previous cursor position */ 250 iInt2 prevCursor; /* previous cursor position */
222 iRangei visWrapLines; /* which wrap lines are current visible */
223 int minWrapLines, maxWrapLines; /* min/max number of visible lines allowed */
224 iRanges mark; /* TODO: would likely simplify things to use two Int2's for marking; no conversions needed */ 251 iRanges mark; /* TODO: would likely simplify things to use two Int2's for marking; no conversions needed */
225 iRanges initialMark; 252 iRanges initialMark;
226 iArray undoStack; 253 iArray undoStack;
227 int font;
228 iClick click;
229 uint32_t tapStartTime; 254 uint32_t tapStartTime;
230 uint32_t lastTapTime; 255 uint32_t lastTapTime;
231 iInt2 lastTapPos; 256 iInt2 lastTapPos;
232 int tapCount; 257 int tapCount;
233 int wheelAccum;
234 int cursorVis; 258 int cursorVis;
235 uint32_t timer; 259 uint32_t timer;
236 iTextBuf * buffered; /* pre-rendered static text */ 260#endif
237 iInputWidgetValidatorFunc validator;
238 void * validatorContext;
239 iString * backupPath;
240 int backupTimer;
241 iSystemTextInput *sysCtrl;
242}; 261};
243 262
244iDefineObjectConstructionArgs(InputWidget, (size_t maxLen), maxLen) 263iDefineObjectConstructionArgs(InputWidget, (size_t maxLen), maxLen)
245 264
246static void updateMetrics_InputWidget_(iInputWidget *);
247
248static void restoreBackup_InputWidget_(iInputWidget *d) { 265static void restoreBackup_InputWidget_(iInputWidget *d) {
249 if (!d->backupPath) return; 266 if (!d->backupPath) return;
250 iFile *f = new_File(d->backupPath); 267 iFile *f = new_File(d->backupPath);
@@ -258,17 +275,21 @@ static void saveBackup_InputWidget_(iInputWidget *d) {
258 if (!d->backupPath) return; 275 if (!d->backupPath) return;
259 iFile *f = new_File(d->backupPath); 276 iFile *f = new_File(d->backupPath);
260 if (open_File(f, writeOnly_FileMode | text_FileMode)) { 277 if (open_File(f, writeOnly_FileMode | text_FileMode)) {
278#if LAGRANGE_USE_SYSTEM_TEXT_INPUT
279 write_File(f, utf8_String(&d->text));
280#else
261 iConstForEach(Array, i, &d->lines) { 281 iConstForEach(Array, i, &d->lines) {
262 const iInputLine *line = i.value; 282 const iInputLine *line = i.value;
263 write_File(f, utf8_String(&line->text)); 283 write_File(f, utf8_String(&line->text));
264 } 284 }
265 d->inFlags &= ~needBackup_InputWidgetFlag; 285# if !defined (NDEBUG)
266#if !defined (NDEBUG)
267 iConstForEach(Array, j, &d->lines) { 286 iConstForEach(Array, j, &d->lines) {
268 iAssert(endsWith_String(&((const iInputLine *) j.value)->text, "\n") || 287 iAssert(endsWith_String(&((const iInputLine *) j.value)->text, "\n") ||
269 index_ArrayConstIterator(&j) == size_Array(&d->lines) - 1); 288 index_ArrayConstIterator(&j) == size_Array(&d->lines) - 1);
270 } 289 }
290# endif
271#endif 291#endif
292 d->inFlags &= ~needBackup_InputWidgetFlag;
272 } 293 }
273 iRelease(f); 294 iRelease(f);
274} 295}
@@ -317,6 +338,12 @@ void setBackupFileName_InputWidget(iInputWidget *d, const char *fileName) {
317 restoreBackup_InputWidget_(d); 338 restoreBackup_InputWidget_(d);
318} 339}
319 340
341iLocalDef iInt2 padding_(void) {
342 return init_I2(gap_UI / 2, gap_UI / 2);
343}
344
345#if !LAGRANGE_USE_SYSTEM_TEXT_INPUT
346
320static void clearUndo_InputWidget_(iInputWidget *d) { 347static void clearUndo_InputWidget_(iInputWidget *d) {
321 iForEach(Array, i, &d->undoStack) { 348 iForEach(Array, i, &d->undoStack) {
322 deinit_InputUndo_(i.value); 349 deinit_InputUndo_(i.value);
@@ -324,10 +351,13 @@ static void clearUndo_InputWidget_(iInputWidget *d) {
324 clear_Array(&d->undoStack); 351 clear_Array(&d->undoStack);
325} 352}
326 353
327iLocalDef iInt2 padding_(void) { 354static const iInputLine *line_InputWidget_(const iInputWidget *d, size_t index) {
328 return init_I2(gap_UI / 2, gap_UI / 2); 355 iAssert(!isEmpty_Array(&d->lines));
356 return constAt_Array(&d->lines, index);
329} 357}
330 358
359#endif /* !LAGRANGE_USE_SYSTEM_TEXT_INPUT */
360
331#define extraPaddingHeight_ (1.25f * gap_UI) 361#define extraPaddingHeight_ (1.25f * gap_UI)
332 362
333static iRect contentBounds_InputWidget_(const iInputWidget *d) { 363static iRect contentBounds_InputWidget_(const iInputWidget *d) {
@@ -343,6 +373,25 @@ static iRect contentBounds_InputWidget_(const iInputWidget *d) {
343 return bounds; 373 return bounds;
344} 374}
345 375
376static iWrapText wrap_InputWidget_(const iInputWidget *d, int y) {
377#if LAGRANGE_USE_SYSTEM_TEXT_INPUT
378 iUnused(y); /* full text is wrapped always */
379 iRangecc text = range_String(&d->text);
380#else
381 iRangecc text = range_String(&line_InputWidget_(d, y)->text);
382#endif
383 return (iWrapText){
384 .text = text,
385 .maxWidth = d->maxLen == 0 ? width_Rect(contentBounds_InputWidget_(d))
386 : unlimitedWidth_InputWidget_,
387 .mode =
388 (d->inFlags & isUrl_InputWidgetFlag ? anyCharacter_WrapTextMode : word_WrapTextMode),
389 .overrideChar = (d->inFlags & isSensitive_InputWidgetFlag ? sensitiveChar_ : 0),
390 };
391}
392
393#if !LAGRANGE_USE_SYSTEM_TEXT_INPUT
394
346iLocalDef iBool isLastLine_InputWidget_(const iInputWidget *d, const iInputLine *line) { 395iLocalDef iBool isLastLine_InputWidget_(const iInputWidget *d, const iInputLine *line) {
347 return (const void *) line == constBack_Array(&d->lines); 396 return (const void *) line == constBack_Array(&d->lines);
348} 397}
@@ -356,11 +405,6 @@ static int numWrapLines_InputWidget_(const iInputWidget *d) {
356 return lastLine_InputWidget_(d)->wrapLines.end; 405 return lastLine_InputWidget_(d)->wrapLines.end;
357} 406}
358 407
359static const iInputLine *line_InputWidget_(const iInputWidget *d, size_t index) {
360 iAssert(!isEmpty_Array(&d->lines));
361 return constAt_Array(&d->lines, index);
362}
363
364static const iString *lineString_InputWidget_(const iInputWidget *d, int y) { 408static const iString *lineString_InputWidget_(const iInputWidget *d, int y) {
365 return &line_InputWidget_(d, y)->text; 409 return &line_InputWidget_(d, y)->text;
366} 410}
@@ -461,20 +505,6 @@ static int visLineOffsetY_InputWidget_(const iInputWidget *d) {
461 d->wheelAccum; 505 d->wheelAccum;
462} 506}
463 507
464static const iChar sensitiveChar_ = 0x25cf; /* black circle */
465static const char *sensitive_ = "\u25cf";
466
467static iWrapText wrap_InputWidget_(const iInputWidget *d, int y) {
468 return (iWrapText){
469 .text = range_String(&line_InputWidget_(d, y)->text),
470 .maxWidth = d->maxLen == 0 ? width_Rect(contentBounds_InputWidget_(d))
471 : unlimitedWidth_InputWidget_,
472 .mode =
473 (d->inFlags & isUrl_InputWidgetFlag ? anyCharacter_WrapTextMode : word_WrapTextMode),
474 .overrideChar = (d->inFlags & isSensitive_InputWidgetFlag ? sensitiveChar_ : 0),
475 };
476}
477
478static iRangei visibleLineRange_InputWidget_(const iInputWidget *d) { 508static iRangei visibleLineRange_InputWidget_(const iInputWidget *d) {
479 iRangei vis = { -1, -1 }; 509 iRangei vis = { -1, -1 };
480 /* Determine which lines are in the potentially visible range. */ 510 /* Determine which lines are in the potentially visible range. */
@@ -557,6 +587,19 @@ static void showCursor_InputWidget_(iInputWidget *d) {
557 updateVisible_InputWidget_(d); 587 updateVisible_InputWidget_(d);
558} 588}
559 589
590#else /* if LAGRANGE_USE_SYSTEM_TEXT_INPUT */
591
592static int visLineOffsetY_InputWidget_(const iInputWidget *d) {
593 return 0; /* offset for the buffered text */
594}
595
596static void updateVisible_InputWidget_(iInputWidget *d) {
597 iUnused(d);
598 /* TODO: Anything to do? */
599}
600
601#endif
602
560static void invalidateBuffered_InputWidget_(iInputWidget *d) { 603static void invalidateBuffered_InputWidget_(iInputWidget *d) {
561 if (d->buffered) { 604 if (d->buffered) {
562 delete_TextBuf(d->buffered); 605 delete_TextBuf(d->buffered);
@@ -580,11 +623,16 @@ static void updateSizeForFixedLength_InputWidget_(iInputWidget *d) {
580} 623}
581 624
582static iString *text_InputWidget_(const iInputWidget *d) { 625static iString *text_InputWidget_(const iInputWidget *d) {
626#if LAGRANGE_USE_SYSTEM_TEXT_INPUT
627 return copy_String(&d->text);
628#else
583 iString *text = new_String(); 629 iString *text = new_String();
584 mergeLines_(&d->lines, text); 630 mergeLines_(&d->lines, text);
585 return text; 631 return text;
632#endif
586} 633}
587 634
635#if !LAGRANGE_USE_SYSTEM_TEXT_INPUT
588static size_t length_InputWidget_(const iInputWidget *d) { 636static size_t length_InputWidget_(const iInputWidget *d) {
589 /* Note: `d->length` is kept up to date, so don't call this normally. */ 637 /* Note: `d->length` is kept up to date, so don't call this normally. */
590 size_t len = 0; 638 size_t len = 0;
@@ -595,42 +643,6 @@ static size_t length_InputWidget_(const iInputWidget *d) {
595 return len; 643 return len;
596} 644}
597 645
598static int contentHeight_InputWidget_(const iInputWidget *d) {
599 if (d->sysCtrl) {
600 const int preferred = preferredHeight_SystemTextInput(d->sysCtrl);
601 return iClamp(preferred,
602 d->minWrapLines * lineHeight_Text(d->font),
603 d->maxWrapLines * lineHeight_Text(d->font));
604 }
605 return size_Range(&d->visWrapLines) * lineHeight_Text(d->font);
606}
607
608static void updateTextInputRect_InputWidget_(const iInputWidget *d) {
609 if (d->sysCtrl) {
610 setRect_SystemTextInput(d->sysCtrl, contentBounds_InputWidget_(d));
611 }
612#if !defined (iPlatformAppleMobile)
613 const iRect bounds = bounds_Widget(constAs_Widget(d));
614 SDL_SetTextInputRect(&(SDL_Rect){ bounds.pos.x, bounds.pos.y, bounds.size.x, bounds.size.y });
615#endif
616}
617
618static void updateMetrics_InputWidget_(iInputWidget *d) {
619 iWidget *w = as_Widget(d);
620 updateSizeForFixedLength_InputWidget_(d);
621 /* Caller must arrange the width, but the height is set here. */
622 const int oldHeight = height_Rect(w->rect);
623 w->rect.size.y = contentHeight_InputWidget_(d) + 3.0f * padding_().y; /* TODO: Why 3x? */
624 if (flags_Widget(w) & extraPadding_WidgetFlag) {
625 w->rect.size.y += extraPaddingHeight_;
626 }
627 invalidateBuffered_InputWidget_(d);
628 if (height_Rect(w->rect) != oldHeight) {
629 postCommand_Widget(d, "input.resized");
630 updateTextInputRect_InputWidget_(d);
631 }
632}
633
634static void updateLine_InputWidget_(iInputWidget *d, iInputLine *line) { 646static void updateLine_InputWidget_(iInputWidget *d, iInputLine *line) {
635 iAssert(endsWith_String(&line->text, "\n") || isLastLine_InputWidget_(d, line)); 647 iAssert(endsWith_String(&line->text, "\n") || isLastLine_InputWidget_(d, line));
636 iWrapText wrapText = wrap_InputWidget_(d, indexOf_Array(&d->lines, line)); 648 iWrapText wrapText = wrap_InputWidget_(d, indexOf_Array(&d->lines, line));
@@ -693,6 +705,63 @@ static void startOrStopCursorTimer_InputWidget_(iInputWidget *d, iBool doStart)
693 } 705 }
694} 706}
695 707
708#else /* using a system-provided text control */
709
710static void updateAllLinesAndResizeHeight_InputWidget_(iInputWidget *d) {
711 /* Rewrap the buffered text and resize accordingly. */
712 iWrapText wt = wrap_InputWidget_(d, 0);
713 const int height = measure_WrapText(&wt, d->font).bounds.size.y;
714 /* We use this to store the number wrapped lines for determining widget height. */
715 d->visWrapLines.start = 0;
716 d->visWrapLines.end = height / lineHeight_Text(d->font);
717 updateMetrics_InputWidget_(d);
718}
719
720#endif
721
722static int contentHeight_InputWidget_(const iInputWidget *d) {
723#if LAGRANGE_USE_SYSTEM_TEXT_INPUT
724 const int minHeight = d->minWrapLines * lineHeight_Text(d->font);
725 const int maxHeight = d->maxWrapLines * lineHeight_Text(d->font);
726 if (d->sysCtrl) {
727 const int preferred = preferredHeight_SystemTextInput(d->sysCtrl);
728 return iClamp(preferred, minHeight, maxHeight);
729 }
730 if (d->buffered && ~d->inFlags & needUpdateBuffer_InputWidgetFlag) {
731 return iClamp(d->buffered->size.y, minHeight, maxHeight);
732 }
733#endif
734 return size_Range(&d->visWrapLines) * lineHeight_Text(d->font);
735}
736
737static void updateTextInputRect_InputWidget_(const iInputWidget *d) {
738#if LAGRANGE_USE_SYSTEM_TEXT_INPUT
739 if (d->sysCtrl) {
740 setRect_SystemTextInput(d->sysCtrl, contentBounds_InputWidget_(d));
741 }
742#endif
743#if !defined (iPlatformAppleMobile)
744 const iRect bounds = bounds_Widget(constAs_Widget(d));
745 SDL_SetTextInputRect(&(SDL_Rect){ bounds.pos.x, bounds.pos.y, bounds.size.x, bounds.size.y });
746#endif
747}
748
749static void updateMetrics_InputWidget_(iInputWidget *d) {
750 iWidget *w = as_Widget(d);
751 updateSizeForFixedLength_InputWidget_(d);
752 /* Caller must arrange the width, but the height is set here. */
753 const int oldHeight = height_Rect(w->rect);
754 w->rect.size.y = contentHeight_InputWidget_(d) + 3.0f * padding_().y; /* TODO: Why 3x? */
755 if (flags_Widget(w) & extraPadding_WidgetFlag) {
756 w->rect.size.y += extraPaddingHeight_;
757 }
758 invalidateBuffered_InputWidget_(d);
759 if (height_Rect(w->rect) != oldHeight) {
760 postCommand_Widget(d, "input.resized");
761 updateTextInputRect_InputWidget_(d);
762 }
763}
764
696void init_InputWidget(iInputWidget *d, size_t maxLen) { 765void init_InputWidget(iInputWidget *d, size_t maxLen) {
697 iWidget *w = &d->widget; 766 iWidget *w = &d->widget;
698 init_Widget(w); 767 init_Widget(w);
@@ -702,37 +771,40 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) {
702#if defined (iPlatformMobile) 771#if defined (iPlatformMobile)
703 setFlags_Widget(w, extraPadding_WidgetFlag, iTrue); 772 setFlags_Widget(w, extraPadding_WidgetFlag, iTrue);
704#endif 773#endif
774#if LAGRANGE_USE_SYSTEM_TEXT_INPUT
775 init_String(&d->text);
776#else
705 init_Array(&d->lines, sizeof(iInputLine)); 777 init_Array(&d->lines, sizeof(iInputLine));
778 init_Array(&d->undoStack, sizeof(iInputUndo));
779 d->cursor = zero_I2();
780 d->prevCursor = zero_I2();
781 d->lastTapTime = 0;
782 d->tapCount = 0;
783 d->timer = 0;
784 d->cursorVis = 0;
785 iZap(d->mark);
786 splitToLines_(&iStringLiteral(""), &d->lines);
787#endif
706 init_String(&d->oldText); 788 init_String(&d->oldText);
707 init_Array(&d->lines, sizeof(iInputLine));
708 init_String(&d->srcHint); 789 init_String(&d->srcHint);
709 init_String(&d->hint); 790 init_String(&d->hint);
710 init_Array(&d->undoStack, sizeof(iInputUndo));
711 d->font = uiInput_FontId | alwaysVariableFlag_FontId; 791 d->font = uiInput_FontId | alwaysVariableFlag_FontId;
712 d->leftPadding = 0; 792 d->leftPadding = 0;
713 d->rightPadding = 0; 793 d->rightPadding = 0;
714 d->cursor = zero_I2();
715 d->prevCursor = zero_I2();
716 d->lastUpdateWidth = 0; 794 d->lastUpdateWidth = 0;
717 d->inFlags = eatEscape_InputWidgetFlag | enterKeyEnabled_InputWidgetFlag | 795 d->inFlags = eatEscape_InputWidgetFlag | enterKeyEnabled_InputWidgetFlag |
718 lineBreaksEnabled_InputWidgetFlag | useReturnKeyBehavior_InputWidgetFlag; 796 lineBreaksEnabled_InputWidgetFlag | useReturnKeyBehavior_InputWidgetFlag;
719 // if (deviceType_App() != desktop_AppDeviceType) { 797 // if (deviceType_App() != desktop_AppDeviceType) {
720 // d->inFlags |= enterKeyInsertsLineFeed_InputWidgetFlag; 798 // d->inFlags |= enterKeyInsertsLineFeed_InputWidgetFlag;
721 // } 799 // }
722 iZap(d->mark);
723 setMaxLen_InputWidget(d, maxLen); 800 setMaxLen_InputWidget(d, maxLen);
724 d->visWrapLines.start = 0; 801 d->visWrapLines.start = 0;
725 d->visWrapLines.end = 1; 802 d->visWrapLines.end = 1;
726 d->maxWrapLines = maxLen > 0 ? 1 : 20; /* TODO: Choose maximum dynamically? */ 803 d->maxWrapLines = maxLen > 0 ? 1 : 20; /* TODO: Choose maximum dynamically? */
727 d->minWrapLines = 1; 804 d->minWrapLines = 1;
728 splitToLines_(&iStringLiteral(""), &d->lines);
729 setFlags_Widget(w, fixedHeight_WidgetFlag, iTrue); /* resizes its own height */ 805 setFlags_Widget(w, fixedHeight_WidgetFlag, iTrue); /* resizes its own height */
730 init_Click(&d->click, d, SDL_BUTTON_LEFT); 806 init_Click(&d->click, d, SDL_BUTTON_LEFT);
731 d->lastTapTime = 0;
732 d->tapCount = 0;
733 d->wheelAccum = 0; 807 d->wheelAccum = 0;
734 d->timer = 0;
735 d->cursorVis = 0;
736 d->buffered = NULL; 808 d->buffered = NULL;
737 d->backupPath = NULL; 809 d->backupPath = NULL;
738 d->backupTimer = 0; 810 d->backupTimer = 0;
@@ -741,7 +813,6 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) {
741} 813}
742 814
743void deinit_InputWidget(iInputWidget *d) { 815void deinit_InputWidget(iInputWidget *d) {
744 delete_SystemTextInput(d->sysCtrl);
745 if (d->backupTimer) { 816 if (d->backupTimer) {
746 SDL_RemoveTimer(d->backupTimer); 817 SDL_RemoveTimer(d->backupTimer);
747 } 818 }
@@ -750,19 +821,24 @@ void deinit_InputWidget(iInputWidget *d) {
750 } 821 }
751 delete_String(d->backupPath); 822 delete_String(d->backupPath);
752 d->backupPath = NULL; 823 d->backupPath = NULL;
824 delete_TextBuf(d->buffered);
825 deinit_String(&d->srcHint);
826 deinit_String(&d->hint);
827 deinit_String(&d->oldText);
828#if LAGRANGE_USE_SYSTEM_TEXT_INPUT
829 delete_SystemTextInput(d->sysCtrl);
830 deinit_String(&d->text);
831#else
832 startOrStopCursorTimer_InputWidget_(d, iFalse);
753 clearInputLines_(&d->lines); 833 clearInputLines_(&d->lines);
754 if (isSelected_Widget(d)) { 834 if (isSelected_Widget(d)) {
755 SDL_StopTextInput(); 835 SDL_StopTextInput();
756 enableEditorKeysInMenus_(iTrue); 836 enableEditorKeysInMenus_(iTrue);
757 } 837 }
758 delete_TextBuf(d->buffered);
759 clearUndo_InputWidget_(d); 838 clearUndo_InputWidget_(d);
760 deinit_Array(&d->undoStack); 839 deinit_Array(&d->undoStack);
761 startOrStopCursorTimer_InputWidget_(d, iFalse);
762 deinit_String(&d->srcHint);
763 deinit_String(&d->hint);
764 deinit_String(&d->oldText);
765 deinit_Array(&d->lines); 840 deinit_Array(&d->lines);
841#endif
766} 842}
767 843
768static iBool isAllowedToInsertNewline_InputWidget_(const iInputWidget *d) { 844static iBool isAllowedToInsertNewline_InputWidget_(const iInputWidget *d) {
@@ -771,7 +847,7 @@ static iBool isAllowedToInsertNewline_InputWidget_(const iInputWidget *d) {
771 d->inFlags & lineBreaksEnabled_InputWidgetFlag && d->maxLen == 0; 847 d->inFlags & lineBreaksEnabled_InputWidgetFlag && d->maxLen == 0;
772} 848}
773 849
774#if defined (LAGRANGE_ENABLE_SYSTEM_INPUT) 850#if LAGRANGE_USE_SYSTEM_TEXT_INPUT
775static void updateAfterVisualOffsetChange_InputWidget_(iInputWidget *d, iRoot *root) { 851static void updateAfterVisualOffsetChange_InputWidget_(iInputWidget *d, iRoot *root) {
776 iAssert(as_Widget(d)->root == root); 852 iAssert(as_Widget(d)->root == root);
777 iUnused(root); 853 iUnused(root);
@@ -786,6 +862,7 @@ void setFont_InputWidget(iInputWidget *d, int fontId) {
786 updateMetrics_InputWidget_(d); 862 updateMetrics_InputWidget_(d);
787} 863}
788 864
865#if !LAGRANGE_USE_SYSTEM_TEXT_INPUT
789static void pushUndo_InputWidget_(iInputWidget *d) { 866static void pushUndo_InputWidget_(iInputWidget *d) {
790 iInputUndo undo; 867 iInputUndo undo;
791 init_InputUndo_(&undo, &d->lines, d->cursor); 868 init_InputUndo_(&undo, &d->lines, d->cursor);
@@ -799,7 +876,6 @@ static void pushUndo_InputWidget_(iInputWidget *d) {
799static iBool popUndo_InputWidget_(iInputWidget *d) { 876static iBool popUndo_InputWidget_(iInputWidget *d) {
800 if (!isEmpty_Array(&d->undoStack)) { 877 if (!isEmpty_Array(&d->undoStack)) {
801 iInputUndo *undo = back_Array(&d->undoStack); 878 iInputUndo *undo = back_Array(&d->undoStack);
802 //setCopy_Array(&d->text, &undo->text);
803 splitToLines_(&undo->text, &d->lines); 879 splitToLines_(&undo->text, &d->lines);
804 d->cursor = undo->cursor; 880 d->cursor = undo->cursor;
805 deinit_InputUndo_(undo); 881 deinit_InputUndo_(undo);
@@ -811,6 +887,43 @@ static iBool popUndo_InputWidget_(iInputWidget *d) {
811 return iFalse; 887 return iFalse;
812} 888}
813 889
890iLocalDef iInputLine *cursorLine_InputWidget_(iInputWidget *d) {
891 return at_Array(&d->lines, d->cursor.y);
892}
893
894iLocalDef const iInputLine *constCursorLine_InputWidget_(const iInputWidget *d) {
895 return constAt_Array(&d->lines, d->cursor.y);
896}
897
898iLocalDef iInt2 cursorMax_InputWidget_(const iInputWidget *d) {
899 const int yLast = size_Array(&d->lines) - 1;
900 return init_I2(endX_InputWidget_(d, yLast), yLast);
901}
902
903static size_t cursorToIndex_InputWidget_(const iInputWidget *d, iInt2 pos) {
904 if (pos.y < 0) {
905 return 0;
906 }
907 if (pos.y >= size_Array(&d->lines)) {
908 return lastLine_InputWidget_(d)->range.end;
909 }
910 const iInputLine *line = line_InputWidget_(d, pos.y);
911 pos.x = iClamp(pos.x, 0, endX_InputWidget_(d, pos.y));
912 return line->range.start + pos.x;
913}
914
915static iInt2 indexToCursor_InputWidget_(const iInputWidget *d, size_t index) {
916 /* TODO: The lines are sorted; this could use a binary search. */
917 iConstForEach(Array, i, &d->lines) {
918 const iInputLine *line = i.value;
919 if (contains_Range(&line->range, index)) {
920 return init_I2(index - line->range.start, index_ArrayConstIterator(&i));
921 }
922 }
923 return cursorMax_InputWidget_(d);
924}
925#endif
926
814void setMode_InputWidget(iInputWidget *d, enum iInputMode mode) { 927void setMode_InputWidget(iInputWidget *d, enum iInputMode mode) {
815 d->mode = mode; 928 d->mode = mode;
816} 929}
@@ -909,7 +1022,11 @@ void setContentPadding_InputWidget(iInputWidget *d, int left, int right) {
909} 1022}
910 1023
911iLocalDef iBool isEmpty_InputWidget_(const iInputWidget *d) { 1024iLocalDef iBool isEmpty_InputWidget_(const iInputWidget *d) {
1025#if LAGRANGE_USE_SYSTEM_TEXT_INPUT
1026 return isEmpty_String(&d->text);
1027#else
912 return size_Array(&d->lines) == 1 && isEmpty_String(&line_InputWidget_(d, 0)->text); 1028 return size_Array(&d->lines) == 1 && isEmpty_String(&line_InputWidget_(d, 0)->text);
1029#endif
913} 1030}
914 1031
915static iBool isHintVisible_InputWidget_(const iInputWidget *d) { 1032static iBool isHintVisible_InputWidget_(const iInputWidget *d) {
@@ -919,15 +1036,20 @@ static iBool isHintVisible_InputWidget_(const iInputWidget *d) {
919static void updateBuffered_InputWidget_(iInputWidget *d) { 1036static void updateBuffered_InputWidget_(iInputWidget *d) {
920 invalidateBuffered_InputWidget_(d); 1037 invalidateBuffered_InputWidget_(d);
921 if (isHintVisible_InputWidget_(d)) { 1038 if (isHintVisible_InputWidget_(d)) {
1039 /* TODO: This should have a "maximum number of lines" parameter! */
922 d->buffered = newRange_TextBuf(d->font, uiAnnotation_ColorId, range_String(&d->hint)); 1040 d->buffered = newRange_TextBuf(d->font, uiAnnotation_ColorId, range_String(&d->hint));
923 } 1041 }
924 else { 1042 else {
925 /* Draw all the potentially visible lines to a buffer. */ 1043 /* Draw all the potentially visible lines to a buffer. */
1044#if LAGRANGE_USE_SYSTEM_TEXT_INPUT
1045 iString *visText = copy_String(&d->text);
1046#else
926 iString *visText = new_String(); 1047 iString *visText = new_String();
927 const iRangei visRange = visibleLineRange_InputWidget_(d); 1048 const iRangei visRange = visibleLineRange_InputWidget_(d);
928 for (int i = visRange.start; i < visRange.end; i++) { 1049 for (int i = visRange.start; i < visRange.end; i++) {
929 append_String(visText, &line_InputWidget_(d, i)->text); 1050 append_String(visText, &line_InputWidget_(d, i)->text);
930 } 1051 }
1052#endif
931 if (d->inFlags & isUrl_InputWidgetFlag) { 1053 if (d->inFlags & isUrl_InputWidgetFlag) {
932 /* Highlight the host name. */ 1054 /* Highlight the host name. */
933 iUrl parts; 1055 iUrl parts;
@@ -953,19 +1075,6 @@ static void updateBuffered_InputWidget_(iInputWidget *d) {
953 d->inFlags &= ~needUpdateBuffer_InputWidgetFlag; 1075 d->inFlags &= ~needUpdateBuffer_InputWidgetFlag;
954} 1076}
955 1077
956iLocalDef iInputLine *cursorLine_InputWidget_(iInputWidget *d) {
957 return at_Array(&d->lines, d->cursor.y);
958}
959
960iLocalDef const iInputLine *constCursorLine_InputWidget_(const iInputWidget *d) {
961 return constAt_Array(&d->lines, d->cursor.y);
962}
963
964iLocalDef iInt2 cursorMax_InputWidget_(const iInputWidget *d) {
965 const int yLast = size_Array(&d->lines) - 1;
966 return init_I2(endX_InputWidget_(d, yLast), yLast);
967}
968
969void setText_InputWidget(iInputWidget *d, const iString *text) { 1078void setText_InputWidget(iInputWidget *d, const iString *text) {
970 if (!d) return; 1079 if (!d) return;
971 if (d->inFlags & isUrl_InputWidgetFlag) { 1080 if (d->inFlags & isUrl_InputWidgetFlag) {
@@ -982,13 +1091,11 @@ void setText_InputWidget(iInputWidget *d, const iString *text) {
982 text = omitDefaultScheme_(collect_String(copy_String(text))); 1091 text = omitDefaultScheme_(collect_String(copy_String(text)));
983 } 1092 }
984 } 1093 }
985 clearUndo_InputWidget_(d);
986 iString *nfcText = collect_String(copy_String(text)); 1094 iString *nfcText = collect_String(copy_String(text));
987 normalize_String(nfcText); 1095 normalize_String(nfcText);
1096#if !LAGRANGE_USE_SYSTEM_TEXT_INPUT
1097 clearUndo_InputWidget_(d);
988 splitToLines_(nfcText, &d->lines); 1098 splitToLines_(nfcText, &d->lines);
989 if (d->sysCtrl) {
990 setText_SystemTextInput(d->sysCtrl, nfcText);
991 }
992 iAssert(!isEmpty_Array(&d->lines)); 1099 iAssert(!isEmpty_Array(&d->lines));
993 iForEach(Array, i, &d->lines) { 1100 iForEach(Array, i, &d->lines) {
994 updateLine_InputWidget_(d, i.value); /* count number of visible lines */ 1101 updateLine_InputWidget_(d, i.value); /* count number of visible lines */
@@ -998,12 +1105,23 @@ void setText_InputWidget(iInputWidget *d, const iString *text) {
998 if (!isFocused_Widget(d)) { 1105 if (!isFocused_Widget(d)) {
999 iZap(d->mark); 1106 iZap(d->mark);
1000 } 1107 }
1108#else
1109 set_String(&d->text, nfcText);
1110 if (d->sysCtrl) {
1111 setText_SystemTextInput(d->sysCtrl, nfcText);
1112 }
1113 else {
1114 updateAllLinesAndResizeHeight_InputWidget_(d); /* need to know the new height */
1115 }
1116#endif
1001 if (!isFocused_Widget(d)) { 1117 if (!isFocused_Widget(d)) {
1002 d->inFlags |= needUpdateBuffer_InputWidgetFlag; 1118 d->inFlags |= needUpdateBuffer_InputWidgetFlag;
1003 } 1119 }
1004 updateVisible_InputWidget_(d); 1120 updateVisible_InputWidget_(d);
1005 updateMetrics_InputWidget_(d); 1121 updateMetrics_InputWidget_(d);
1006 refresh_Widget(as_Widget(d)); 1122 if (!d->sysCtrl) {
1123 refresh_Widget(as_Widget(d));
1124 }
1007} 1125}
1008 1126
1009void setTextCStr_InputWidget(iInputWidget *d, const char *cstr) { 1127void setTextCStr_InputWidget(iInputWidget *d, const char *cstr) {
@@ -1012,32 +1130,11 @@ void setTextCStr_InputWidget(iInputWidget *d, const char *cstr) {
1012 delete_String(str); 1130 delete_String(str);
1013} 1131}
1014 1132
1015static size_t cursorToIndex_InputWidget_(const iInputWidget *d, iInt2 pos) {
1016 if (pos.y < 0) {
1017 return 0;
1018 }
1019 if (pos.y >= size_Array(&d->lines)) {
1020 return lastLine_InputWidget_(d)->range.end;
1021 }
1022 const iInputLine *line = line_InputWidget_(d, pos.y);
1023 pos.x = iClamp(pos.x, 0, endX_InputWidget_(d, pos.y));
1024 return line->range.start + pos.x;
1025}
1026
1027static iInt2 indexToCursor_InputWidget_(const iInputWidget *d, size_t index) {
1028 /* TODO: The lines are sorted; this could use a binary search. */
1029 iConstForEach(Array, i, &d->lines) {
1030 const iInputLine *line = i.value;
1031 if (contains_Range(&line->range, index)) {
1032 return init_I2(index - line->range.start, index_ArrayConstIterator(&i));
1033 }
1034 }
1035 return cursorMax_InputWidget_(d);
1036}
1037
1038void selectAll_InputWidget(iInputWidget *d) { 1133void selectAll_InputWidget(iInputWidget *d) {
1134#if !LAGRANGE_USE_SYSTEM_TEXT_INPUT
1039 d->mark = (iRanges){ 0, lastLine_InputWidget_(d)->range.end }; 1135 d->mark = (iRanges){ 0, lastLine_InputWidget_(d)->range.end };
1040 refresh_Widget(as_Widget(d)); 1136 refresh_Widget(as_Widget(d));
1137#endif
1041} 1138}
1042 1139
1043iLocalDef iBool isEditing_InputWidget_(const iInputWidget *d) { 1140iLocalDef iBool isEditing_InputWidget_(const iInputWidget *d) {
@@ -1046,10 +1143,10 @@ iLocalDef iBool isEditing_InputWidget_(const iInputWidget *d) {
1046 1143
1047static void contentsWereChanged_InputWidget_(iInputWidget *); 1144static void contentsWereChanged_InputWidget_(iInputWidget *);
1048 1145
1049#if defined (LAGRANGE_ENABLE_SYSTEM_INPUT) 1146#if LAGRANGE_USE_SYSTEM_TEXT_INPUT
1050void systemInputChanged_InputWidget_(iSystemTextInput *sysCtrl, void *widget) { 1147void systemInputChanged_InputWidget_(iSystemTextInput *sysCtrl, void *widget) {
1051 iInputWidget *d = widget; 1148 iInputWidget *d = widget;
1052 splitToLines_(text_SystemTextInput(sysCtrl), &d->lines); 1149 set_String(&d->text, text_SystemTextInput(sysCtrl));
1053 contentsWereChanged_InputWidget_(d); 1150 contentsWereChanged_InputWidget_(d);
1054 updateMetrics_InputWidget_(d); 1151 updateMetrics_InputWidget_(d);
1055} 1152}
@@ -1064,22 +1161,24 @@ void begin_InputWidget(iInputWidget *d) {
1064 invalidateBuffered_InputWidget_(d); 1161 invalidateBuffered_InputWidget_(d);
1065 setFlags_Widget(w, hidden_WidgetFlag | disabled_WidgetFlag, iFalse); 1162 setFlags_Widget(w, hidden_WidgetFlag | disabled_WidgetFlag, iFalse);
1066 setFlags_Widget(w, selected_WidgetFlag, iTrue); 1163 setFlags_Widget(w, selected_WidgetFlag, iTrue);
1067 mergeLines_(&d->lines, &d->oldText); 1164 d->inFlags &= ~enterPressed_InputWidgetFlag;
1068#if defined (LAGRANGE_ENABLE_SYSTEM_INPUT) 1165#if LAGRANGE_USE_SYSTEM_TEXT_INPUT
1166 set_String(&d->oldText, &d->text);
1069 d->sysCtrl = new_SystemTextInput(contentBounds_InputWidget_(d), 1167 d->sysCtrl = new_SystemTextInput(contentBounds_InputWidget_(d),
1070 (d->maxWrapLines > 1 ? multiLine_SystemTextInputFlags : 0) | 1168 (d->maxWrapLines > 1 ? multiLine_SystemTextInputFlags : 0) |
1071 (d->inFlags & isUrl_InputWidgetFlag ? (disableAutocorrect_SystemTextInputFlag | 1169 (d->inFlags & isUrl_InputWidgetFlag ? (disableAutocorrect_SystemTextInputFlag |
1072 disableAutocapitalize_SystemTextInputFlag) : 0) | 1170 disableAutocapitalize_SystemTextInputFlag) : 0) |
1073 (!cmp_String(id_Widget(w), "url") ? returnGo_SystemTextInputFlags : 0) | 1171 (!cmp_String(id_Widget(w), "url") ? returnGo_SystemTextInputFlags : 0) |
1074 (flags_Widget(w) & alignRight_WidgetFlag ? alignRight_SystemTextInputFlag : 0) | 1172 (flags_Widget(w) & alignRight_WidgetFlag ? alignRight_SystemTextInputFlag : 0) |
1075 (isAllowedToInsertNewline_InputWidget_(d) ? insertNewlines_SystemTextInputFlag : 0)); 1173 (isAllowedToInsertNewline_InputWidget_(d) ? insertNewlines_SystemTextInputFlag : 0) |
1174 (d->inFlags & selectAllOnFocus_InputWidgetFlag ? selectAll_SystemTextInputFlags : 0));
1076 setFont_SystemTextInput(d->sysCtrl, d->font); 1175 setFont_SystemTextInput(d->sysCtrl, d->font);
1077 setText_SystemTextInput(d->sysCtrl, &d->oldText); 1176 setText_SystemTextInput(d->sysCtrl, &d->oldText);
1078 setTextChangedFunc_SystemTextInput(d->sysCtrl, systemInputChanged_InputWidget_, d); 1177 setTextChangedFunc_SystemTextInput(d->sysCtrl, systemInputChanged_InputWidget_, d);
1079 iConnect(Root, w->root, visualOffsetsChanged, d, updateAfterVisualOffsetChange_InputWidget_); 1178 iConnect(Root, w->root, visualOffsetsChanged, d, updateAfterVisualOffsetChange_InputWidget_);
1080 updateMetrics_InputWidget_(d); 1179 updateMetrics_InputWidget_(d);
1081 return; 1180#else
1082#endif 1181 mergeLines_(&d->lines, &d->oldText);
1083 if (d->mode == overwrite_InputMode) { 1182 if (d->mode == overwrite_InputMode) {
1084 d->cursor = zero_I2(); 1183 d->cursor = zero_I2();
1085 } 1184 }
@@ -1091,7 +1190,6 @@ void begin_InputWidget(iInputWidget *d) {
1091 showCursor_InputWidget_(d); 1190 showCursor_InputWidget_(d);
1092 refresh_Widget(w); 1191 refresh_Widget(w);
1093 startOrStopCursorTimer_InputWidget_(d, iTrue); 1192 startOrStopCursorTimer_InputWidget_(d, iTrue);
1094 d->inFlags &= ~enterPressed_InputWidgetFlag;
1095 if (d->inFlags & selectAllOnFocus_InputWidgetFlag) { 1193 if (d->inFlags & selectAllOnFocus_InputWidgetFlag) {
1096 d->mark = (iRanges){ 0, lastLine_InputWidget_(d)->range.end }; 1194 d->mark = (iRanges){ 0, lastLine_InputWidget_(d)->range.end };
1097 d->cursor = cursorMax_InputWidget_(d); 1195 d->cursor = cursorMax_InputWidget_(d);
@@ -1102,6 +1200,7 @@ void begin_InputWidget(iInputWidget *d) {
1102 enableEditorKeysInMenus_(iFalse); 1200 enableEditorKeysInMenus_(iFalse);
1103 updateTextInputRect_InputWidget_(d); 1201 updateTextInputRect_InputWidget_(d);
1104 updateVisible_InputWidget_(d); 1202 updateVisible_InputWidget_(d);
1203#endif
1105} 1204}
1106 1205
1107void end_InputWidget(iInputWidget *d, iBool accept) { 1206void end_InputWidget(iInputWidget *d, iBool accept) {
@@ -1110,23 +1209,29 @@ void end_InputWidget(iInputWidget *d, iBool accept) {
1110 /* Was not active. */ 1209 /* Was not active. */
1111 return; 1210 return;
1112 } 1211 }
1212#if LAGRANGE_USE_SYSTEM_TEXT_INPUT
1113 if (d->sysCtrl) { 1213 if (d->sysCtrl) {
1114 iDisconnect(Root, w->root, visualOffsetsChanged, d, updateAfterVisualOffsetChange_InputWidget_); 1214 iDisconnect(Root, w->root, visualOffsetsChanged, d, updateAfterVisualOffsetChange_InputWidget_);
1115 if (accept) { 1215 if (accept) {
1116 splitToLines_(text_SystemTextInput(d->sysCtrl), &d->lines); 1216 set_String(&d->text, text_SystemTextInput(d->sysCtrl));
1217 }
1218 else {
1219 set_String(&d->text, &d->oldText);
1117 } 1220 }
1118 delete_SystemTextInput(d->sysCtrl); 1221 delete_SystemTextInput(d->sysCtrl);
1119 d->sysCtrl = NULL; 1222 d->sysCtrl = NULL;
1120 } 1223 }
1121 else if (!accept) { 1224#else
1225 if (!accept) {
1122 /* Overwrite the edited lines. */ 1226 /* Overwrite the edited lines. */
1123 splitToLines_(&d->oldText, &d->lines); 1227 splitToLines_(&d->oldText, &d->lines);
1124 SDL_StopTextInput(); 1228 SDL_StopTextInput();
1125 } 1229 }
1126 enableEditorKeysInMenus_(iTrue); 1230 enableEditorKeysInMenus_(iTrue);
1127 d->inFlags |= needUpdateBuffer_InputWidgetFlag;
1128 d->inFlags &= ~isMarking_InputWidgetFlag; 1231 d->inFlags &= ~isMarking_InputWidgetFlag;
1129 startOrStopCursorTimer_InputWidget_(d, iFalse); 1232 startOrStopCursorTimer_InputWidget_(d, iFalse);
1233#endif
1234 d->inFlags |= needUpdateBuffer_InputWidgetFlag;
1130 setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag | touchDrag_WidgetFlag, iFalse); 1235 setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag | touchDrag_WidgetFlag, iFalse);
1131 const char *id = cstr_String(id_Widget(as_Widget(d))); 1236 const char *id = cstr_String(id_Widget(as_Widget(d)));
1132 if (!*id) id = "_"; 1237 if (!*id) id = "_";
@@ -1138,6 +1243,7 @@ void end_InputWidget(iInputWidget *d, iBool accept) {
1138 accept ? 1 : 0); 1243 accept ? 1 : 0);
1139} 1244}
1140 1245
1246#if !LAGRANGE_USE_SYSTEM_TEXT_INPUT
1141static void textOfLinesWasChanged_InputWidget_(iInputWidget *d, iRangei lineRange) { 1247static void textOfLinesWasChanged_InputWidget_(iInputWidget *d, iRangei lineRange) {
1142 for (int i = lineRange.start; i < lineRange.end; i++) { 1248 for (int i = lineRange.start; i < lineRange.end; i++) {
1143 updateLine_InputWidget_(d, at_Array(&d->lines, i)); 1249 updateLine_InputWidget_(d, at_Array(&d->lines, i));
@@ -1271,27 +1377,6 @@ static iBool moveCursorByLine_InputWidget_(iInputWidget *d, int dir, int horiz)
1271 return iTrue; 1377 return iTrue;
1272} 1378}
1273 1379
1274void setSensitiveContent_InputWidget(iInputWidget *d, iBool isSensitive) {
1275 iChangeFlags(d->inFlags, isSensitive_InputWidgetFlag, isSensitive);
1276}
1277
1278void setUrlContent_InputWidget(iInputWidget *d, iBool isUrl) {
1279 iChangeFlags(d->inFlags, isUrl_InputWidgetFlag, isUrl);
1280 d->inFlags |= needUpdateBuffer_InputWidgetFlag;
1281}
1282
1283void setSelectAllOnFocus_InputWidget(iInputWidget *d, iBool selectAllOnFocus) {
1284 iChangeFlags(d->inFlags, selectAllOnFocus_InputWidgetFlag, selectAllOnFocus);
1285}
1286
1287void setNotifyEdits_InputWidget(iInputWidget *d, iBool notifyEdits) {
1288 iChangeFlags(d->inFlags, notifyEdits_InputWidgetFlag, notifyEdits);
1289}
1290
1291void setEatEscape_InputWidget(iInputWidget *d, iBool eatEscape) {
1292 iChangeFlags(d->inFlags, eatEscape_InputWidgetFlag, eatEscape);
1293}
1294
1295static iRanges mark_InputWidget_(const iInputWidget *d) { 1380static iRanges mark_InputWidget_(const iInputWidget *d) {
1296 iRanges m = { iMin(d->mark.start, d->mark.end), iMax(d->mark.start, d->mark.end) }; 1381 iRanges m = { iMin(d->mark.start, d->mark.end), iMax(d->mark.start, d->mark.end) };
1297 const iInputLine *last = lastLine_InputWidget_(d); 1382 const iInputLine *last = lastLine_InputWidget_(d);
@@ -1300,15 +1385,6 @@ static iRanges mark_InputWidget_(const iInputWidget *d) {
1300 return m; 1385 return m;
1301} 1386}
1302 1387
1303static void contentsWereChanged_InputWidget_(iInputWidget *d) {
1304 if (d->validator) {
1305 d->validator(d, d->validatorContext); /* this may change the contents */
1306 }
1307 if (d->inFlags & notifyEdits_InputWidgetFlag) {
1308 postCommand_Widget(d, "input.edited id:%s", cstr_String(id_Widget(constAs_Widget(d))));
1309 }
1310}
1311
1312static void deleteIndexRange_InputWidget_(iInputWidget *d, iRanges deleted) { 1388static void deleteIndexRange_InputWidget_(iInputWidget *d, iRanges deleted) {
1313 size_t firstModified = iInvalidPos; 1389 size_t firstModified = iInvalidPos;
1314 restartBackupTimer_InputWidget_(d); 1390 restartBackupTimer_InputWidget_(d);
@@ -1517,6 +1593,42 @@ static void extendRange_InputWidget_(iInputWidget *d, size_t *index, int dir) {
1517 *index = cursorToIndex_InputWidget_(d, pos); 1593 *index = cursorToIndex_InputWidget_(d, pos);
1518} 1594}
1519 1595
1596static void lineTextWasChanged_InputWidget_(iInputWidget *d, iInputLine *line) {
1597 const int y = indexOf_Array(&d->lines, line);
1598 textOfLinesWasChanged_InputWidget_(d, (iRangei){ y, y + 1 });
1599}
1600#endif
1601
1602void setSensitiveContent_InputWidget(iInputWidget *d, iBool isSensitive) {
1603 iChangeFlags(d->inFlags, isSensitive_InputWidgetFlag, isSensitive);
1604}
1605
1606void setUrlContent_InputWidget(iInputWidget *d, iBool isUrl) {
1607 iChangeFlags(d->inFlags, isUrl_InputWidgetFlag, isUrl);
1608 d->inFlags |= needUpdateBuffer_InputWidgetFlag;
1609}
1610
1611void setSelectAllOnFocus_InputWidget(iInputWidget *d, iBool selectAllOnFocus) {
1612 iChangeFlags(d->inFlags, selectAllOnFocus_InputWidgetFlag, selectAllOnFocus);
1613}
1614
1615void setNotifyEdits_InputWidget(iInputWidget *d, iBool notifyEdits) {
1616 iChangeFlags(d->inFlags, notifyEdits_InputWidgetFlag, notifyEdits);
1617}
1618
1619void setEatEscape_InputWidget(iInputWidget *d, iBool eatEscape) {
1620 iChangeFlags(d->inFlags, eatEscape_InputWidgetFlag, eatEscape);
1621}
1622
1623static void contentsWereChanged_InputWidget_(iInputWidget *d) {
1624 if (d->validator) {
1625 d->validator(d, d->validatorContext); /* this may change the contents */
1626 }
1627 if (d->inFlags & notifyEdits_InputWidgetFlag) {
1628 postCommand_Widget(d, "input.edited id:%s", cstr_String(id_Widget(constAs_Widget(d))));
1629 }
1630}
1631
1520static iRect bounds_InputWidget_(const iInputWidget *d) { 1632static iRect bounds_InputWidget_(const iInputWidget *d) {
1521 const iWidget *w = constAs_Widget(d); 1633 const iWidget *w = constAs_Widget(d);
1522 iRect bounds = bounds_Widget(w); 1634 iRect bounds = bounds_Widget(w);
@@ -1535,11 +1647,6 @@ static iBool contains_InputWidget_(const iInputWidget *d, iInt2 coord) {
1535 return contains_Rect(bounds_InputWidget_(d), coord); 1647 return contains_Rect(bounds_InputWidget_(d), coord);
1536} 1648}
1537 1649
1538static void lineTextWasChanged_InputWidget_(iInputWidget *d, iInputLine *line) {
1539 const int y = indexOf_Array(&d->lines, line);
1540 textOfLinesWasChanged_InputWidget_(d, (iRangei){ y, y + 1 });
1541}
1542
1543static iBool isArrowUpDownConsumed_InputWidget_(const iInputWidget *d) { 1650static iBool isArrowUpDownConsumed_InputWidget_(const iInputWidget *d) {
1544 return d->maxWrapLines > 1; 1651 return d->maxWrapLines > 1;
1545} 1652}
@@ -1564,6 +1671,7 @@ enum iEventResult {
1564 true_EventResult = 2, /* event was processed and should not be passed on */ 1671 true_EventResult = 2, /* event was processed and should not be passed on */
1565}; 1672};
1566 1673
1674#if !LAGRANGE_USE_SYSTEM_TEXT_INPUT
1567static void markWordAtCursor_InputWidget_(iInputWidget *d) { 1675static void markWordAtCursor_InputWidget_(iInputWidget *d) {
1568 d->mark.start = d->mark.end = cursorToIndex_InputWidget_(d, d->cursor); 1676 d->mark.start = d->mark.end = cursorToIndex_InputWidget_(d, d->cursor);
1569 extendRange_InputWidget_(d, &d->mark.start, -1); 1677 extendRange_InputWidget_(d, &d->mark.start, -1);
@@ -1580,8 +1688,10 @@ static void showClipMenu_(iInt2 coord) {
1580 openMenuFlags_Widget(clipMenu, coord, iFalse); 1688 openMenuFlags_Widget(clipMenu, coord, iFalse);
1581 } 1689 }
1582} 1690}
1691#endif
1583 1692
1584static enum iEventResult processPointerEvents_InputWidget_(iInputWidget *d, const SDL_Event *ev) { 1693static enum iEventResult processPointerEvents_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1694#if !LAGRANGE_USE_SYSTEM_TEXT_INPUT
1585 iWidget *w = as_Widget(d); 1695 iWidget *w = as_Widget(d);
1586 if (ev->type == SDL_MOUSEMOTION && (isHover_Widget(d) || flags_Widget(w) & keepOnTop_WidgetFlag)) { 1696 if (ev->type == SDL_MOUSEMOTION && (isHover_Widget(d) || flags_Widget(w) & keepOnTop_WidgetFlag)) {
1587 const iInt2 coord = init_I2(ev->motion.x, ev->motion.y); 1697 const iInt2 coord = init_I2(ev->motion.x, ev->motion.y);
@@ -1655,9 +1765,11 @@ static enum iEventResult processPointerEvents_InputWidget_(iInputWidget *d, cons
1655 return true_EventResult; 1765 return true_EventResult;
1656 } 1766 }
1657 } 1767 }
1768#endif
1658 return ignored_EventResult; 1769 return ignored_EventResult;
1659} 1770}
1660 1771
1772#if !LAGRANGE_USE_SYSTEM_TEXT_INPUT
1661static iInt2 touchCoordCursor_InputWidget_(const iInputWidget *d, iInt2 coord) { 1773static iInt2 touchCoordCursor_InputWidget_(const iInputWidget *d, iInt2 coord) {
1662 /* Clamp to the bounds so the cursor doesn't wrap at the ends. */ 1774 /* Clamp to the bounds so the cursor doesn't wrap at the ends. */
1663 iRect bounds = shrunk_Rect(contentBounds_InputWidget_(d), one_I2()); 1775 iRect bounds = shrunk_Rect(contentBounds_InputWidget_(d), one_I2());
@@ -1679,9 +1791,11 @@ static int distanceToPos_InputWidget_(const iInputWidget *d, iInt2 uiCoord, iInt
1679 } 1791 }
1680 return dist_I2(addY_I2(winCoord, lineHeight_Text(d->font) / 2), uiCoord); 1792 return dist_I2(addY_I2(winCoord, lineHeight_Text(d->font) / 2), uiCoord);
1681} 1793}
1794#endif
1682 1795
1683static enum iEventResult processTouchEvents_InputWidget_(iInputWidget *d, const SDL_Event *ev) { 1796static enum iEventResult processTouchEvents_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1684 iWidget *w = as_Widget(d); 1797 iWidget *w = as_Widget(d);
1798#if !LAGRANGE_USE_SYSTEM_TEXT_INPUT
1685 /* 1799 /*
1686 + first tap to focus & select all/place cursor 1800 + first tap to focus & select all/place cursor
1687 + focused tap to place cursor 1801 + focused tap to place cursor
@@ -1923,9 +2037,22 @@ static enum iEventResult processTouchEvents_InputWidget_(iInputWidget *d, const
1923// /* Eat all mouse clicks on the widget. */ 2037// /* Eat all mouse clicks on the widget. */
1924// return true_EventResult; 2038// return true_EventResult;
1925// } 2039// }
2040#else
2041 /* Just a tap to activate the system-provided text input control. */
2042 switch (processEvent_Click(&d->click, ev)) {
2043 case none_ClickResult:
2044 break;
2045 case started_ClickResult:
2046 setFocus_Widget(w);
2047 return true_EventResult;
2048 default:
2049 return true_EventResult;
2050 }
2051#endif
1926 return ignored_EventResult; 2052 return ignored_EventResult;
1927} 2053}
1928 2054
2055#if !LAGRANGE_USE_SYSTEM_TEXT_INPUT
1929static void clampWheelAccum_InputWidget_(iInputWidget *d, int wheel) { 2056static void clampWheelAccum_InputWidget_(iInputWidget *d, int wheel) {
1930 if (wheel > 0 && d->visWrapLines.start == 0) { 2057 if (wheel > 0 && d->visWrapLines.start == 0) {
1931 d->wheelAccum = 0; 2058 d->wheelAccum = 0;
@@ -1936,6 +2063,7 @@ static void clampWheelAccum_InputWidget_(iInputWidget *d, int wheel) {
1936 refresh_Widget(d); 2063 refresh_Widget(d);
1937 } 2064 }
1938} 2065}
2066#endif
1939 2067
1940static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { 2068static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1941 iWidget *w = as_Widget(d); 2069 iWidget *w = as_Widget(d);
@@ -1953,13 +2081,6 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1953 begin_InputWidget(d); 2081 begin_InputWidget(d);
1954 return iFalse; 2082 return iFalse;
1955 } 2083 }
1956 else if (isEditing_InputWidget_(d) && (isCommand_UserEvent(ev, "window.focus.lost") ||
1957 isCommand_UserEvent(ev, "window.focus.gained"))) {
1958 startOrStopCursorTimer_InputWidget_(d, isCommand_UserEvent(ev, "window.focus.gained"));
1959 d->cursorVis = 1;
1960 refresh_Widget(d);
1961 return iFalse;
1962 }
1963 else if (isCommand_UserEvent(ev, "keyroot.changed")) { 2084 else if (isCommand_UserEvent(ev, "keyroot.changed")) {
1964 d->inFlags |= needUpdateBuffer_InputWidgetFlag; 2085 d->inFlags |= needUpdateBuffer_InputWidgetFlag;
1965 } 2086 }
@@ -1972,11 +2093,23 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1972 end_InputWidget(d, iTrue); 2093 end_InputWidget(d, iTrue);
1973 return iFalse; 2094 return iFalse;
1974 } 2095 }
2096#if !LAGRANGE_USE_SYSTEM_TEXT_INPUT
2097 else if (isEditing_InputWidget_(d) && (isCommand_UserEvent(ev, "window.focus.lost") ||
2098 isCommand_UserEvent(ev, "window.focus.gained"))) {
2099 startOrStopCursorTimer_InputWidget_(d, isCommand_UserEvent(ev, "window.focus.gained"));
2100 d->cursorVis = 1;
2101 refresh_Widget(d);
2102 return iFalse;
2103 }
1975 else if ((isCommand_UserEvent(ev, "copy") || isCommand_UserEvent(ev, "input.copy")) && 2104 else if ((isCommand_UserEvent(ev, "copy") || isCommand_UserEvent(ev, "input.copy")) &&
1976 isEditing_InputWidget_(d)) { 2105 isEditing_InputWidget_(d)) {
1977 copy_InputWidget_(d, argLabel_Command(command_UserEvent(ev), "cut")); 2106 copy_InputWidget_(d, argLabel_Command(command_UserEvent(ev), "cut"));
1978 return iTrue; 2107 return iTrue;
1979 } 2108 }
2109// else if (isFocused_Widget(d) && isCommand_UserEvent(ev, "copy")) {
2110// copy_InputWidget_(d, iFalse);
2111// return iTrue;
2112// }
1980 else if (isCommand_UserEvent(ev, "input.paste") && isEditing_InputWidget_(d)) { 2113 else if (isCommand_UserEvent(ev, "input.paste") && isEditing_InputWidget_(d)) {
1981 paste_InputWidget_(d); 2114 paste_InputWidget_(d);
1982 return iTrue; 2115 return iTrue;
@@ -1992,6 +2125,14 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1992 selectAll_InputWidget(d); 2125 selectAll_InputWidget(d);
1993 return iTrue; 2126 return iTrue;
1994 } 2127 }
2128 else if (isCommand_UserEvent(ev, "text.insert")) {
2129 pushUndo_InputWidget_(d);
2130 deleteMarked_InputWidget_(d);
2131 insertChar_InputWidget_(d, arg_Command(command_UserEvent(ev)));
2132 contentsWereChanged_InputWidget_(d);
2133 return iTrue;
2134 }
2135#endif
1995 else if (isCommand_UserEvent(ev, "theme.changed")) { 2136 else if (isCommand_UserEvent(ev, "theme.changed")) {
1996 if (d->buffered) { 2137 if (d->buffered) {
1997 d->inFlags |= needUpdateBuffer_InputWidgetFlag; 2138 d->inFlags |= needUpdateBuffer_InputWidgetFlag;
@@ -2010,13 +2151,6 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
2010// } 2151// }
2011// return iFalse; 2152// return iFalse;
2012// } 2153// }
2013 else if (isCommand_UserEvent(ev, "text.insert")) {
2014 pushUndo_InputWidget_(d);
2015 deleteMarked_InputWidget_(d);
2016 insertChar_InputWidget_(d, arg_Command(command_UserEvent(ev)));
2017 contentsWereChanged_InputWidget_(d);
2018 return iTrue;
2019 }
2020 else if (isCommand_Widget(w, ev, "input.backup")) { 2154 else if (isCommand_Widget(w, ev, "input.backup")) {
2021 if (d->inFlags & needBackup_InputWidgetFlag) { 2155 if (d->inFlags & needBackup_InputWidgetFlag) {
2022 saveBackup_InputWidget_(d); 2156 saveBackup_InputWidget_(d);
@@ -2027,10 +2161,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
2027 updateMetrics_InputWidget_(d); 2161 updateMetrics_InputWidget_(d);
2028 // updateLinesAndResize_InputWidget_(d); 2162 // updateLinesAndResize_InputWidget_(d);
2029 } 2163 }
2030 else if (isFocused_Widget(d) && isCommand_UserEvent(ev, "copy")) { 2164#if !LAGRANGE_USE_SYSTEM_TEXT_INPUT
2031 copy_InputWidget_(d, iFalse);
2032 return iTrue;
2033 }
2034 if (ev->type == SDL_MOUSEWHEEL && contains_Widget(w, coord_MouseWheelEvent(&ev->wheel))) { 2165 if (ev->type == SDL_MOUSEWHEEL && contains_Widget(w, coord_MouseWheelEvent(&ev->wheel))) {
2035 if (numWrapLines_InputWidget_(d) <= size_Range(&d->visWrapLines)) { 2166 if (numWrapLines_InputWidget_(d) <= size_Range(&d->visWrapLines)) {
2036 return ignored_EventResult; 2167 return ignored_EventResult;
@@ -2064,6 +2195,17 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
2064 } 2195 }
2065 return false_EventResult; 2196 return false_EventResult;
2066 } 2197 }
2198 if (ev->type == SDL_TEXTINPUT && isFocused_Widget(w)) {
2199 pushUndo_InputWidget_(d);
2200 deleteMarked_InputWidget_(d);
2201 insertRange_InputWidget_(d, range_CStr(ev->text.text));
2202 contentsWereChanged_InputWidget_(d);
2203 return iTrue;
2204 }
2205 const iInt2 curMax = cursorMax_InputWidget_(d);
2206 const iInt2 lineFirst = init_I2(0, d->cursor.y);
2207 const iInt2 lineLast = init_I2(endX_InputWidget_(d, d->cursor.y), d->cursor.y);
2208#endif
2067 /* Click behavior depends on device type. */ { 2209 /* Click behavior depends on device type. */ {
2068 const int mbResult = (deviceType_App() == desktop_AppDeviceType 2210 const int mbResult = (deviceType_App() == desktop_AppDeviceType
2069 ? processPointerEvents_InputWidget_(d, ev) 2211 ? processPointerEvents_InputWidget_(d, ev)
@@ -2075,12 +2217,10 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
2075 if (ev->type == SDL_KEYUP && isFocused_Widget(w)) { 2217 if (ev->type == SDL_KEYUP && isFocused_Widget(w)) {
2076 return iTrue; 2218 return iTrue;
2077 } 2219 }
2078 const iInt2 curMax = cursorMax_InputWidget_(d);
2079 const iInt2 lineFirst = init_I2(0, d->cursor.y);
2080 const iInt2 lineLast = init_I2(endX_InputWidget_(d, d->cursor.y), d->cursor.y);
2081 if (ev->type == SDL_KEYDOWN && isFocused_Widget(w)) { 2220 if (ev->type == SDL_KEYDOWN && isFocused_Widget(w)) {
2082 const int key = ev->key.keysym.sym; 2221 const int key = ev->key.keysym.sym;
2083 const int mods = keyMods_Sym(ev->key.keysym.mod); 2222 const int mods = keyMods_Sym(ev->key.keysym.mod);
2223#if !LAGRANGE_USE_SYSTEM_TEXT_INPUT
2084 if (mods == KMOD_PRIMARY) { 2224 if (mods == KMOD_PRIMARY) {
2085 switch (key) { 2225 switch (key) {
2086 case 'c': 2226 case 'c':
@@ -2098,7 +2238,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
2098 return iTrue; 2238 return iTrue;
2099 } 2239 }
2100 } 2240 }
2101#if defined (iPlatformApple) 2241# if defined (iPlatformApple)
2102 if (mods == KMOD_PRIMARY || mods == (KMOD_PRIMARY | KMOD_SHIFT)) { 2242 if (mods == KMOD_PRIMARY || mods == (KMOD_PRIMARY | KMOD_SHIFT)) {
2103 switch (key) { 2243 switch (key) {
2104 case SDLK_UP: 2244 case SDLK_UP:
@@ -2108,16 +2248,13 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
2108 return iTrue; 2248 return iTrue;
2109 } 2249 }
2110 } 2250 }
2111#endif 2251# endif
2112 d->prevCursor = d->cursor; 2252 d->prevCursor = d->cursor;
2253#endif
2113 switch (key) { 2254 switch (key) {
2114 case SDLK_INSERT:
2115 if (mods == KMOD_SHIFT) {
2116 paste_InputWidget_(d);
2117 }
2118 return iTrue;
2119 case SDLK_RETURN: 2255 case SDLK_RETURN:
2120 case SDLK_KP_ENTER: 2256 case SDLK_KP_ENTER:
2257#if !LAGRANGE_USE_SYSTEM_TEXT_INPUT
2121 if (isAllowedToInsertNewline_InputWidget_(d)) { 2258 if (isAllowedToInsertNewline_InputWidget_(d)) {
2122 if (checkLineBreakMods_InputWidget_(d, mods)) { 2259 if (checkLineBreakMods_InputWidget_(d, mods)) {
2123 pushUndo_InputWidget_(d); 2260 pushUndo_InputWidget_(d);
@@ -2127,6 +2264,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
2127 return iTrue; 2264 return iTrue;
2128 } 2265 }
2129 } 2266 }
2267#endif
2130 if (d->inFlags & enterKeyEnabled_InputWidgetFlag && 2268 if (d->inFlags & enterKeyEnabled_InputWidgetFlag &&
2131 (checkAcceptMods_InputWidget_(d, mods) || 2269 (checkAcceptMods_InputWidget_(d, mods) ||
2132 (~d->inFlags & lineBreaksEnabled_InputWidgetFlag))) { 2270 (~d->inFlags & lineBreaksEnabled_InputWidgetFlag))) {
@@ -2139,6 +2277,12 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
2139 end_InputWidget(d, iTrue); 2277 end_InputWidget(d, iTrue);
2140 setFocus_Widget(NULL); 2278 setFocus_Widget(NULL);
2141 return (d->inFlags & eatEscape_InputWidgetFlag) != 0; 2279 return (d->inFlags & eatEscape_InputWidgetFlag) != 0;
2280#if !LAGRANGE_USE_SYSTEM_TEXT_INPUT
2281 case SDLK_INSERT:
2282 if (mods == KMOD_SHIFT) {
2283 paste_InputWidget_(d);
2284 }
2285 return iTrue;
2142 case SDLK_BACKSPACE: 2286 case SDLK_BACKSPACE:
2143 if (!isEmpty_Range(&d->mark)) { 2287 if (!isEmpty_Range(&d->mark)) {
2144 pushUndo_InputWidget_(d); 2288 pushUndo_InputWidget_(d);
@@ -2238,7 +2382,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
2238 refresh_Widget(w); 2382 refresh_Widget(w);
2239 return iTrue; 2383 return iTrue;
2240 } 2384 }
2241#if defined (iPlatformApple) 2385# if defined (iPlatformApple)
2242 /* fall through for Emacs-style Home/End */ 2386 /* fall through for Emacs-style Home/End */
2243 case SDLK_e: 2387 case SDLK_e:
2244 if (mods == KMOD_CTRL || mods == (KMOD_CTRL | KMOD_SHIFT)) { 2388 if (mods == KMOD_CTRL || mods == (KMOD_CTRL | KMOD_SHIFT)) {
@@ -2246,7 +2390,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
2246 refresh_Widget(w); 2390 refresh_Widget(w);
2247 return iTrue; 2391 return iTrue;
2248 } 2392 }
2249#endif 2393# endif
2250 break; 2394 break;
2251 case SDLK_LEFT: 2395 case SDLK_LEFT:
2252 case SDLK_RIGHT: { 2396 case SDLK_RIGHT: {
@@ -2296,22 +2440,17 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
2296 } 2440 }
2297 refresh_Widget(d); 2441 refresh_Widget(d);
2298 return iTrue; 2442 return iTrue;
2443#endif
2299 } 2444 }
2300 if (mods & (KMOD_PRIMARY | KMOD_SECONDARY)) { 2445 if (mods & (KMOD_PRIMARY | KMOD_SECONDARY)) {
2301 return iFalse; 2446 return iFalse;
2302 } 2447 }
2303 return iTrue; 2448 return iTrue;
2304 } 2449 }
2305 else if (ev->type == SDL_TEXTINPUT && isFocused_Widget(w)) {
2306 pushUndo_InputWidget_(d);
2307 deleteMarked_InputWidget_(d);
2308 insertRange_InputWidget_(d, range_CStr(ev->text.text));
2309 contentsWereChanged_InputWidget_(d);
2310 return iTrue;
2311 }
2312 return processEvent_Widget(w, ev); 2450 return processEvent_Widget(w, ev);
2313} 2451}
2314 2452
2453#if !LAGRANGE_USE_SYSTEM_TEXT_INPUT
2315iDeclareType(MarkPainter) 2454iDeclareType(MarkPainter)
2316 2455
2317struct Impl_MarkPainter { 2456struct Impl_MarkPainter {
@@ -2370,6 +2509,7 @@ static iBool draw_MarkPainter_(iWrapText *wrapText, iRangecc wrappedText, iTextA
2370 } 2509 }
2371 return iTrue; 2510 return iTrue;
2372} 2511}
2512#endif
2373 2513
2374static void draw_InputWidget_(const iInputWidget *d) { 2514static void draw_InputWidget_(const iInputWidget *d) {
2375 const iWidget *w = constAs_Widget(d); 2515 const iWidget *w = constAs_Widget(d);
@@ -2393,16 +2533,18 @@ static void draw_InputWidget_(const iInputWidget *d) {
2393 isFocused ? uiInputFrameFocused_ColorId 2533 isFocused ? uiInputFrameFocused_ColorId
2394 : isHover ? uiInputFrameHover_ColorId : uiInputFrame_ColorId); 2534 : isHover ? uiInputFrameHover_ColorId : uiInputFrame_ColorId);
2395 if (d->sysCtrl) { 2535 if (d->sysCtrl) {
2536 /* The system-provided control is drawing the text. */
2396 drawChildren_Widget(w); 2537 drawChildren_Widget(w);
2397 return; 2538 return;
2398 } 2539 }
2399 setClip_Paint(&p, adjusted_Rect(bounds, init_I2(d->leftPadding, 0),
2400 init_I2(-d->rightPadding, w->flags & extraPadding_WidgetFlag ? -gap_UI / 2 : 0)));
2401 const iRect contentBounds = contentBounds_InputWidget_(d); 2540 const iRect contentBounds = contentBounds_InputWidget_(d);
2402 iInt2 drawPos = topLeft_Rect(contentBounds); 2541 iInt2 drawPos = topLeft_Rect(contentBounds);
2403 const int fg = isHint ? uiAnnotation_ColorId 2542 const int fg = isHint ? uiAnnotation_ColorId
2404 : isFocused /*&& !isEmpty_Array(&d->lines)*/ ? uiInputTextFocused_ColorId 2543 : isFocused /*&& !isEmpty_Array(&d->lines)*/ ? uiInputTextFocused_ColorId
2405 : uiInputText_ColorId; 2544 : uiInputText_ColorId;
2545#if !LAGRANGE_USE_SYSTEM_TEXT_INPUT
2546 setClip_Paint(&p, adjusted_Rect(bounds, init_I2(d->leftPadding, 0),
2547 init_I2(-d->rightPadding, w->flags & extraPadding_WidgetFlag ? -gap_UI / 2 : 0)));
2406 iWrapText wrapText = { 2548 iWrapText wrapText = {
2407 .maxWidth = d->maxLen == 0 ? width_Rect(contentBounds) : unlimitedWidth_InputWidget_, 2549 .maxWidth = d->maxLen == 0 ? width_Rect(contentBounds) : unlimitedWidth_InputWidget_,
2408 .mode = (d->inFlags & isUrl_InputWidgetFlag ? anyCharacter_WrapTextMode 2550 .mode = (d->inFlags & isUrl_InputWidgetFlag ? anyCharacter_WrapTextMode
@@ -2410,8 +2552,9 @@ static void draw_InputWidget_(const iInputWidget *d) {
2410 .overrideChar = (d->inFlags & isSensitive_InputWidgetFlag ? sensitiveChar_ : 0), 2552 .overrideChar = (d->inFlags & isSensitive_InputWidgetFlag ? sensitiveChar_ : 0),
2411 }; 2553 };
2412 const iRangei visLines = visibleLineRange_InputWidget_(d); 2554 const iRangei visLines = visibleLineRange_InputWidget_(d);
2413 const int visLineOffsetY = visLineOffsetY_InputWidget_(d);
2414 iRect markerRects[2] = { zero_Rect(), zero_Rect() }; 2555 iRect markerRects[2] = { zero_Rect(), zero_Rect() };
2556#endif
2557 const int visLineOffsetY = visLineOffsetY_InputWidget_(d);
2415 /* If buffered, just draw the buffered copy. */ 2558 /* If buffered, just draw the buffered copy. */
2416 if (d->buffered && !isFocused) { 2559 if (d->buffered && !isFocused) {
2417 /* Most input widgets will use this, since only one is focused at a time. */ 2560 /* Most input widgets will use this, since only one is focused at a time. */
@@ -2439,6 +2582,7 @@ static void draw_InputWidget_(const iInputWidget *d) {
2439 drawRange_Text(d->font, drawPos, uiAnnotation_ColorId, range_String(&d->hint)); 2582 drawRange_Text(d->font, drawPos, uiAnnotation_ColorId, range_String(&d->hint));
2440 } 2583 }
2441 } 2584 }
2585#if !LAGRANGE_USE_SYSTEM_TEXT_INPUT
2442 else { 2586 else {
2443 iAssert(~d->inFlags & isSensitive_InputWidgetFlag || size_Range(&visLines) == 1); 2587 iAssert(~d->inFlags & isSensitive_InputWidgetFlag || size_Range(&visLines) == 1);
2444 drawPos.y += visLineOffsetY; 2588 drawPos.y += visLineOffsetY;
@@ -2517,6 +2661,7 @@ static void draw_InputWidget_(const iInputWidget *d) {
2517 drawPin_Paint(&p, markerRects[i], i, uiTextCaution_ColorId); 2661 drawPin_Paint(&p, markerRects[i], i, uiTextCaution_ColorId);
2518 } 2662 }
2519 } 2663 }
2664#endif
2520 drawChildren_Widget(w); 2665 drawChildren_Widget(w);
2521} 2666}
2522 2667