summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-12-04 17:55:38 +0200
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-12-04 17:55:38 +0200
commit368eddea42200ec95faa4f4767a72b2b3cf7bdff (patch)
tree5de42312f4ff697d7a141e926be41731e957d043
parentf8c0c817c2ce1919f71ef333439c9f4742fc6a12 (diff)
iOS: Setting up a system-provided text input control
-rw-r--r--src/ios.h20
-rw-r--r--src/ios.m111
-rw-r--r--src/ui/inputwidget.c50
3 files changed, 176 insertions, 5 deletions
diff --git a/src/ios.h b/src/ios.h
index 85177409..2908d833 100644
--- a/src/ios.h
+++ b/src/ios.h
@@ -61,3 +61,23 @@ iBool isPaused_AVFAudioPlayer (const iAVFAudioPlayer *);
61 61
62void clearNowPlayingInfo_iOS (void); 62void clearNowPlayingInfo_iOS (void);
63void updateNowPlayingInfo_iOS (void); 63void updateNowPlayingInfo_iOS (void);
64
65/*----------------------------------------------------------------------------------------------*/
66
67enum iSystemTextInputFlags {
68 multiLine_SystemTextInputFlags = iBit(1),
69 returnGo_SystemTextInputFlags = iBit(2),
70 returnSend_SystemTextInputFlags = iBit(3),
71 disableAutocorrect_SystemTextInputFlag = iBit(4),
72 alignRight_SystemTextInputFlag = iBit(5),
73};
74
75iDeclareType(SystemTextInput)
76iDeclareTypeConstructionArgs(SystemTextInput, int flags)
77
78void setRect_SystemTextInput (iSystemTextInput *, iRect rect);
79void setText_SystemTextInput (iSystemTextInput *, const iString *text);
80void setFont_SystemTextInput (iSystemTextInput *, int fontId);
81void setTextChangedFunc_SystemTextInput (iSystemTextInput *, void (*textChangedFunc)(iSystemTextInput *, void *), void *);
82
83const iString * text_SystemTextInput (const iSystemTextInput *);
diff --git a/src/ios.m b/src/ios.m
index b46fb8dc..be1e644f 100644
--- a/src/ios.m
+++ b/src/ios.m
@@ -60,6 +60,8 @@ static UIViewController *viewController_(iWindow *window) {
60 return NULL; 60 return NULL;
61} 61}
62 62
63static void notifyChange_SystemTextInput_(iSystemTextInput *);
64
63/*----------------------------------------------------------------------------------------------*/ 65/*----------------------------------------------------------------------------------------------*/
64 66
65API_AVAILABLE(ios(13.0)) 67API_AVAILABLE(ios(13.0))
@@ -159,9 +161,10 @@ API_AVAILABLE(ios(13.0))
159 161
160/*----------------------------------------------------------------------------------------------*/ 162/*----------------------------------------------------------------------------------------------*/
161 163
162@interface AppState : NSObject<UIDocumentPickerDelegate> { 164@interface AppState : NSObject<UIDocumentPickerDelegate, UITextFieldDelegate> {
163 iString *fileBeingSaved; 165 iString *fileBeingSaved;
164 iString *pickFileCommand; 166 iString *pickFileCommand;
167 iSystemTextInput *sysCtrl;
165} 168}
166@property (nonatomic, assign) BOOL isHapticsAvailable; 169@property (nonatomic, assign) BOOL isHapticsAvailable;
167@property (nonatomic, strong) NSObject *haptic; 170@property (nonatomic, strong) NSObject *haptic;
@@ -175,9 +178,18 @@ static AppState *appState_;
175 self = [super init]; 178 self = [super init];
176 fileBeingSaved = NULL; 179 fileBeingSaved = NULL;
177 pickFileCommand = NULL; 180 pickFileCommand = NULL;
181 sysCtrl = NULL;
178 return self; 182 return self;
179} 183}
180 184
185-(void)setSystemTextInput:(iSystemTextInput *)sys {
186 sysCtrl = sys;
187}
188
189-(iSystemTextInput *)systemTextInput {
190 return sysCtrl;
191}
192
181-(void)setPickFileCommand:(const char *)command { 193-(void)setPickFileCommand:(const char *)command {
182 if (!pickFileCommand) { 194 if (!pickFileCommand) {
183 pickFileCommand = new_String(); 195 pickFileCommand = new_String();
@@ -256,6 +268,21 @@ didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
256-(void)keyboardOffScreen:(NSNotification *)notification { 268-(void)keyboardOffScreen:(NSNotification *)notification {
257 setKeyboardHeight_MainWindow(get_MainWindow(), 0); 269 setKeyboardHeight_MainWindow(get_MainWindow(), 0);
258} 270}
271
272- (BOOL)textFieldShouldReturn:(UITextField *)textField {
273 SDL_Event ev = { .type = SDL_KEYDOWN };
274 ev.key.keysym.sym = SDLK_RETURN;
275 SDL_PushEvent(&ev);
276 printf("Return pressed\n");
277 return NO;
278}
279
280- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
281 iSystemTextInput *sysCtrl = [appState_ systemTextInput];
282 notifyChange_SystemTextInput_(sysCtrl);
283 return YES;
284}
285
259@end 286@end
260 287
261static void enableMouse_(iBool yes) { 288static void enableMouse_(iBool yes) {
@@ -607,3 +634,85 @@ iBool isStarted_AVFAudioPlayer(const iAVFAudioPlayer *d) {
607iBool isPaused_AVFAudioPlayer(const iAVFAudioPlayer *d) { 634iBool isPaused_AVFAudioPlayer(const iAVFAudioPlayer *d) {
608 return d->state == paused_AVFAudioPlayerState; 635 return d->state == paused_AVFAudioPlayerState;
609} 636}
637
638/*----------------------------------------------------------------------------------------------*/
639
640struct Impl_SystemTextInput {
641 void *ctrl;
642 void (*textChangedFunc)(iSystemTextInput *, void *);
643 void *textChangedContext;
644};
645
646iDefineTypeConstructionArgs(SystemTextInput, (int flags), flags)
647
648#define REF_d_ctrl (__bridge UITextField *)d->ctrl
649
650void init_SystemTextInput(iSystemTextInput *d, int flags) {
651 d->ctrl = (void *) CFBridgingRetain([[UITextField alloc] init]);
652 UITextField *field = REF_d_ctrl;
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
654 [[viewController_(get_Window()) view] addSubview:REF_d_ctrl];
655 if (flags & returnGo_SystemTextInputFlags) {
656 [field setReturnKeyType:UIReturnKeyGo];
657 }
658 if (flags & returnSend_SystemTextInputFlags) {
659 [field setReturnKeyType:UIReturnKeySend];
660 }
661 if (flags & disableAutocorrect_SystemTextInputFlag) {
662 [field setAutocorrectionType:UITextAutocorrectionTypeNo];
663 [field setAutocapitalizationType:UITextAutocapitalizationTypeNone];
664 [field setSpellCheckingType:UITextSpellCheckingTypeNo];
665 }
666 if (flags & alignRight_WidgetFlag) {
667 [field setTextAlignment:NSTextAlignmentRight];
668 }
669 [field setDelegate:appState_];
670 [field becomeFirstResponder];
671 d->textChangedFunc = NULL;
672 d->textChangedContext = NULL;
673 [appState_ setSystemTextInput:d];
674}
675
676void deinit_SystemTextInput(iSystemTextInput *d) {
677 [appState_ setSystemTextInput:nil];
678 [REF_d_ctrl removeFromSuperview];
679 d->ctrl = nil; // TODO: Does this need to be released??
680}
681
682void setText_SystemTextInput(iSystemTextInput *d, const iString *text) {
683 [REF_d_ctrl setText:[NSString stringWithUTF8String:cstr_String(text)]];
684 [REF_d_ctrl selectAll:nil];
685}
686
687void setFont_SystemTextInput(iSystemTextInput *d, int fontId) {
688 int height = lineHeight_Text(fontId);
689 UIFont *font = [UIFont systemFontOfSize:0.65f * height / get_Window()->pixelRatio];
690 [REF_d_ctrl setFont:font];
691}
692
693const iString *text_SystemTextInput(const iSystemTextInput *d) {
694 return collectNewCStr_String([[REF_d_ctrl text] cStringUsingEncoding:NSUTF8StringEncoding]);;
695}
696
697void setRect_SystemTextInput(iSystemTextInput *d, iRect rect) {
698 const iWindow *win = get_Window();
699 CGRect frame;
700 frame.origin.x = rect.pos.x / win->pixelRatio;
701 frame.origin.y = (rect.pos.y - gap_UI + 2) / win->pixelRatio;
702 frame.size.width = rect.size.x / win->pixelRatio;
703 frame.size.height = rect.size.y / win->pixelRatio;
704 [REF_d_ctrl setFrame:frame];
705}
706
707void setTextChangedFunc_SystemTextInput(iSystemTextInput *d,
708 void (*textChangedFunc)(iSystemTextInput *, void *),
709 void *context) {
710 d->textChangedFunc = textChangedFunc;
711 d->textChangedContext = context;
712}
713
714static void notifyChange_SystemTextInput_(iSystemTextInput *d) {
715 if (d && d->textChangedFunc) {
716 d->textChangedFunc(d, d->textChangedContext);
717 }
718}
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index bd9b63b5..bb3851df 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -40,6 +40,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
40# include "macos.h" 40# include "macos.h"
41#endif 41#endif
42 42
43#if defined (iPlatformAppleMobile)
44# include "ios.h"
45# define LAGRANGE_ENABLE_SYSTEM_INPUT 1
46#endif
47
43static const int refreshInterval_InputWidget_ = 512; 48static const int refreshInterval_InputWidget_ = 512;
44static const size_t maxUndo_InputWidget_ = 64; 49static const size_t maxUndo_InputWidget_ = 64;
45static const int unlimitedWidth_InputWidget_ = 1000000; /* TODO: WrapText disables some functionality if maxWidth==0 */ 50static const int unlimitedWidth_InputWidget_ = 1000000; /* TODO: WrapText disables some functionality if maxWidth==0 */
@@ -233,6 +238,7 @@ struct Impl_InputWidget {
233 void * validatorContext; 238 void * validatorContext;
234 iString * backupPath; 239 iString * backupPath;
235 int backupTimer; 240 int backupTimer;
241 iSystemTextInput *sysCtrl;
236}; 242};
237 243
238iDefineObjectConstructionArgs(InputWidget, (size_t maxLen), maxLen) 244iDefineObjectConstructionArgs(InputWidget, (size_t maxLen), maxLen)
@@ -721,10 +727,12 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) {
721 d->buffered = NULL; 727 d->buffered = NULL;
722 d->backupPath = NULL; 728 d->backupPath = NULL;
723 d->backupTimer = 0; 729 d->backupTimer = 0;
730 d->sysCtrl = NULL;
724 updateMetrics_InputWidget_(d); 731 updateMetrics_InputWidget_(d);
725} 732}
726 733
727void deinit_InputWidget(iInputWidget *d) { 734void deinit_InputWidget(iInputWidget *d) {
735 delete_SystemTextInput(d->sysCtrl);
728 if (d->backupTimer) { 736 if (d->backupTimer) {
729 SDL_RemoveTimer(d->backupTimer); 737 SDL_RemoveTimer(d->backupTimer);
730 } 738 }
@@ -953,6 +961,9 @@ void setText_InputWidget(iInputWidget *d, const iString *text) {
953 iString *nfcText = collect_String(copy_String(text)); 961 iString *nfcText = collect_String(copy_String(text));
954 normalize_String(nfcText); 962 normalize_String(nfcText);
955 splitToLines_(nfcText, &d->lines); 963 splitToLines_(nfcText, &d->lines);
964 if (d->sysCtrl) {
965 setText_SystemTextInput(d->sysCtrl, nfcText);
966 }
956 iAssert(!isEmpty_Array(&d->lines)); 967 iAssert(!isEmpty_Array(&d->lines));
957 iForEach(Array, i, &d->lines) { 968 iForEach(Array, i, &d->lines) {
958 updateLine_InputWidget_(d, i.value); /* count number of visible lines */ 969 updateLine_InputWidget_(d, i.value); /* count number of visible lines */
@@ -1008,6 +1019,16 @@ iLocalDef iBool isEditing_InputWidget_(const iInputWidget *d) {
1008 return (flags_Widget(constAs_Widget(d)) & selected_WidgetFlag) != 0; 1019 return (flags_Widget(constAs_Widget(d)) & selected_WidgetFlag) != 0;
1009} 1020}
1010 1021
1022static void contentsWereChanged_InputWidget_(iInputWidget *);
1023
1024#if defined (LAGRANGE_ENABLE_SYSTEM_INPUT)
1025void systemInputChanged_InputWidget_(iSystemTextInput *sysCtrl, void *widget) {
1026 iInputWidget *d = widget;
1027 splitToLines_(text_SystemTextInput(sysCtrl), &d->lines);
1028 contentsWereChanged_InputWidget_(d);
1029}
1030#endif
1031
1011void begin_InputWidget(iInputWidget *d) { 1032void begin_InputWidget(iInputWidget *d) {
1012 iWidget *w = as_Widget(d); 1033 iWidget *w = as_Widget(d);
1013 if (isEditing_InputWidget_(d)) { 1034 if (isEditing_InputWidget_(d)) {
@@ -1016,7 +1037,18 @@ void begin_InputWidget(iInputWidget *d) {
1016 } 1037 }
1017 invalidateBuffered_InputWidget_(d); 1038 invalidateBuffered_InputWidget_(d);
1018 setFlags_Widget(w, hidden_WidgetFlag | disabled_WidgetFlag, iFalse); 1039 setFlags_Widget(w, hidden_WidgetFlag | disabled_WidgetFlag, iFalse);
1040 setFlags_Widget(w, selected_WidgetFlag, iTrue);
1019 mergeLines_(&d->lines, &d->oldText); 1041 mergeLines_(&d->lines, &d->oldText);
1042#if defined (LAGRANGE_ENABLE_SYSTEM_INPUT)
1043 d->sysCtrl = new_SystemTextInput((d->inFlags & isUrl_InputWidgetFlag ? disableAutocorrect_SystemTextInputFlag : 0) |
1044 (!cmp_String(id_Widget(w), "url") ? returnGo_SystemTextInputFlags : 0) );
1045// (flags_Widget(w) & alignRight_WidgetFlag ? alignRight_SystemTextInputFlag : 0));
1046 setFont_SystemTextInput(d->sysCtrl, d->font);
1047 setRect_SystemTextInput(d->sysCtrl, contentBounds_InputWidget_(d));
1048 setText_SystemTextInput(d->sysCtrl, &d->oldText);
1049 setTextChangedFunc_SystemTextInput(d->sysCtrl, systemInputChanged_InputWidget_, d);
1050 return;
1051#endif
1020 if (d->mode == overwrite_InputMode) { 1052 if (d->mode == overwrite_InputMode) {
1021 d->cursor = zero_I2(); 1053 d->cursor = zero_I2();
1022 } 1054 }
@@ -1025,7 +1057,6 @@ void begin_InputWidget(iInputWidget *d) {
1025 d->cursor.x = iMin(d->cursor.x, cursorLine_InputWidget_(d)->range.end); 1057 d->cursor.x = iMin(d->cursor.x, cursorLine_InputWidget_(d)->range.end);
1026 } 1058 }
1027 SDL_StartTextInput(); 1059 SDL_StartTextInput();
1028 setFlags_Widget(w, selected_WidgetFlag, iTrue);
1029 showCursor_InputWidget_(d); 1060 showCursor_InputWidget_(d);
1030 refresh_Widget(w); 1061 refresh_Widget(w);
1031 startOrStopCursorTimer_InputWidget_(d, iTrue); 1062 startOrStopCursorTimer_InputWidget_(d, iTrue);
@@ -1048,15 +1079,22 @@ void end_InputWidget(iInputWidget *d, iBool accept) {
1048 /* Was not active. */ 1079 /* Was not active. */
1049 return; 1080 return;
1050 } 1081 }
1051 enableEditorKeysInMenus_(iTrue); 1082 if (d->sysCtrl) {
1052 if (!accept) { 1083 if (accept) {
1084 splitToLines_(text_SystemTextInput(d->sysCtrl), &d->lines);
1085 }
1086 delete_SystemTextInput(d->sysCtrl);
1087 d->sysCtrl = NULL;
1088 }
1089 else if (!accept) {
1053 /* Overwrite the edited lines. */ 1090 /* Overwrite the edited lines. */
1054 splitToLines_(&d->oldText, &d->lines); 1091 splitToLines_(&d->oldText, &d->lines);
1092 SDL_StopTextInput();
1055 } 1093 }
1094 enableEditorKeysInMenus_(iTrue);
1056 d->inFlags |= needUpdateBuffer_InputWidgetFlag; 1095 d->inFlags |= needUpdateBuffer_InputWidgetFlag;
1057 d->inFlags &= ~isMarking_InputWidgetFlag; 1096 d->inFlags &= ~isMarking_InputWidgetFlag;
1058 startOrStopCursorTimer_InputWidget_(d, iFalse); 1097 startOrStopCursorTimer_InputWidget_(d, iFalse);
1059 SDL_StopTextInput();
1060 setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag | touchDrag_WidgetFlag, iFalse); 1098 setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag | touchDrag_WidgetFlag, iFalse);
1061 const char *id = cstr_String(id_Widget(as_Widget(d))); 1099 const char *id = cstr_String(id_Widget(as_Widget(d)));
1062 if (!*id) id = "_"; 1100 if (!*id) id = "_";
@@ -2324,6 +2362,10 @@ static void draw_InputWidget_(const iInputWidget *d) {
2324 isFocused ? gap_UI / 4 : 1, 2362 isFocused ? gap_UI / 4 : 1,
2325 isFocused ? uiInputFrameFocused_ColorId 2363 isFocused ? uiInputFrameFocused_ColorId
2326 : isHover ? uiInputFrameHover_ColorId : uiInputFrame_ColorId); 2364 : isHover ? uiInputFrameHover_ColorId : uiInputFrame_ColorId);
2365 if (d->sysCtrl) {
2366 drawChildren_Widget(w);
2367 return;
2368 }
2327 setClip_Paint(&p, adjusted_Rect(bounds, init_I2(d->leftPadding, 0), 2369 setClip_Paint(&p, adjusted_Rect(bounds, init_I2(d->leftPadding, 0),
2328 init_I2(-d->rightPadding, w->flags & extraPadding_WidgetFlag ? -gap_UI / 2 : 0))); 2370 init_I2(-d->rightPadding, w->flags & extraPadding_WidgetFlag ? -gap_UI / 2 : 0)));
2329 const iRect contentBounds = contentBounds_InputWidget_(d); 2371 const iRect contentBounds = contentBounds_InputWidget_(d);