summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-12-05 07:54:15 +0200
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-12-05 07:54:15 +0200
commitb8656d5a2e0f540d47d2dc5ccc269f709dd0b923 (patch)
treefbfc90bddc79bf35fb024293270f71496186fb22
parent8050511b683d89c5ef907bc461b98e9aaaa051a7 (diff)
iOS: Multiline text input using UITextView
-rw-r--r--src/ios.h14
-rw-r--r--src/ios.m158
-rw-r--r--src/ui/inputwidget.c16
3 files changed, 148 insertions, 40 deletions
diff --git a/src/ios.h b/src/ios.h
index 2908d833..ff07b2e8 100644
--- a/src/ios.h
+++ b/src/ios.h
@@ -65,15 +65,16 @@ void updateNowPlayingInfo_iOS (void);
65/*----------------------------------------------------------------------------------------------*/ 65/*----------------------------------------------------------------------------------------------*/
66 66
67enum iSystemTextInputFlags { 67enum iSystemTextInputFlags {
68 multiLine_SystemTextInputFlags = iBit(1), 68 selectAll_SystemTextInputFlags = iBit(1),
69 returnGo_SystemTextInputFlags = iBit(2), 69 multiLine_SystemTextInputFlags = iBit(2),
70 returnSend_SystemTextInputFlags = iBit(3), 70 returnGo_SystemTextInputFlags = iBit(3),
71 disableAutocorrect_SystemTextInputFlag = iBit(4), 71 returnSend_SystemTextInputFlags = iBit(4),
72 alignRight_SystemTextInputFlag = iBit(5), 72 disableAutocorrect_SystemTextInputFlag = iBit(5),
73 alignRight_SystemTextInputFlag = iBit(6),
73}; 74};
74 75
75iDeclareType(SystemTextInput) 76iDeclareType(SystemTextInput)
76iDeclareTypeConstructionArgs(SystemTextInput, int flags) 77iDeclareTypeConstructionArgs(SystemTextInput, iRect rect, int flags)
77 78
78void setRect_SystemTextInput (iSystemTextInput *, iRect rect); 79void setRect_SystemTextInput (iSystemTextInput *, iRect rect);
79void setText_SystemTextInput (iSystemTextInput *, const iString *text); 80void setText_SystemTextInput (iSystemTextInput *, const iString *text);
@@ -81,3 +82,4 @@ void setFont_SystemTextInput (iSystemTextInput *, int fontId);
81void setTextChangedFunc_SystemTextInput (iSystemTextInput *, void (*textChangedFunc)(iSystemTextInput *, void *), void *); 82void setTextChangedFunc_SystemTextInput (iSystemTextInput *, void (*textChangedFunc)(iSystemTextInput *, void *), void *);
82 83
83const iString * text_SystemTextInput (const iSystemTextInput *); 84const iString * text_SystemTextInput (const iSystemTextInput *);
85int preferredHeight_SystemTextInput (const iSystemTextInput *);
diff --git a/src/ios.m b/src/ios.m
index 59d02fc4..02659c55 100644
--- a/src/ios.m
+++ b/src/ios.m
@@ -161,7 +161,7 @@ API_AVAILABLE(ios(13.0))
161 161
162/*----------------------------------------------------------------------------------------------*/ 162/*----------------------------------------------------------------------------------------------*/
163 163
164@interface AppState : NSObject<UIDocumentPickerDelegate, UITextFieldDelegate> { 164@interface AppState : NSObject<UIDocumentPickerDelegate, UITextFieldDelegate, UITextViewDelegate> {
165 iString *fileBeingSaved; 165 iString *fileBeingSaved;
166 iString *pickFileCommand; 166 iString *pickFileCommand;
167 iSystemTextInput *sysCtrl; 167 iSystemTextInput *sysCtrl;
@@ -273,16 +273,21 @@ didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
273 SDL_Event ev = { .type = SDL_KEYDOWN }; 273 SDL_Event ev = { .type = SDL_KEYDOWN };
274 ev.key.keysym.sym = SDLK_RETURN; 274 ev.key.keysym.sym = SDLK_RETURN;
275 SDL_PushEvent(&ev); 275 SDL_PushEvent(&ev);
276 printf("Return pressed\n");
277 return NO; 276 return NO;
278} 277}
279 278
280- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { 279- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range
280replacementString:(NSString *)string {
281 iSystemTextInput *sysCtrl = [appState_ systemTextInput]; 281 iSystemTextInput *sysCtrl = [appState_ systemTextInput];
282 notifyChange_SystemTextInput_(sysCtrl); 282 notifyChange_SystemTextInput_(sysCtrl);
283 return YES; 283 return YES;
284} 284}
285 285
286- (void)textViewDidChange:(UITextView *)textView {
287 iSystemTextInput *sysCtrl = [appState_ systemTextInput];
288 notifyChange_SystemTextInput_(sysCtrl);
289}
290
286@end 291@end
287 292
288static void enableMouse_(iBool yes) { 293static void enableMouse_(iBool yes) {
@@ -538,6 +543,10 @@ void init_AVFAudioPlayer(iAVFAudioPlayer *d) {
538 543
539void deinit_AVFAudioPlayer(iAVFAudioPlayer *d) { 544void deinit_AVFAudioPlayer(iAVFAudioPlayer *d) {
540 setInput_AVFAudioPlayer(d, NULL, NULL); 545 setInput_AVFAudioPlayer(d, NULL, NULL);
546 if (d->player) {
547 CFBridgingRelease(d->player);
548 d->player = nil;
549 }
541} 550}
542 551
543static const char *cacheDir_ = "~/Library/Caches/Audio"; 552static const char *cacheDir_ = "~/Library/Caches/Audio";
@@ -638,70 +647,155 @@ iBool isPaused_AVFAudioPlayer(const iAVFAudioPlayer *d) {
638/*----------------------------------------------------------------------------------------------*/ 647/*----------------------------------------------------------------------------------------------*/
639 648
640struct Impl_SystemTextInput { 649struct Impl_SystemTextInput {
641 void *ctrl; 650 int flags;
651 void *field; /* single-line text field */
652 void *view; /* multi-line text view */
642 void (*textChangedFunc)(iSystemTextInput *, void *); 653 void (*textChangedFunc)(iSystemTextInput *, void *);
643 void *textChangedContext; 654 void *textChangedContext;
644}; 655};
645 656
646iDefineTypeConstructionArgs(SystemTextInput, (int flags), flags) 657iDefineTypeConstructionArgs(SystemTextInput, (iRect rect, int flags), rect, flags)
647 658
648#define REF_d_ctrl (__bridge UITextField *)d->ctrl 659#define REF_d_field (__bridge UITextField *)d->field
660#define REF_d_view (__bridge UITextView *)d->view
649 661
650void init_SystemTextInput(iSystemTextInput *d, int flags) { 662static CGRect convertToCGRect_(const iRect *rect) {
651 d->ctrl = (void *) CFBridgingRetain([[UITextField alloc] init]); 663 const iWindow *win = get_Window();
652 UITextField *field = REF_d_ctrl; 664 CGRect frame;
665 // TODO: Convert coordinates properly!
666 frame.origin.x = rect->pos.x / win->pixelRatio;
667 frame.origin.y = (rect->pos.y - gap_UI + 2) / win->pixelRatio;
668 frame.size.width = rect->size.x / win->pixelRatio;
669 frame.size.height = rect->size.y / win->pixelRatio;
670 return frame;
671}
672
673static UIColor *makeUIColor_(enum iColorId colorId) {
674 iColor color = get_Color(colorId);
675 return [UIColor colorWithRed:color.r / 255.0
676 green:color.g / 255.0
677 blue:color.b / 255.0
678 alpha:color.a / 255.0];
679}
680
681void init_SystemTextInput(iSystemTextInput *d, iRect rect, int flags) {
682 d->flags = flags;
683 d->field = NULL;
684 d->view = NULL;
685 CGRect frame = convertToCGRect_(&rect);
686 if (flags & multiLine_SystemTextInputFlags) {
687 d->view = (void *) CFBridgingRetain([[UITextView alloc] initWithFrame:frame textContainer:nil]);
688 [[viewController_(get_Window()) view] addSubview:REF_d_view];
689 }
690 else {
691 d->field = (void *) CFBridgingRetain([[UITextField alloc] initWithFrame:frame]);
692 [[viewController_(get_Window()) view] addSubview:REF_d_field];
693 }
694 UIControl<UITextInputTraits> *traits = (UIControl<UITextInputTraits> *) (d->view ? REF_d_view : REF_d_field);
653 // TODO: Use the right font: https://developer.apple.com/documentation/uikit/text_display_and_fonts/adding_a_custom_font_to_your_app?language=objc 695 // TODO: Use the right font: https://developer.apple.com/documentation/uikit/text_display_and_fonts/adding_a_custom_font_to_your_app?language=objc
654 [[viewController_(get_Window()) view] addSubview:REF_d_ctrl];
655 if (flags & returnGo_SystemTextInputFlags) { 696 if (flags & returnGo_SystemTextInputFlags) {
656 [field setReturnKeyType:UIReturnKeyGo]; 697 [traits setReturnKeyType:UIReturnKeyGo];
657 } 698 }
658 if (flags & returnSend_SystemTextInputFlags) { 699 if (flags & returnSend_SystemTextInputFlags) {
659 [field setReturnKeyType:UIReturnKeySend]; 700 [traits setReturnKeyType:UIReturnKeySend];
660 } 701 }
661 if (flags & disableAutocorrect_SystemTextInputFlag) { 702 if (flags & disableAutocorrect_SystemTextInputFlag) {
662 [field setAutocorrectionType:UITextAutocorrectionTypeNo]; 703 [traits setAutocorrectionType:UITextAutocorrectionTypeNo];
663 [field setAutocapitalizationType:UITextAutocapitalizationTypeNone]; 704 [traits setAutocapitalizationType:UITextAutocapitalizationTypeNone];
664 [field setSpellCheckingType:UITextSpellCheckingTypeNo]; 705 [traits setSpellCheckingType:UITextSpellCheckingTypeNo];
665 } 706 }
666 if (flags & alignRight_SystemTextInputFlag) { 707 if (flags & alignRight_SystemTextInputFlag) {
667 [field setTextAlignment:NSTextAlignmentRight]; 708 if (d->field) {
709 [REF_d_field setTextAlignment:NSTextAlignmentRight];
710 }
711 }
712 UIColor *textColor = makeUIColor_(uiInputTextFocused_ColorId);
713 UIColor *backgroundColor = makeUIColor_(uiInputBackgroundFocused_ColorId);
714 [appState_ setSystemTextInput:d];
715 if (d->field) {
716 [REF_d_field setTextColor:textColor];
717 [REF_d_field setDelegate:appState_];
718 [REF_d_field becomeFirstResponder];
719 }
720 else {
721 [REF_d_view setTextColor:textColor];
722 [REF_d_view setBackgroundColor:backgroundColor];
723 [REF_d_view setEditable:YES];
724// [REF_d_view setSelectable:YES];
725 [REF_d_view setDelegate:appState_];
726 [REF_d_view becomeFirstResponder];
668 } 727 }
669 [field setDelegate:appState_];
670 [field becomeFirstResponder];
671 d->textChangedFunc = NULL; 728 d->textChangedFunc = NULL;
672 d->textChangedContext = NULL; 729 d->textChangedContext = NULL;
673 [appState_ setSystemTextInput:d];
674} 730}
675 731
676void deinit_SystemTextInput(iSystemTextInput *d) { 732void deinit_SystemTextInput(iSystemTextInput *d) {
677 [appState_ setSystemTextInput:nil]; 733 [appState_ setSystemTextInput:nil];
678 [REF_d_ctrl removeFromSuperview]; 734 if (d->field) {
679 d->ctrl = nil; // TODO: Does this need to be released?? 735 [REF_d_field removeFromSuperview];
736 CFBridgingRelease(d->field);
737 d->field = nil;
738 }
739 if (d->view) {
740 [REF_d_view removeFromSuperview];
741 CFBridgingRelease(d->view);
742 d->view = nil;
743 }
680} 744}
681 745
682void setText_SystemTextInput(iSystemTextInput *d, const iString *text) { 746void setText_SystemTextInput(iSystemTextInput *d, const iString *text) {
683 [REF_d_ctrl setText:[NSString stringWithUTF8String:cstr_String(text)]]; 747 NSString *str = [NSString stringWithUTF8String:cstr_String(text)];
684 [REF_d_ctrl selectAll:nil]; 748 if (d->field) {
749 [REF_d_field setText:str];
750 if (d->flags & selectAll_SystemTextInputFlags) {
751 [REF_d_field selectAll:nil];
752 }
753 }
754 else {
755 [REF_d_view setText:str];
756 if (d->flags & selectAll_SystemTextInputFlags) {
757 [REF_d_view selectAll:nil];
758 }
759 }
760}
761
762int preferredHeight_SystemTextInput(const iSystemTextInput *d) {
763 if (d->view) {
764 CGRect usedRect = [[REF_d_view layoutManager] usedRectForTextContainer:[REF_d_view textContainer]];
765 return usedRect.size.height * get_Window()->pixelRatio;
766 }
767 return 0;
685} 768}
686 769
687void setFont_SystemTextInput(iSystemTextInput *d, int fontId) { 770void setFont_SystemTextInput(iSystemTextInput *d, int fontId) {
688 int height = lineHeight_Text(fontId); 771 int height = lineHeight_Text(fontId);
689 UIFont *font = [UIFont systemFontOfSize:0.65f * height / get_Window()->pixelRatio]; 772 UIFont *font = [UIFont systemFontOfSize:0.65f * height / get_Window()->pixelRatio];
690 [REF_d_ctrl setFont:font]; 773 if (d->field) {
774 [REF_d_field setFont:font];
775 }
776 if (d->view) {
777 [REF_d_view setFont:font];
778 }
691} 779}
692 780
693const iString *text_SystemTextInput(const iSystemTextInput *d) { 781const iString *text_SystemTextInput(const iSystemTextInput *d) {
694 return collectNewCStr_String([[REF_d_ctrl text] cStringUsingEncoding:NSUTF8StringEncoding]);; 782 if (d->field) {
783 return collectNewCStr_String([[REF_d_field text] cStringUsingEncoding:NSUTF8StringEncoding]);
784 }
785 if (d->view) {
786 return collectNewCStr_String([[REF_d_view text] cStringUsingEncoding:NSUTF8StringEncoding]);
787 }
788 return NULL;
695} 789}
696 790
697void setRect_SystemTextInput(iSystemTextInput *d, iRect rect) { 791void setRect_SystemTextInput(iSystemTextInput *d, iRect rect) {
698 const iWindow *win = get_Window(); 792 CGRect frame = convertToCGRect_(&rect);
699 CGRect frame; 793 if (d->field) {
700 frame.origin.x = rect.pos.x / win->pixelRatio; 794 [REF_d_field setFrame:frame];
701 frame.origin.y = (rect.pos.y - gap_UI + 2) / win->pixelRatio; 795 }
702 frame.size.width = rect.size.x / win->pixelRatio; 796 else {
703 frame.size.height = rect.size.y / win->pixelRatio; 797 [REF_d_view setFrame:frame];
704 [REF_d_ctrl setFrame:frame]; 798 }
705} 799}
706 800
707void setTextChangedFunc_SystemTextInput(iSystemTextInput *d, 801void setTextChangedFunc_SystemTextInput(iSystemTextInput *d,
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index 1c6b8d9e..ea7ed523 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -596,10 +596,19 @@ static size_t length_InputWidget_(const iInputWidget *d) {
596} 596}
597 597
598static int contentHeight_InputWidget_(const iInputWidget *d) { 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 }
599 return size_Range(&d->visWrapLines) * lineHeight_Text(d->font); 605 return size_Range(&d->visWrapLines) * lineHeight_Text(d->font);
600} 606}
601 607
602static void updateTextInputRect_InputWidget_(const iInputWidget *d) { 608static void updateTextInputRect_InputWidget_(const iInputWidget *d) {
609 if (d->sysCtrl) {
610 setRect_SystemTextInput(d->sysCtrl, contentBounds_InputWidget_(d));
611 }
603#if !defined (iPlatformAppleMobile) 612#if !defined (iPlatformAppleMobile)
604 const iRect bounds = bounds_Widget(constAs_Widget(d)); 613 const iRect bounds = bounds_Widget(constAs_Widget(d));
605 SDL_SetTextInputRect(&(SDL_Rect){ bounds.pos.x, bounds.pos.y, bounds.size.x, bounds.size.y }); 614 SDL_SetTextInputRect(&(SDL_Rect){ bounds.pos.x, bounds.pos.y, bounds.size.x, bounds.size.y });
@@ -1036,6 +1045,7 @@ void systemInputChanged_InputWidget_(iSystemTextInput *sysCtrl, void *widget) {
1036 iInputWidget *d = widget; 1045 iInputWidget *d = widget;
1037 splitToLines_(text_SystemTextInput(sysCtrl), &d->lines); 1046 splitToLines_(text_SystemTextInput(sysCtrl), &d->lines);
1038 contentsWereChanged_InputWidget_(d); 1047 contentsWereChanged_InputWidget_(d);
1048 updateMetrics_InputWidget_(d);
1039} 1049}
1040#endif 1050#endif
1041 1051
@@ -1050,14 +1060,16 @@ void begin_InputWidget(iInputWidget *d) {
1050 setFlags_Widget(w, selected_WidgetFlag, iTrue); 1060 setFlags_Widget(w, selected_WidgetFlag, iTrue);
1051 mergeLines_(&d->lines, &d->oldText); 1061 mergeLines_(&d->lines, &d->oldText);
1052#if defined (LAGRANGE_ENABLE_SYSTEM_INPUT) 1062#if defined (LAGRANGE_ENABLE_SYSTEM_INPUT)
1053 d->sysCtrl = new_SystemTextInput((d->inFlags & isUrl_InputWidgetFlag ? disableAutocorrect_SystemTextInputFlag : 0) | 1063 d->sysCtrl = new_SystemTextInput(contentBounds_InputWidget_(d),
1064 (d->maxWrapLines > 1 ? multiLine_SystemTextInputFlags : 0) |
1065 (d->inFlags & isUrl_InputWidgetFlag ? disableAutocorrect_SystemTextInputFlag : 0) |
1054 (!cmp_String(id_Widget(w), "url") ? returnGo_SystemTextInputFlags : 0) | 1066 (!cmp_String(id_Widget(w), "url") ? returnGo_SystemTextInputFlags : 0) |
1055 (flags_Widget(w) & alignRight_WidgetFlag ? alignRight_SystemTextInputFlag : 0)); 1067 (flags_Widget(w) & alignRight_WidgetFlag ? alignRight_SystemTextInputFlag : 0));
1056 setFont_SystemTextInput(d->sysCtrl, d->font); 1068 setFont_SystemTextInput(d->sysCtrl, d->font);
1057 setRect_SystemTextInput(d->sysCtrl, contentBounds_InputWidget_(d));
1058 setText_SystemTextInput(d->sysCtrl, &d->oldText); 1069 setText_SystemTextInput(d->sysCtrl, &d->oldText);
1059 setTextChangedFunc_SystemTextInput(d->sysCtrl, systemInputChanged_InputWidget_, d); 1070 setTextChangedFunc_SystemTextInput(d->sysCtrl, systemInputChanged_InputWidget_, d);
1060 iConnect(Root, w->root, visualOffsetsChanged, d, updateAfterVisualOffsetChange_InputWidget_); 1071 iConnect(Root, w->root, visualOffsetsChanged, d, updateAfterVisualOffsetChange_InputWidget_);
1072 updateMetrics_InputWidget_(d);
1061 return; 1073 return;
1062#endif 1074#endif
1063 if (d->mode == overwrite_InputMode) { 1075 if (d->mode == overwrite_InputMode) {