From b8656d5a2e0f540d47d2dc5ccc269f709dd0b923 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Sun, 5 Dec 2021 07:54:15 +0200 Subject: iOS: Multiline text input using UITextView --- src/ios.h | 14 +++-- src/ios.m | 158 ++++++++++++++++++++++++++++++++++++++++----------- src/ui/inputwidget.c | 16 +++++- 3 files changed, 148 insertions(+), 40 deletions(-) (limited to 'src') 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); /*----------------------------------------------------------------------------------------------*/ enum iSystemTextInputFlags { - multiLine_SystemTextInputFlags = iBit(1), - returnGo_SystemTextInputFlags = iBit(2), - returnSend_SystemTextInputFlags = iBit(3), - disableAutocorrect_SystemTextInputFlag = iBit(4), - alignRight_SystemTextInputFlag = iBit(5), + selectAll_SystemTextInputFlags = iBit(1), + multiLine_SystemTextInputFlags = iBit(2), + returnGo_SystemTextInputFlags = iBit(3), + returnSend_SystemTextInputFlags = iBit(4), + disableAutocorrect_SystemTextInputFlag = iBit(5), + alignRight_SystemTextInputFlag = iBit(6), }; iDeclareType(SystemTextInput) -iDeclareTypeConstructionArgs(SystemTextInput, int flags) +iDeclareTypeConstructionArgs(SystemTextInput, iRect rect, int flags) void setRect_SystemTextInput (iSystemTextInput *, iRect rect); void setText_SystemTextInput (iSystemTextInput *, const iString *text); @@ -81,3 +82,4 @@ void setFont_SystemTextInput (iSystemTextInput *, int fontId); void setTextChangedFunc_SystemTextInput (iSystemTextInput *, void (*textChangedFunc)(iSystemTextInput *, void *), void *); const iString * text_SystemTextInput (const iSystemTextInput *); +int 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)) /*----------------------------------------------------------------------------------------------*/ -@interface AppState : NSObject { +@interface AppState : NSObject { iString *fileBeingSaved; iString *pickFileCommand; iSystemTextInput *sysCtrl; @@ -273,16 +273,21 @@ didPickDocumentsAtURLs:(NSArray *)urls { SDL_Event ev = { .type = SDL_KEYDOWN }; ev.key.keysym.sym = SDLK_RETURN; SDL_PushEvent(&ev); - printf("Return pressed\n"); return NO; } -- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { +- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range +replacementString:(NSString *)string { iSystemTextInput *sysCtrl = [appState_ systemTextInput]; notifyChange_SystemTextInput_(sysCtrl); return YES; } +- (void)textViewDidChange:(UITextView *)textView { + iSystemTextInput *sysCtrl = [appState_ systemTextInput]; + notifyChange_SystemTextInput_(sysCtrl); +} + @end static void enableMouse_(iBool yes) { @@ -538,6 +543,10 @@ void init_AVFAudioPlayer(iAVFAudioPlayer *d) { void deinit_AVFAudioPlayer(iAVFAudioPlayer *d) { setInput_AVFAudioPlayer(d, NULL, NULL); + if (d->player) { + CFBridgingRelease(d->player); + d->player = nil; + } } static const char *cacheDir_ = "~/Library/Caches/Audio"; @@ -638,70 +647,155 @@ iBool isPaused_AVFAudioPlayer(const iAVFAudioPlayer *d) { /*----------------------------------------------------------------------------------------------*/ struct Impl_SystemTextInput { - void *ctrl; + int flags; + void *field; /* single-line text field */ + void *view; /* multi-line text view */ void (*textChangedFunc)(iSystemTextInput *, void *); void *textChangedContext; }; -iDefineTypeConstructionArgs(SystemTextInput, (int flags), flags) +iDefineTypeConstructionArgs(SystemTextInput, (iRect rect, int flags), rect, flags) -#define REF_d_ctrl (__bridge UITextField *)d->ctrl +#define REF_d_field (__bridge UITextField *)d->field +#define REF_d_view (__bridge UITextView *)d->view -void init_SystemTextInput(iSystemTextInput *d, int flags) { - d->ctrl = (void *) CFBridgingRetain([[UITextField alloc] init]); - UITextField *field = REF_d_ctrl; +static CGRect convertToCGRect_(const iRect *rect) { + const iWindow *win = get_Window(); + CGRect frame; + // TODO: Convert coordinates properly! + frame.origin.x = rect->pos.x / win->pixelRatio; + frame.origin.y = (rect->pos.y - gap_UI + 2) / win->pixelRatio; + frame.size.width = rect->size.x / win->pixelRatio; + frame.size.height = rect->size.y / win->pixelRatio; + return frame; +} + +static UIColor *makeUIColor_(enum iColorId colorId) { + iColor color = get_Color(colorId); + return [UIColor colorWithRed:color.r / 255.0 + green:color.g / 255.0 + blue:color.b / 255.0 + alpha:color.a / 255.0]; +} + +void init_SystemTextInput(iSystemTextInput *d, iRect rect, int flags) { + d->flags = flags; + d->field = NULL; + d->view = NULL; + CGRect frame = convertToCGRect_(&rect); + if (flags & multiLine_SystemTextInputFlags) { + d->view = (void *) CFBridgingRetain([[UITextView alloc] initWithFrame:frame textContainer:nil]); + [[viewController_(get_Window()) view] addSubview:REF_d_view]; + } + else { + d->field = (void *) CFBridgingRetain([[UITextField alloc] initWithFrame:frame]); + [[viewController_(get_Window()) view] addSubview:REF_d_field]; + } + UIControl *traits = (UIControl *) (d->view ? REF_d_view : REF_d_field); // TODO: Use the right font: https://developer.apple.com/documentation/uikit/text_display_and_fonts/adding_a_custom_font_to_your_app?language=objc - [[viewController_(get_Window()) view] addSubview:REF_d_ctrl]; if (flags & returnGo_SystemTextInputFlags) { - [field setReturnKeyType:UIReturnKeyGo]; + [traits setReturnKeyType:UIReturnKeyGo]; } if (flags & returnSend_SystemTextInputFlags) { - [field setReturnKeyType:UIReturnKeySend]; + [traits setReturnKeyType:UIReturnKeySend]; } if (flags & disableAutocorrect_SystemTextInputFlag) { - [field setAutocorrectionType:UITextAutocorrectionTypeNo]; - [field setAutocapitalizationType:UITextAutocapitalizationTypeNone]; - [field setSpellCheckingType:UITextSpellCheckingTypeNo]; + [traits setAutocorrectionType:UITextAutocorrectionTypeNo]; + [traits setAutocapitalizationType:UITextAutocapitalizationTypeNone]; + [traits setSpellCheckingType:UITextSpellCheckingTypeNo]; } if (flags & alignRight_SystemTextInputFlag) { - [field setTextAlignment:NSTextAlignmentRight]; + if (d->field) { + [REF_d_field setTextAlignment:NSTextAlignmentRight]; + } + } + UIColor *textColor = makeUIColor_(uiInputTextFocused_ColorId); + UIColor *backgroundColor = makeUIColor_(uiInputBackgroundFocused_ColorId); + [appState_ setSystemTextInput:d]; + if (d->field) { + [REF_d_field setTextColor:textColor]; + [REF_d_field setDelegate:appState_]; + [REF_d_field becomeFirstResponder]; + } + else { + [REF_d_view setTextColor:textColor]; + [REF_d_view setBackgroundColor:backgroundColor]; + [REF_d_view setEditable:YES]; +// [REF_d_view setSelectable:YES]; + [REF_d_view setDelegate:appState_]; + [REF_d_view becomeFirstResponder]; } - [field setDelegate:appState_]; - [field becomeFirstResponder]; d->textChangedFunc = NULL; d->textChangedContext = NULL; - [appState_ setSystemTextInput:d]; } void deinit_SystemTextInput(iSystemTextInput *d) { [appState_ setSystemTextInput:nil]; - [REF_d_ctrl removeFromSuperview]; - d->ctrl = nil; // TODO: Does this need to be released?? + if (d->field) { + [REF_d_field removeFromSuperview]; + CFBridgingRelease(d->field); + d->field = nil; + } + if (d->view) { + [REF_d_view removeFromSuperview]; + CFBridgingRelease(d->view); + d->view = nil; + } } void setText_SystemTextInput(iSystemTextInput *d, const iString *text) { - [REF_d_ctrl setText:[NSString stringWithUTF8String:cstr_String(text)]]; - [REF_d_ctrl selectAll:nil]; + NSString *str = [NSString stringWithUTF8String:cstr_String(text)]; + if (d->field) { + [REF_d_field setText:str]; + if (d->flags & selectAll_SystemTextInputFlags) { + [REF_d_field selectAll:nil]; + } + } + else { + [REF_d_view setText:str]; + if (d->flags & selectAll_SystemTextInputFlags) { + [REF_d_view selectAll:nil]; + } + } +} + +int preferredHeight_SystemTextInput(const iSystemTextInput *d) { + if (d->view) { + CGRect usedRect = [[REF_d_view layoutManager] usedRectForTextContainer:[REF_d_view textContainer]]; + return usedRect.size.height * get_Window()->pixelRatio; + } + return 0; } void setFont_SystemTextInput(iSystemTextInput *d, int fontId) { int height = lineHeight_Text(fontId); UIFont *font = [UIFont systemFontOfSize:0.65f * height / get_Window()->pixelRatio]; - [REF_d_ctrl setFont:font]; + if (d->field) { + [REF_d_field setFont:font]; + } + if (d->view) { + [REF_d_view setFont:font]; + } } const iString *text_SystemTextInput(const iSystemTextInput *d) { - return collectNewCStr_String([[REF_d_ctrl text] cStringUsingEncoding:NSUTF8StringEncoding]);; + if (d->field) { + return collectNewCStr_String([[REF_d_field text] cStringUsingEncoding:NSUTF8StringEncoding]); + } + if (d->view) { + return collectNewCStr_String([[REF_d_view text] cStringUsingEncoding:NSUTF8StringEncoding]); + } + return NULL; } void setRect_SystemTextInput(iSystemTextInput *d, iRect rect) { - const iWindow *win = get_Window(); - CGRect frame; - frame.origin.x = rect.pos.x / win->pixelRatio; - frame.origin.y = (rect.pos.y - gap_UI + 2) / win->pixelRatio; - frame.size.width = rect.size.x / win->pixelRatio; - frame.size.height = rect.size.y / win->pixelRatio; - [REF_d_ctrl setFrame:frame]; + CGRect frame = convertToCGRect_(&rect); + if (d->field) { + [REF_d_field setFrame:frame]; + } + else { + [REF_d_view setFrame:frame]; + } } void 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) { } static int contentHeight_InputWidget_(const iInputWidget *d) { + if (d->sysCtrl) { + const int preferred = preferredHeight_SystemTextInput(d->sysCtrl); + return iClamp(preferred, + d->minWrapLines * lineHeight_Text(d->font), + d->maxWrapLines * lineHeight_Text(d->font)); + } return size_Range(&d->visWrapLines) * lineHeight_Text(d->font); } static void updateTextInputRect_InputWidget_(const iInputWidget *d) { + if (d->sysCtrl) { + setRect_SystemTextInput(d->sysCtrl, contentBounds_InputWidget_(d)); + } #if !defined (iPlatformAppleMobile) const iRect bounds = bounds_Widget(constAs_Widget(d)); 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) { iInputWidget *d = widget; splitToLines_(text_SystemTextInput(sysCtrl), &d->lines); contentsWereChanged_InputWidget_(d); + updateMetrics_InputWidget_(d); } #endif @@ -1050,14 +1060,16 @@ void begin_InputWidget(iInputWidget *d) { setFlags_Widget(w, selected_WidgetFlag, iTrue); mergeLines_(&d->lines, &d->oldText); #if defined (LAGRANGE_ENABLE_SYSTEM_INPUT) - d->sysCtrl = new_SystemTextInput((d->inFlags & isUrl_InputWidgetFlag ? disableAutocorrect_SystemTextInputFlag : 0) | + d->sysCtrl = new_SystemTextInput(contentBounds_InputWidget_(d), + (d->maxWrapLines > 1 ? multiLine_SystemTextInputFlags : 0) | + (d->inFlags & isUrl_InputWidgetFlag ? disableAutocorrect_SystemTextInputFlag : 0) | (!cmp_String(id_Widget(w), "url") ? returnGo_SystemTextInputFlags : 0) | (flags_Widget(w) & alignRight_WidgetFlag ? alignRight_SystemTextInputFlag : 0)); setFont_SystemTextInput(d->sysCtrl, d->font); - setRect_SystemTextInput(d->sysCtrl, contentBounds_InputWidget_(d)); setText_SystemTextInput(d->sysCtrl, &d->oldText); setTextChangedFunc_SystemTextInput(d->sysCtrl, systemInputChanged_InputWidget_, d); iConnect(Root, w->root, visualOffsetsChanged, d, updateAfterVisualOffsetChange_InputWidget_); + updateMetrics_InputWidget_(d); return; #endif if (d->mode == overwrite_InputMode) { -- cgit v1.2.3