summaryrefslogtreecommitdiff
path: root/src/ios.m
diff options
context:
space:
mode:
Diffstat (limited to 'src/ios.m')
-rw-r--r--src/ios.m319
1 files changed, 318 insertions, 1 deletions
diff --git a/src/ios.m b/src/ios.m
index b46fb8dc..82596ffd 100644
--- a/src/ios.m
+++ b/src/ios.m
@@ -25,6 +25,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
25#include "audio/player.h" 25#include "audio/player.h"
26#include "ui/command.h" 26#include "ui/command.h"
27#include "ui/window.h" 27#include "ui/window.h"
28#include "ui/touch.h"
28 29
29#include <the_Foundation/file.h> 30#include <the_Foundation/file.h>
30#include <the_Foundation/fileinfo.h> 31#include <the_Foundation/fileinfo.h>
@@ -60,6 +61,9 @@ static UIViewController *viewController_(iWindow *window) {
60 return NULL; 61 return NULL;
61} 62}
62 63
64static void notifyChange_SystemTextInput_(iSystemTextInput *);
65static BOOL isNewlineAllowed_SystemTextInput_(const iSystemTextInput *);
66
63/*----------------------------------------------------------------------------------------------*/ 67/*----------------------------------------------------------------------------------------------*/
64 68
65API_AVAILABLE(ios(13.0)) 69API_AVAILABLE(ios(13.0))
@@ -159,9 +163,10 @@ API_AVAILABLE(ios(13.0))
159 163
160/*----------------------------------------------------------------------------------------------*/ 164/*----------------------------------------------------------------------------------------------*/
161 165
162@interface AppState : NSObject<UIDocumentPickerDelegate> { 166@interface AppState : NSObject<UIDocumentPickerDelegate, UITextFieldDelegate, UITextViewDelegate> {
163 iString *fileBeingSaved; 167 iString *fileBeingSaved;
164 iString *pickFileCommand; 168 iString *pickFileCommand;
169 iSystemTextInput *sysCtrl;
165} 170}
166@property (nonatomic, assign) BOOL isHapticsAvailable; 171@property (nonatomic, assign) BOOL isHapticsAvailable;
167@property (nonatomic, strong) NSObject *haptic; 172@property (nonatomic, strong) NSObject *haptic;
@@ -175,9 +180,18 @@ static AppState *appState_;
175 self = [super init]; 180 self = [super init];
176 fileBeingSaved = NULL; 181 fileBeingSaved = NULL;
177 pickFileCommand = NULL; 182 pickFileCommand = NULL;
183 sysCtrl = NULL;
178 return self; 184 return self;
179} 185}
180 186
187-(void)setSystemTextInput:(iSystemTextInput *)sys {
188 sysCtrl = sys;
189}
190
191-(iSystemTextInput *)systemTextInput {
192 return sysCtrl;
193}
194
181-(void)setPickFileCommand:(const char *)command { 195-(void)setPickFileCommand:(const char *)command {
182 if (!pickFileCommand) { 196 if (!pickFileCommand) {
183 pickFileCommand = new_String(); 197 pickFileCommand = new_String();
@@ -256,6 +270,46 @@ didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
256-(void)keyboardOffScreen:(NSNotification *)notification { 270-(void)keyboardOffScreen:(NSNotification *)notification {
257 setKeyboardHeight_MainWindow(get_MainWindow(), 0); 271 setKeyboardHeight_MainWindow(get_MainWindow(), 0);
258} 272}
273
274static void sendReturnKeyPress_(void) {
275 SDL_Event ev = { .type = SDL_KEYDOWN };
276 ev.key.timestamp = SDL_GetTicks();
277 ev.key.keysym.sym = SDLK_RETURN;
278 ev.key.state = SDL_PRESSED;
279 SDL_PushEvent(&ev);
280 ev.type = SDL_KEYUP;
281 ev.key.state = SDL_RELEASED;
282 SDL_PushEvent(&ev);
283}
284
285- (BOOL)textFieldShouldReturn:(UITextField *)textField {
286 sendReturnKeyPress_();
287 return NO;
288}
289
290- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range
291replacementString:(NSString *)string {
292 iSystemTextInput *sysCtrl = [appState_ systemTextInput];
293 notifyChange_SystemTextInput_(sysCtrl);
294 return YES;
295}
296
297- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range
298 replacementText:(NSString *)text {
299 if ([text isEqualToString:@"\n"]) {
300 if (!isNewlineAllowed_SystemTextInput_([appState_ systemTextInput])) {
301 sendReturnKeyPress_();
302 return NO;
303 }
304 }
305 return YES;
306}
307
308- (void)textViewDidChange:(UITextView *)textView {
309 iSystemTextInput *sysCtrl = [appState_ systemTextInput];
310 notifyChange_SystemTextInput_(sysCtrl);
311}
312
259@end 313@end
260 314
261static void enableMouse_(iBool yes) { 315static void enableMouse_(iBool yes) {
@@ -483,6 +537,35 @@ void pickFile_iOS(const char *command) {
483 [viewController_(get_Window()) presentViewController:picker animated:YES completion:nil]; 537 [viewController_(get_Window()) presentViewController:picker animated:YES completion:nil];
484} 538}
485 539
540static void openActivityView_(NSArray *activityItems) {
541 UIActivityViewController *actView =
542 [[UIActivityViewController alloc]
543 initWithActivityItems:activityItems
544 applicationActivities:nil];
545 iWindow *win = get_Window();
546 UIViewController *viewCtl = viewController_(win);
547 UIPopoverPresentationController *popover = [actView popoverPresentationController];
548 if (popover) {
549 [popover setSourceView:[viewCtl view]];
550 iInt2 tapPos = latestTapPosition_Touch();
551 tapPos.x /= win->pixelRatio;
552 tapPos.y /= win->pixelRatio;
553 [popover setSourceRect:(CGRect){{tapPos.x - 10, tapPos.y - 10}, {20, 20}}];
554 [popover setCanOverlapSourceViewRect:YES];
555 }
556 [viewCtl presentViewController:actView animated:YES completion:nil];
557}
558
559void openTextActivityView_iOS(const iString *text) {
560 openActivityView_(@[[NSString stringWithUTF8String:cstr_String(text)]]);
561}
562
563void openFileActivityView_iOS(const iString *path) {
564 NSURL *url = [NSURL fileURLWithPath:[[NSString alloc] initWithCString:cstr_String(path)
565 encoding:NSUTF8StringEncoding]];
566 openActivityView_(@[url]);
567}
568
486/*----------------------------------------------------------------------------------------------*/ 569/*----------------------------------------------------------------------------------------------*/
487 570
488enum iAVFAudioPlayerState { 571enum iAVFAudioPlayerState {
@@ -511,6 +594,10 @@ void init_AVFAudioPlayer(iAVFAudioPlayer *d) {
511 594
512void deinit_AVFAudioPlayer(iAVFAudioPlayer *d) { 595void deinit_AVFAudioPlayer(iAVFAudioPlayer *d) {
513 setInput_AVFAudioPlayer(d, NULL, NULL); 596 setInput_AVFAudioPlayer(d, NULL, NULL);
597 if (d->player) {
598 CFBridgingRelease(d->player);
599 d->player = nil;
600 }
514} 601}
515 602
516static const char *cacheDir_ = "~/Library/Caches/Audio"; 603static const char *cacheDir_ = "~/Library/Caches/Audio";
@@ -607,3 +694,233 @@ iBool isStarted_AVFAudioPlayer(const iAVFAudioPlayer *d) {
607iBool isPaused_AVFAudioPlayer(const iAVFAudioPlayer *d) { 694iBool isPaused_AVFAudioPlayer(const iAVFAudioPlayer *d) {
608 return d->state == paused_AVFAudioPlayerState; 695 return d->state == paused_AVFAudioPlayerState;
609} 696}
697
698/*----------------------------------------------------------------------------------------------*/
699
700struct Impl_SystemTextInput {
701 int flags;
702 void *field; /* single-line text field */
703 void *view; /* multi-line text view */
704 void (*textChangedFunc)(iSystemTextInput *, void *);
705 void *textChangedContext;
706};
707
708iDefineTypeConstructionArgs(SystemTextInput, (iRect rect, int flags), rect, flags)
709
710#define REF_d_field (__bridge UITextField *)d->field
711#define REF_d_view (__bridge UITextView *)d->view
712
713static CGRect convertToCGRect_(const iRect *rect, iBool expanded) {
714 const iWindow *win = get_Window();
715 CGRect frame;
716 // TODO: Convert coordinates properly!
717 frame.origin.x = rect->pos.x / win->pixelRatio;
718 frame.origin.y = (rect->pos.y - gap_UI + 1) / win->pixelRatio;
719 frame.size.width = rect->size.x / win->pixelRatio;
720 frame.size.height = rect->size.y / win->pixelRatio;
721 /* Some padding to account for insets. If we just zero out the insets, the insertion point
722 may be clipped at the edges. */
723 if (expanded) {
724 const float inset = gap_UI / get_Window()->pixelRatio;
725 frame.origin.x -= inset + 1;
726 frame.origin.y -= inset + 1;
727 frame.size.width += 2 * inset + 2;
728 frame.size.height += inset + 1 + inset;
729 }
730 return frame;
731}
732
733static UIColor *makeUIColor_(enum iColorId colorId) {
734 iColor color = get_Color(colorId);
735 return [UIColor colorWithRed:color.r / 255.0
736 green:color.g / 255.0
737 blue:color.b / 255.0
738 alpha:color.a / 255.0];
739}
740
741void init_SystemTextInput(iSystemTextInput *d, iRect rect, int flags) {
742 d->flags = flags;
743 d->field = NULL;
744 d->view = NULL;
745 CGRect frame = convertToCGRect_(&rect, (flags & multiLine_SystemTextInputFlags) != 0);
746 if (flags & multiLine_SystemTextInputFlags) {
747 d->view = (void *) CFBridgingRetain([[UITextView alloc] initWithFrame:frame textContainer:nil]);
748 [[viewController_(get_Window()) view] addSubview:REF_d_view];
749 }
750 else {
751 d->field = (void *) CFBridgingRetain([[UITextField alloc] initWithFrame:frame]);
752 [[viewController_(get_Window()) view] addSubview:REF_d_field];
753 }
754 UIControl<UITextInputTraits> *traits = (UIControl<UITextInputTraits> *) (d->view ? REF_d_view : REF_d_field);
755 if (~flags & insertNewlines_SystemTextInputFlag) {
756 [traits setReturnKeyType:UIReturnKeyDone];
757 }
758 if (flags & returnGo_SystemTextInputFlags) {
759 [traits setReturnKeyType:UIReturnKeyGo];
760 }
761 if (flags & returnSend_SystemTextInputFlags) {
762 [traits setReturnKeyType:UIReturnKeySend];
763 }
764 if (flags & disableAutocorrect_SystemTextInputFlag) {
765 [traits setAutocorrectionType:UITextAutocorrectionTypeNo];
766 [traits setSpellCheckingType:UITextSpellCheckingTypeNo];
767 }
768 if (flags & disableAutocapitalize_SystemTextInputFlag) {
769 [traits setAutocapitalizationType:UITextAutocapitalizationTypeNone];
770 }
771 if (flags & alignRight_SystemTextInputFlag) {
772 if (d->field) {
773 [REF_d_field setTextAlignment:NSTextAlignmentRight];
774 }
775 if (d->view) {
776 [REF_d_view setTextAlignment:NSTextAlignmentRight];
777 }
778 }
779 UIColor *textColor = makeUIColor_(uiInputTextFocused_ColorId);
780 UIColor *backgroundColor = makeUIColor_(uiInputBackgroundFocused_ColorId);
781 UIColor *tintColor = makeUIColor_(uiInputFrameHover_ColorId); /* use the accent color */ //uiInputCursor_ColorId);
782 [appState_ setSystemTextInput:d];
783 if (d->field) {
784 UITextField *field = REF_d_field;
785 [field setTextColor:textColor];
786 [field setTintColor:tintColor];
787 [field setDelegate:appState_];
788 [field becomeFirstResponder];
789 }
790 else {
791 UITextView *view = REF_d_view;
792 [view setBackgroundColor:[UIColor colorWithWhite:1.0f alpha:0.0f]];
793 [view setTextColor:textColor];
794 [view setTintColor:tintColor];
795 if (flags & extraPadding_SystemTextInputFlag) {
796 [view setContentInset:(UIEdgeInsets){ 0, 0, 3 * gap_UI / get_Window()->pixelRatio, 0}];
797 }
798 [view setEditable:YES];
799 [view setDelegate:appState_];
800 [view becomeFirstResponder];
801 }
802 d->textChangedFunc = NULL;
803 d->textChangedContext = NULL;
804}
805
806void deinit_SystemTextInput(iSystemTextInput *d) {
807 [appState_ setSystemTextInput:nil];
808 if (d->field) {
809 [REF_d_field removeFromSuperview];
810 CFBridgingRelease(d->field);
811 d->field = nil;
812 }
813 if (d->view) {
814 [REF_d_view removeFromSuperview];
815 CFBridgingRelease(d->view);
816 d->view = nil;
817 }
818}
819
820void selectAll_SystemTextInput(iSystemTextInput *d) {
821 if (d->field) {
822 [REF_d_field selectAll:nil];
823 }
824 if (d->view) {
825 [REF_d_view selectAll:nil];
826 }
827}
828
829void setText_SystemTextInput(iSystemTextInput *d, const iString *text, iBool allowUndo) {
830 NSString *str = [NSString stringWithUTF8String:cstr_String(text)];
831 if (d->field) {
832 [REF_d_field setText:str];
833 if (d->flags & selectAll_SystemTextInputFlags) {
834 [REF_d_field selectAll:nil];
835 }
836 }
837 else {
838 UITextView *view = REF_d_view;
839// if (allowUndo) {
840// [view selectAll:nil];
841// if ([view shouldChangeTextInRange:[view selectedTextRange] replacementText:@""]) {
842// [[view textStorage] beginEditing];
843// [[view textStorage] replaceCharactersInRange:[view selectedRange] withString:@""];
844// [[view textStorage] endEditing];
845// }
846// }
847// else {
848 // TODO: How to implement `allowUndo`, given that UITextView does not exist when unfocused?
849 // Maybe keep the UITextStorage (if it has the undo?)?
850 [view setText:str];
851// }
852 if (d->flags & selectAll_SystemTextInputFlags) {
853 [view selectAll:nil];
854 }
855 }
856}
857
858int preferredHeight_SystemTextInput(const iSystemTextInput *d) {
859 if (d->view) {
860 CGRect usedRect = [[REF_d_view layoutManager] usedRectForTextContainer:[REF_d_view textContainer]];
861 return usedRect.size.height * get_Window()->pixelRatio;
862 }
863 return 0;
864}
865
866void setFont_SystemTextInput(iSystemTextInput *d, int fontId) {
867 float height = lineHeight_Text(fontId) / get_Window()->pixelRatio;
868 UIFont *font;
869 // for (NSString *name in [UIFont familyNames]) {
870 // printf("family: %s\n", [name cStringUsingEncoding:NSUTF8StringEncoding]);
871 // }
872 if (fontId / maxVariants_Fonts * maxVariants_Fonts == monospace_FontId) {
873// font = [UIFont monospacedSystemFontOfSize:0.8f * height weight:UIFontWeightRegular];
874// for (NSString *name in [UIFont fontNamesForFamilyName:@"Iosevka Term"]) {
875// printf("fontname: %s\n", [name cStringUsingEncoding:NSUTF8StringEncoding]);
876// }
877 font = [UIFont fontWithName:@"Iosevka-Term-Extended" size:height * 0.82f];
878 }
879 else {
880// font = [UIFont systemFontOfSize:0.65f * height];
881 font = [UIFont fontWithName:@"SourceSans3-Regular" size:height * 0.7f];
882 }
883 if (d->field) {
884 [REF_d_field setFont:font];
885 }
886 if (d->view) {
887 [REF_d_view setFont:font];
888 }
889}
890
891const iString *text_SystemTextInput(const iSystemTextInput *d) {
892 if (d->field) {
893 return collectNewCStr_String([[REF_d_field text] cStringUsingEncoding:NSUTF8StringEncoding]);
894 }
895 if (d->view) {
896 return collectNewCStr_String([[REF_d_view text] cStringUsingEncoding:NSUTF8StringEncoding]);
897 }
898 return NULL;
899}
900
901void setRect_SystemTextInput(iSystemTextInput *d, iRect rect) {
902 CGRect frame = convertToCGRect_(&rect, (d->flags & multiLine_SystemTextInputFlags) != 0);
903 if (d->field) {
904 [REF_d_field setFrame:frame];
905 }
906 else {
907 [REF_d_view setFrame:frame];
908 }
909}
910
911void setTextChangedFunc_SystemTextInput(iSystemTextInput *d,
912 void (*textChangedFunc)(iSystemTextInput *, void *),
913 void *context) {
914 d->textChangedFunc = textChangedFunc;
915 d->textChangedContext = context;
916}
917
918static void notifyChange_SystemTextInput_(iSystemTextInput *d) {
919 if (d && d->textChangedFunc) {
920 d->textChangedFunc(d, d->textChangedContext);
921 }
922}
923
924static BOOL isNewlineAllowed_SystemTextInput_(const iSystemTextInput *d) {
925 return (d->flags & insertNewlines_SystemTextInputFlag) != 0;
926}