diff options
-rw-r--r-- | src/ui/inputwidget.c | 587 |
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 |
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | 21 | SOFTWARE, 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 | ||
52 | iDeclareType(SystemTextInput) | ||
46 | #endif | 53 | #endif |
47 | 54 | ||
48 | static const int refreshInterval_InputWidget_ = 512; | 55 | static const int refreshInterval_InputWidget_ = 512; |
49 | static const size_t maxUndo_InputWidget_ = 64; | 56 | static const size_t maxUndo_InputWidget_ = 64; |
50 | static const int unlimitedWidth_InputWidget_ = 1000000; /* TODO: WrapText disables some functionality if maxWidth==0 */ | 57 | static const int unlimitedWidth_InputWidget_ = 1000000; /* TODO: WrapText disables some functionality if maxWidth==0 */ |
51 | 58 | ||
59 | static const iChar sensitiveChar_ = 0x25cf; /* black circle */ | ||
60 | static const char * sensitive_ = "\u25cf"; | ||
61 | |||
52 | static void enableEditorKeysInMenus_(iBool enable) { | 62 | static 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 | ||
75 | static void updateMetrics_InputWidget_(iInputWidget *); | ||
76 | |||
65 | /*----------------------------------------------------------------------------------------------*/ | 77 | /*----------------------------------------------------------------------------------------------*/ |
78 | #if !LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
66 | 79 | ||
67 | iDeclareType(InputLine) | 80 | iDeclareType(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 | |||
186 | enum iInputWidgetFlag { | 201 | enum 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 { | |||
208 | struct Impl_InputWidget { | 223 | struct 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 | ||
244 | iDefineObjectConstructionArgs(InputWidget, (size_t maxLen), maxLen) | 263 | iDefineObjectConstructionArgs(InputWidget, (size_t maxLen), maxLen) |
245 | 264 | ||
246 | static void updateMetrics_InputWidget_(iInputWidget *); | ||
247 | |||
248 | static void restoreBackup_InputWidget_(iInputWidget *d) { | 265 | static 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 | ||
341 | iLocalDef iInt2 padding_(void) { | ||
342 | return init_I2(gap_UI / 2, gap_UI / 2); | ||
343 | } | ||
344 | |||
345 | #if !LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
346 | |||
320 | static void clearUndo_InputWidget_(iInputWidget *d) { | 347 | static 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 | ||
327 | iLocalDef iInt2 padding_(void) { | 354 | static 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 | ||
333 | static iRect contentBounds_InputWidget_(const iInputWidget *d) { | 363 | static 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 | ||
376 | static 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 | |||
346 | iLocalDef iBool isLastLine_InputWidget_(const iInputWidget *d, const iInputLine *line) { | 395 | iLocalDef 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 | ||
359 | static 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 | |||
364 | static const iString *lineString_InputWidget_(const iInputWidget *d, int y) { | 408 | static 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 | ||
464 | static const iChar sensitiveChar_ = 0x25cf; /* black circle */ | ||
465 | static const char *sensitive_ = "\u25cf"; | ||
466 | |||
467 | static 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 | |||
478 | static iRangei visibleLineRange_InputWidget_(const iInputWidget *d) { | 508 | static 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 | |||
592 | static int visLineOffsetY_InputWidget_(const iInputWidget *d) { | ||
593 | return 0; /* offset for the buffered text */ | ||
594 | } | ||
595 | |||
596 | static void updateVisible_InputWidget_(iInputWidget *d) { | ||
597 | iUnused(d); | ||
598 | /* TODO: Anything to do? */ | ||
599 | } | ||
600 | |||
601 | #endif | ||
602 | |||
560 | static void invalidateBuffered_InputWidget_(iInputWidget *d) { | 603 | static 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 | ||
582 | static iString *text_InputWidget_(const iInputWidget *d) { | 625 | static 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 | ||
588 | static size_t length_InputWidget_(const iInputWidget *d) { | 636 | static 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 | ||
598 | static 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 | |||
608 | static 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 | |||
618 | static 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 | |||
634 | static void updateLine_InputWidget_(iInputWidget *d, iInputLine *line) { | 646 | static 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 | |||
710 | static 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 | |||
722 | static 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 | |||
737 | static 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 | |||
749 | static 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 | |||
696 | void init_InputWidget(iInputWidget *d, size_t maxLen) { | 765 | void 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 | ||
743 | void deinit_InputWidget(iInputWidget *d) { | 815 | void 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 | ||
768 | static iBool isAllowedToInsertNewline_InputWidget_(const iInputWidget *d) { | 844 | static 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 |
775 | static void updateAfterVisualOffsetChange_InputWidget_(iInputWidget *d, iRoot *root) { | 851 | static 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 | ||
789 | static void pushUndo_InputWidget_(iInputWidget *d) { | 866 | static 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) { | |||
799 | static iBool popUndo_InputWidget_(iInputWidget *d) { | 876 | static 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 | ||
890 | iLocalDef iInputLine *cursorLine_InputWidget_(iInputWidget *d) { | ||
891 | return at_Array(&d->lines, d->cursor.y); | ||
892 | } | ||
893 | |||
894 | iLocalDef const iInputLine *constCursorLine_InputWidget_(const iInputWidget *d) { | ||
895 | return constAt_Array(&d->lines, d->cursor.y); | ||
896 | } | ||
897 | |||
898 | iLocalDef 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 | |||
903 | static 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 | |||
915 | static 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 | |||
814 | void setMode_InputWidget(iInputWidget *d, enum iInputMode mode) { | 927 | void 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 | ||
911 | iLocalDef iBool isEmpty_InputWidget_(const iInputWidget *d) { | 1024 | iLocalDef 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 | ||
915 | static iBool isHintVisible_InputWidget_(const iInputWidget *d) { | 1032 | static iBool isHintVisible_InputWidget_(const iInputWidget *d) { |
@@ -919,15 +1036,20 @@ static iBool isHintVisible_InputWidget_(const iInputWidget *d) { | |||
919 | static void updateBuffered_InputWidget_(iInputWidget *d) { | 1036 | static 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 | ||
956 | iLocalDef iInputLine *cursorLine_InputWidget_(iInputWidget *d) { | ||
957 | return at_Array(&d->lines, d->cursor.y); | ||
958 | } | ||
959 | |||
960 | iLocalDef const iInputLine *constCursorLine_InputWidget_(const iInputWidget *d) { | ||
961 | return constAt_Array(&d->lines, d->cursor.y); | ||
962 | } | ||
963 | |||
964 | iLocalDef 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 | |||
969 | void setText_InputWidget(iInputWidget *d, const iString *text) { | 1078 | void 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 | ||
1009 | void setTextCStr_InputWidget(iInputWidget *d, const char *cstr) { | 1127 | void 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 | ||
1015 | static 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 | |||
1027 | static 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 | |||
1038 | void selectAll_InputWidget(iInputWidget *d) { | 1133 | void 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 | ||
1043 | iLocalDef iBool isEditing_InputWidget_(const iInputWidget *d) { | 1140 | iLocalDef iBool isEditing_InputWidget_(const iInputWidget *d) { |
@@ -1046,10 +1143,10 @@ iLocalDef iBool isEditing_InputWidget_(const iInputWidget *d) { | |||
1046 | 1143 | ||
1047 | static void contentsWereChanged_InputWidget_(iInputWidget *); | 1144 | static void contentsWereChanged_InputWidget_(iInputWidget *); |
1048 | 1145 | ||
1049 | #if defined (LAGRANGE_ENABLE_SYSTEM_INPUT) | 1146 | #if LAGRANGE_USE_SYSTEM_TEXT_INPUT |
1050 | void systemInputChanged_InputWidget_(iSystemTextInput *sysCtrl, void *widget) { | 1147 | void 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 | ||
1107 | void end_InputWidget(iInputWidget *d, iBool accept) { | 1206 | void 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 | ||
1141 | static void textOfLinesWasChanged_InputWidget_(iInputWidget *d, iRangei lineRange) { | 1247 | static 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 | ||
1274 | void setSensitiveContent_InputWidget(iInputWidget *d, iBool isSensitive) { | ||
1275 | iChangeFlags(d->inFlags, isSensitive_InputWidgetFlag, isSensitive); | ||
1276 | } | ||
1277 | |||
1278 | void setUrlContent_InputWidget(iInputWidget *d, iBool isUrl) { | ||
1279 | iChangeFlags(d->inFlags, isUrl_InputWidgetFlag, isUrl); | ||
1280 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; | ||
1281 | } | ||
1282 | |||
1283 | void setSelectAllOnFocus_InputWidget(iInputWidget *d, iBool selectAllOnFocus) { | ||
1284 | iChangeFlags(d->inFlags, selectAllOnFocus_InputWidgetFlag, selectAllOnFocus); | ||
1285 | } | ||
1286 | |||
1287 | void setNotifyEdits_InputWidget(iInputWidget *d, iBool notifyEdits) { | ||
1288 | iChangeFlags(d->inFlags, notifyEdits_InputWidgetFlag, notifyEdits); | ||
1289 | } | ||
1290 | |||
1291 | void setEatEscape_InputWidget(iInputWidget *d, iBool eatEscape) { | ||
1292 | iChangeFlags(d->inFlags, eatEscape_InputWidgetFlag, eatEscape); | ||
1293 | } | ||
1294 | |||
1295 | static iRanges mark_InputWidget_(const iInputWidget *d) { | 1380 | static 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 | ||
1303 | static 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 | |||
1312 | static void deleteIndexRange_InputWidget_(iInputWidget *d, iRanges deleted) { | 1388 | static 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 | ||
1596 | static 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 | |||
1602 | void setSensitiveContent_InputWidget(iInputWidget *d, iBool isSensitive) { | ||
1603 | iChangeFlags(d->inFlags, isSensitive_InputWidgetFlag, isSensitive); | ||
1604 | } | ||
1605 | |||
1606 | void setUrlContent_InputWidget(iInputWidget *d, iBool isUrl) { | ||
1607 | iChangeFlags(d->inFlags, isUrl_InputWidgetFlag, isUrl); | ||
1608 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; | ||
1609 | } | ||
1610 | |||
1611 | void setSelectAllOnFocus_InputWidget(iInputWidget *d, iBool selectAllOnFocus) { | ||
1612 | iChangeFlags(d->inFlags, selectAllOnFocus_InputWidgetFlag, selectAllOnFocus); | ||
1613 | } | ||
1614 | |||
1615 | void setNotifyEdits_InputWidget(iInputWidget *d, iBool notifyEdits) { | ||
1616 | iChangeFlags(d->inFlags, notifyEdits_InputWidgetFlag, notifyEdits); | ||
1617 | } | ||
1618 | |||
1619 | void setEatEscape_InputWidget(iInputWidget *d, iBool eatEscape) { | ||
1620 | iChangeFlags(d->inFlags, eatEscape_InputWidgetFlag, eatEscape); | ||
1621 | } | ||
1622 | |||
1623 | static 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 | |||
1520 | static iRect bounds_InputWidget_(const iInputWidget *d) { | 1632 | static 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 | ||
1538 | static 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 | |||
1543 | static iBool isArrowUpDownConsumed_InputWidget_(const iInputWidget *d) { | 1650 | static 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 | ||
1567 | static void markWordAtCursor_InputWidget_(iInputWidget *d) { | 1675 | static 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 | ||
1584 | static enum iEventResult processPointerEvents_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | 1693 | static 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 | ||
1661 | static iInt2 touchCoordCursor_InputWidget_(const iInputWidget *d, iInt2 coord) { | 1773 | static 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 | ||
1683 | static enum iEventResult processTouchEvents_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | 1796 | static 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 | ||
1929 | static void clampWheelAccum_InputWidget_(iInputWidget *d, int wheel) { | 2056 | static 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 | ||
1940 | static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | 2068 | static 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 | ||
2315 | iDeclareType(MarkPainter) | 2454 | iDeclareType(MarkPainter) |
2316 | 2455 | ||
2317 | struct Impl_MarkPainter { | 2456 | struct 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 | ||
2374 | static void draw_InputWidget_(const iInputWidget *d) { | 2514 | static 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 | ||