diff --git a/ios/CCIOS.xcodeproj/project.pbxproj b/ios/CCIOS.xcodeproj/project.pbxproj index e56008d96..233ae0900 100644 --- a/ios/CCIOS.xcodeproj/project.pbxproj +++ b/ios/CCIOS.xcodeproj/project.pbxproj @@ -14,6 +14,9 @@ 9A6C79652BFDDEF200676D27 /* FancyLighting.c in Sources */ = {isa = PBXBuildFile; fileRef = 9A6C79642BFDDEF100676D27 /* FancyLighting.c */; }; 9A6C79672BFDDF0700676D27 /* Queue.c in Sources */ = {isa = PBXBuildFile; fileRef = 9A6C79662BFDDF0600676D27 /* Queue.c */; }; 9A6C7DFA2C2F610C00676D27 /* LBackend_ios.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A6C7DF92C2F610C00676D27 /* LBackend_ios.m */; }; + 9A6C7DFC2C41E93700676D27 /* InputHandler.c in Sources */ = {isa = PBXBuildFile; fileRef = 9A6C7DFB2C41E93700676D27 /* InputHandler.c */; }; + 9A6C7DFE2C41E95D00676D27 /* MenuOptions.c in Sources */ = {isa = PBXBuildFile; fileRef = 9A6C7DFD2C41E95C00676D27 /* MenuOptions.c */; }; + 9A6C7E002C41EED700676D27 /* Window_ios.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A6C7DFF2C41EED700676D27 /* Window_ios.m */; }; 9A7401D92B737D5C0040E575 /* Commands.c in Sources */ = {isa = PBXBuildFile; fileRef = 9A7401D82B737D5B0040E575 /* Commands.c */; }; 9A7401DB2B7384060040E575 /* SSL.c in Sources */ = {isa = PBXBuildFile; fileRef = 9A7401DA2B7384060040E575 /* SSL.c */; }; 9A89D4F227F802F600FF3F80 /* LWidgets.c in Sources */ = {isa = PBXBuildFile; fileRef = 9A89D37827F802F500FF3F80 /* LWidgets.c */; }; @@ -103,6 +106,9 @@ 9A6C79642BFDDEF100676D27 /* FancyLighting.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = FancyLighting.c; sourceTree = ""; }; 9A6C79662BFDDF0600676D27 /* Queue.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = Queue.c; sourceTree = ""; }; 9A6C7DF92C2F610C00676D27 /* LBackend_ios.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LBackend_ios.m; sourceTree = ""; }; + 9A6C7DFB2C41E93700676D27 /* InputHandler.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = InputHandler.c; sourceTree = ""; }; + 9A6C7DFD2C41E95C00676D27 /* MenuOptions.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = MenuOptions.c; sourceTree = ""; }; + 9A6C7DFF2C41EED700676D27 /* Window_ios.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Window_ios.m; sourceTree = ""; }; 9A7401D82B737D5B0040E575 /* Commands.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = Commands.c; sourceTree = ""; }; 9A7401DA2B7384060040E575 /* SSL.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SSL.c; sourceTree = ""; }; 9A89D35727F802B100FF3F80 /* ClassiCube.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ClassiCube.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -218,6 +224,9 @@ 9A89D37727F802F500FF3F80 /* src */ = { isa = PBXGroup; children = ( + 9A6C7DFF2C41EED700676D27 /* Window_ios.m */, + 9A6C7DFD2C41E95C00676D27 /* MenuOptions.c */, + 9A6C7DFB2C41E93700676D27 /* InputHandler.c */, 9A6C7DF92C2F610C00676D27 /* LBackend_ios.m */, 9A6C79662BFDDF0600676D27 /* Queue.c */, 9A6C79642BFDDEF100676D27 /* FancyLighting.c */, @@ -386,6 +395,7 @@ 9A89D58327F802F600FF3F80 /* EntityComponents.c in Sources */, 9A7401D92B737D5C0040E575 /* Commands.c in Sources */, 9A89D57327F802F600FF3F80 /* Utils.c in Sources */, + 9A6C7DFE2C41E95D00676D27 /* MenuOptions.c in Sources */, 9A89D58427F802F600FF3F80 /* Camera.c in Sources */, 9A89D57C27F802F600FF3F80 /* Chat.c in Sources */, 9A89D50527F802F600FF3F80 /* LScreens.c in Sources */, @@ -456,11 +466,13 @@ 9A89D4FE27F802F600FF3F80 /* Inventory.c in Sources */, 9A89D56527F802F600FF3F80 /* Generator.c in Sources */, 9A89D57527F802F600FF3F80 /* AxisLinesRenderer.c in Sources */, + 9A6C7E002C41EED700676D27 /* Window_ios.m in Sources */, 9A89D55E27F802F600FF3F80 /* World.c in Sources */, 9A89D58627F802F600FF3F80 /* Screens.c in Sources */, 9A89D4F927F802F600FF3F80 /* Http_Worker.c in Sources */, 9A89D58B27F802F600FF3F80 /* Launcher.c in Sources */, 9A89D59027F802F600FF3F80 /* Stream.c in Sources */, + 9A6C7DFC2C41E93700676D27 /* InputHandler.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/readme.md b/readme.md index 904a02dd7..b0ea5b5e8 100644 --- a/readme.md +++ b/readme.md @@ -73,7 +73,8 @@ And also runs on: * IRIX - needs curl and openal packages * SerenityOS - needs SDL2 * Classic Mac OS (System 7 and later) -* Dreamcast - unfinished, but renders (can [download from here](https://www.classicube.net/download/dreamcast)) +* Dreamcast - unfinished, but usable (can [download from here](https://www.classicube.net/download/dreamcast)) +* Saturn - unfinished, major rendering and **stability issues** (if you have a GitHub account, can [download from here](https://github.com/ClassiCube/ClassiCube/actions/workflows/build_saturn.yml)) * Switch - unfinished, but usable (can [download from here](https://www.classicube.net/download/switch)) * Wii U - unfinished, major issues (if you have a GitHub account, can [download from here](https://github.com/ClassiCube/ClassiCube/actions/workflows/build_wiiu.yml)), **untested on real hardware**) * Wii - unfinished, but usable (can [download from here](https://www.classicube.net/download/wii)) @@ -85,7 +86,7 @@ And also runs on: * PSP - unfinished, rendering issues (can [download from here](https://www.classicube.net/download/psp)) * PS3 - unfinished, rendering issues (can [download from here](https://www.classicube.net/download/ps3)) * PS2 - unfinished, major rendering and **stability issues** (can [download from here](https://www.classicube.net/download/ps2)) -* PS1 - unfinished, major rendering and **stability issues** +* PS1 - unfinished, major rendering and **stability issues** (if you have a GitHub account, can [download from here](https://github.com/ClassiCube/ClassiCube/actions/workflows/build_ps1.yml)) * Xbox 360 - completely unfinished (if you have a GitHub account, can [download from here](https://github.com/ClassiCube/ClassiCube/actions/workflows/build_xbox360.yml)), **untested on real hardware**) * Xbox - unfinished, major rendering issues (can [download from here](https://www.classicube.net/download/xbox)) @@ -139,24 +140,24 @@ Compiling with TCC: Install appropriate libs as required. For ubuntu these are: libx11-dev, libxi-dev and libgl1-mesa-dev -```gcc -fno-math-errno *.c -o ClassiCube -rdynamic -lpthread -lX11 -lXi -lGL -ldl``` +```gcc -fno-math-errno src/*.c -o ClassiCube -rdynamic -lpthread -lX11 -lXi -lGL -ldl``` ##### Cross compiling for Windows (32 bit): -```i686-w64-mingw32-gcc -fno-math-errno *.c -o ClassiCube.exe -mwindows -lwinmm -limagehlp``` +```i686-w64-mingw32-gcc -fno-math-errno src/*.c -o ClassiCube.exe -mwindows -lwinmm -limagehlp``` ##### Cross compiling for Windows (64 bit): -```x86_64-w64-mingw32-gcc -fno-math-errno *.c -o ClassiCube.exe -mwindows -lwinmm -limagehlp``` +```x86_64-w64-mingw32-gcc -fno-math-errno src/*.c -o ClassiCube.exe -mwindows -lwinmm -limagehlp``` ##### Raspberry Pi Although the regular linux compiliation flags will work fine, to take full advantage of the hardware: -```gcc -fno-math-errno *.c -o ClassiCube -DCC_BUILD_RPI -rdynamic -lpthread -lX11 -lXi -lEGL -lGLESv2 -ldl``` +```gcc -fno-math-errno src/*.c -o ClassiCube -DCC_BUILD_RPI -rdynamic -lpthread -lX11 -lXi -lEGL -lGLESv2 -ldl``` ## Compiling - macOS -```cc -fno-math-errno *.c *.m -o ClassiCube -framework Cocoa -framework OpenGL -framework IOKit -lobjc``` +```cc -fno-math-errno src/*.c src/*.m -o ClassiCube -framework Cocoa -framework OpenGL -framework IOKit -lobjc``` Note: You may need to install Xcode before you can compile ClassiCube @@ -192,7 +193,7 @@ Open the `ios/CCIOS.xcodeproj` project in Xcode, and then compile it ## Compiling - webclient -```emcc *.c -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_STACK=1Mb --js-library interop_web.js``` +```emcc src/*.c -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_STACK=1Mb --js-library interop_web.js``` The generated javascript file has some issues. [See here for how to fix](doc/compile-fixes.md#webclient-patches) @@ -314,37 +315,37 @@ Run `make saturn`. You'll need [libyaul](https://github.com/yaul-org/libyaul) Install libxi, libexecinfo, curl and openal-soft package if needed -```cc *.c -o ClassiCube -I /usr/local/include -L /usr/local/lib -lm -lpthread -lX11 -lXi -lGL -lexecinfo``` +```cc src/*.c -o ClassiCube -I /usr/local/include -L /usr/local/lib -lm -lpthread -lX11 -lXi -lGL -lexecinfo``` #### OpenBSD Install libexecinfo, curl and openal package if needed -```cc *.c -o ClassiCube -I /usr/X11R6/include -I /usr/local/include -L /usr/X11R6/lib -L /usr/local/lib -lm -lpthread -lX11 -lXi -lGL -lexecinfo``` +```cc src/*.c -o ClassiCube -I /usr/X11R6/include -I /usr/local/include -L /usr/X11R6/lib -L /usr/local/lib -lm -lpthread -lX11 -lXi -lGL -lexecinfo``` #### NetBSD Install libexecinfo, curl and openal-soft package if needed -```cc *.c -o ClassiCube -I /usr/X11R7/include -I /usr/pkg/include -L /usr/X11R7/lib -L /usr/pkg/lib -lpthread -lX11 -lXi -lGL -lexecinfo``` +```cc src/*.c -o ClassiCube -I /usr/X11R7/include -I /usr/pkg/include -L /usr/X11R7/lib -L /usr/pkg/lib -lpthread -lX11 -lXi -lGL -lexecinfo``` #### DragonflyBSD -```cc *.c -o ClassiCube -I /usr/local/include -L /usr/local/lib -lm -lpthread -lX11 -lXi -lGL -lexecinfo``` +```cc src/*.c -o ClassiCube -I /usr/local/include -L /usr/local/lib -lm -lpthread -lX11 -lXi -lGL -lexecinfo``` #### Solaris -```gcc -fno-math-errno *.c -o ClassiCube -lsocket -lX11 -lXi -lGL``` +```gcc -fno-math-errno src/*.c -o ClassiCube -lsocket -lX11 -lXi -lGL``` #### Haiku Install openal_devel package if needed -```cc -fno-math-errno *.c interop_BeOS.cpp -o ClassiCube -lGL -lnetwork -lstdc++ -lbe -lgame -ltracker``` +```cc -fno-math-errno src/*.c interop_BeOS.cpp -o ClassiCube -lGL -lnetwork -lstdc++ -lbe -lgame -ltracker``` #### BeOS -```cc -fno-math-errno *.c interop_BeOS.cpp -o ClassiCube -lGL -lbe -lgame -ltracker``` +```cc -fno-math-errno src/*.c interop_BeOS.cpp -o ClassiCube -lGL -lbe -lgame -ltracker``` #### IRIX @@ -354,7 +355,7 @@ Install openal_devel package if needed Install SDL2 port if needed -```cc *.c -o ClassiCube -lgl -lSDL2``` +```cc src/*.c -o ClassiCube -lgl -lSDL2``` #### Classic Mac OS diff --git a/src/LBackend_ios.m b/src/LBackend_ios.m index cbf424ebb..575b337ce 100644 --- a/src/LBackend_ios.m +++ b/src/LBackend_ios.m @@ -39,7 +39,7 @@ void LInput_SetPlaceholder(UITextField* fld, const char* placeholder); /*########################################################################################################################* - *------------------------------------------------------Common helpers--------------------------------------------------------* + *----------------------------------------------------Common helpers------------------------------------------------------* *#########################################################################################################################*/ static NSMutableAttributedString* ToAttributedString(const cc_string* text) { // NSMutableAttributedString - iOS 3.2 diff --git a/src/Window_ios.m b/src/Window_ios.m new file mode 100644 index 000000000..be295956a --- /dev/null +++ b/src/Window_ios.m @@ -0,0 +1,708 @@ +// Silence deprecation warnings on modern iOS +#define GLES_SILENCE_DEPRECATION +#include "Core.h" + +#if defined CC_BUILD_IOS +#include "_WindowBase.h" +#include "Bitmap.h" +#include "Input.h" +#include "Platform.h" +#include "String.h" +#include "Errors.h" +#include "Drawer2D.h" +#include "Launcher.h" +#include "Funcs.h" +#include "Gui.h" +#include +#include +#include +#include +#include + +#ifdef TARGET_OS_TV + // NSFontAttributeName etc - iOS 6.0 + #define TEXT_ATTRIBUTE_FONT NSFontAttributeName + #define TEXT_ATTRIBUTE_COLOR NSForegroundColorAttributeName +#else + // UITextAttributeFont etc - iOS 5.0 + #define TEXT_ATTRIBUTE_FONT UITextAttributeFont + #define TEXT_ATTRIBUTE_COLOR UITextAttributeTextColor +#endif + +// shared state with LBackend_ios.m and interop_ios.m +UITextField* kb_widget; +CGContextRef win_ctx; +UIView* view_handle; +UIViewController* cc_controller; + +UIColor* ToUIColor(BitmapCol color, float A); +NSString* ToNSString(const cc_string* text); +void LInput_SetKeyboardType(UITextField* fld, int flags); +void LInput_SetPlaceholder(UITextField* fld, const char* placeholder); +UIInterfaceOrientationMask SupportedOrientations(void); +void LogUnhandledNSErrors(NSException* ex); + + +@interface CCWindow : UIWindow +@end + +@interface CCViewController : UIViewController +@end +static UIWindow* win_handle; +static cc_bool launcherMode; + +static void AddTouch(UITouch* t) { + CGPoint loc = [t locationInView:view_handle]; + int x = loc.x, y = loc.y; long ui_id = (long)t; + Platform_Log3("POINTER %x - DOWN %i,%i", &ui_id, &x, &y); + Input_AddTouch((long)t, loc.x, loc.y); +} + +static void UpdateTouch(UITouch* t) { + CGPoint loc = [t locationInView:view_handle]; + int x = loc.x, y = loc.y; long ui_id = (long)t; + Platform_Log3("POINTER %x - MOVE %i,%i", &ui_id, &x, &y); + Input_UpdateTouch((long)t, loc.x, loc.y); +} + +static void RemoveTouch(UITouch* t) { + CGPoint loc = [t locationInView:view_handle]; + int x = loc.x, y = loc.y; long ui_id = (long)t; + Platform_Log3("POINTER %x - UP %i,%i", &ui_id, &x, &y); + Input_RemoveTouch((long)t, loc.x, loc.y); +} + +static cc_bool landscape_locked; +UIInterfaceOrientationMask SupportedOrientations(void) { + if (landscape_locked) + return UIInterfaceOrientationMaskLandscape; + return UIInterfaceOrientationMaskAll; +} + +static cc_bool fullscreen = true; +static void UpdateStatusBar(void) { + if ([cc_controller respondsToSelector:@selector(setNeedsStatusBarAppearanceUpdate)]) { + // setNeedsStatusBarAppearanceUpdate - iOS 7.0 + [cc_controller setNeedsStatusBarAppearanceUpdate]; + } else { + [[UIApplication sharedApplication] setStatusBarHidden:fullscreen withAnimation:UIStatusBarAnimationNone]; + } +} + +static CGRect GetViewFrame(void) { + UIScreen* screen = UIScreen.mainScreen; + return fullscreen ? screen.bounds : screen.applicationFrame; +} + +@implementation CCWindow + +- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event { + // touchesBegan:withEvent - iOS 2.0 + for (UITouch* t in touches) AddTouch(t); + + // clicking on the background should dismiss onscren keyboard + if (launcherMode) { [view_handle endEditing:NO]; } +} + +- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent *)event { + // touchesMoved:withEvent - iOS 2.0 + for (UITouch* t in touches) UpdateTouch(t); +} + +- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent *)event { + // touchesEnded:withEvent - iOS 2.0 + for (UITouch* t in touches) RemoveTouch(t); +} + +- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent *)event { + // touchesCancelled:withEvent - iOS 2.0 + for (UITouch* t in touches) RemoveTouch(t); +} + +- (BOOL)isOpaque { return YES; } +@end + + +@implementation CCViewController +- (UIInterfaceOrientationMask)supportedInterfaceOrientations { + // supportedInterfaceOrientations - iOS 6.0 + return SupportedOrientations(); +} + +- (BOOL)shouldAutorotate { + // shouldAutorotate - iOS 6.0 + return YES; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)ori { + // shouldAutorotateToInterfaceOrientation - iOS 2.0 + if (landscape_locked && !(ori == UIInterfaceOrientationLandscapeLeft || ori == UIInterfaceOrientationLandscapeRight)) + return NO; + return YES; +} + +- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { + // viewWillTransitionToSize:withTransitionCoordinator - iOS 8.0 + Window_Main.Width = size.width; + Window_Main.Height = size.height; + + Event_RaiseVoid(&WindowEvents.Resized); + [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; +} + +// ==== UIDocumentPickerDelegate ==== +static FileDialogCallback open_dlg_callback; +static char save_buffer[FILENAME_SIZE]; +static cc_string save_path = String_FromArray(save_buffer); + +static void DeleteExportTempFile(void) { + if (!save_path.length) return; + + char path[NATIVE_STR_LEN]; + String_EncodeUtf8(path, &save_path); + unlink(path); + save_path.length = 0; +} + +- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url { + // documentPicker:didPickDocumentAtURL - iOS 8.0 + NSString* str = url.path; + const char* utf8 = str.UTF8String; + + char tmpBuffer[NATIVE_STR_LEN]; + cc_string tmp = String_FromArray(tmpBuffer); + String_AppendUtf8(&tmp, utf8, String_Length(utf8)); + + DeleteExportTempFile(); + if (!open_dlg_callback) return; + open_dlg_callback(&tmp); + open_dlg_callback = NULL; +} + +- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller { + // documentPickerWasCancelled - iOS 8.0 + DeleteExportTempFile(); +} + +static cc_bool kb_active; +- (void)keyboardDidShow:(NSNotification*)notification { + NSDictionary* info = notification.userInfo; + if (kb_active) return; + // TODO this is wrong + // TODO this doesn't actually trigger view resize??? + kb_active = true; + + double interval = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + NSInteger curve = [[info objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue]; + CGRect kbFrame = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; + CGRect winFrame = view_handle.frame; + + cc_bool can_shift = true; + // would the active input widget be pushed offscreen? + if (kb_widget) { + can_shift = kb_widget.frame.origin.y > kbFrame.size.height; + } + if (can_shift) winFrame.origin.y = -kbFrame.size.height; + kb_widget = nil; + + Platform_LogConst("APPEAR"); + [UIView animateWithDuration:interval delay: 0.0 options:curve animations:^{ + view_handle.frame = winFrame; + } completion:nil]; +} + +- (void)keyboardDidHide:(NSNotification*)notification { + NSDictionary* info = notification.userInfo; + if (!kb_active) return; + kb_active = false; + kb_widget = nil; + + double interval = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + NSInteger curve = [[info objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue]; + CGRect winFrame = view_handle.frame; + winFrame.origin.y = 0; + + Platform_LogConst("VANISH"); + [UIView animateWithDuration:interval delay: 0.0 options:curve animations:^{ + view_handle.frame = winFrame; + } completion:nil]; +} + +- (BOOL)prefersStatusBarHidden { + // prefersStatusBarHidden - iOS 7.0 + return fullscreen; +} + +- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures { + // preferredScreenEdgesDeferringSystemGestures - iOS 11.0 + // recent iOS versions have a 'bottom home bar', which when swiped up, + // switches out of ClassiCube and to the app list menu + // overriding this forces the user to swipe up twice, which should + // significantly the chance of accidentally triggering this gesture + return UIRectEdgeBottom; +} + +// == UIAlertViewDelegate == +static int alert_completed; +- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex { + alert_completed = true; +} +@end + +// iOS textfields manage ctrl+c/v +void Clipboard_GetText(cc_string* value) { } +void Clipboard_SetText(const cc_string* value) { } + + +/*########################################################################################################################* +*---------------------------------------------------------Window----------------------------------------------------------* +*#########################################################################################################################*/ +// no cursor on iOS +void Cursor_GetRawPos(int* x, int* y) { *x = 0; *y = 0; } +void Cursor_SetPosition(int x, int y) { } +void Cursor_DoSetVisible(cc_bool visible) { } + +void Window_SetTitle(const cc_string* title) { + // TODO: Implement this somehow +} + +void Window_PreInit(void) { + DisplayInfo.CursorVisible = true; +} + +void Window_Init(void) { + //Window_Main.SoftKeyboard = SOFT_KEYBOARD_RESIZE; + // keyboard now shifts up + Window_Main.SoftKeyboard = SOFT_KEYBOARD_SHIFT; + Input_SetTouchMode(true); + Input.Sources = INPUT_SOURCE_NORMAL; + Gui_SetTouchUI(true); + + DisplayInfo.Depth = 32; + DisplayInfo.ScaleX = 1; // TODO dpi scale + DisplayInfo.ScaleY = 1; // TODO dpi scale + NSSetUncaughtExceptionHandler(LogUnhandledNSErrors); +} + +void Window_Free(void) { } + +static UIColor* CalcBackgroundColor(void) { + // default to purple if no themed background color yet + if (!Launcher_Theme.BackgroundColor) + return UIColor.purpleColor; + return ToUIColor(Launcher_Theme.BackgroundColor, 1.0f); +} + +static CGRect DoCreateWindow(void) { + // UIKeyboardWillShowNotification - iOS 2.0 + cc_controller = [CCViewController alloc]; + UpdateStatusBar(); + + CGRect bounds = GetViewFrame(); + win_handle = [[CCWindow alloc] initWithFrame:bounds]; + + win_handle.rootViewController = cc_controller; + win_handle.backgroundColor = CalcBackgroundColor(); + Window_Main.Exists = true; + Window_Main.UIScaleX = DEFAULT_UI_SCALE_X; + Window_Main.UIScaleY = DEFAULT_UI_SCALE_Y; + + Window_Main.Width = bounds.size.width; + Window_Main.Height = bounds.size.height; + Window_Main.SoftKeyboardInstant = true; + + NSNotificationCenter* notifications = NSNotificationCenter.defaultCenter; + [notifications addObserver:cc_controller selector:@selector(keyboardDidShow:) name:UIKeyboardWillShowNotification object:nil]; + [notifications addObserver:cc_controller selector:@selector(keyboardDidHide:) name:UIKeyboardWillHideNotification object:nil]; + return bounds; +} +void Window_SetSize(int width, int height) { } + +void Window_Show(void) { + [win_handle makeKeyAndVisible]; +} + +void Window_RequestClose(void) { + Event_RaiseVoid(&WindowEvents.Closing); +} + +void Window_ProcessEvents(float delta) { + SInt32 res; + // manually tick event queue + do { + res = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, TRUE); + } while (res == kCFRunLoopRunHandledSource); +} + +void Gamepads_Init(void) { + +} + +void Gamepads_Process(float delta) { } + +void ShowDialogCore(const char* title, const char* msg) { + // UIAlertController - iOS 8.0 + // UIAlertAction - iOS 8.0 + // UIAlertView - iOS 2.0 + Platform_LogConst(title); + Platform_LogConst(msg); + NSString* _title = [NSString stringWithCString:title encoding:NSASCIIStringEncoding]; + NSString* _msg = [NSString stringWithCString:msg encoding:NSASCIIStringEncoding]; + alert_completed = false; + +#ifdef TARGET_OS_TV + UIAlertController* alert = [UIAlertController alertControllerWithTitle:_title message:_msg preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction* okBtn = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction* act) { alert_completed = true; }]; + [alert addAction:okBtn]; + [cc_controller presentViewController:alert animated:YES completion: Nil]; +#else + UIAlertView* alert = [UIAlertView alloc]; + alert = [alert initWithTitle:_title message:_msg delegate:cc_controller cancelButtonTitle:@"OK" otherButtonTitles:nil]; + [alert show]; +#endif + + // TODO clicking outside message box crashes launcher + // loop until alert is closed TODO avoid sleeping + while (!alert_completed) { + Window_ProcessEvents(0.0); + Thread_Sleep(16); + } +} + + +@interface CCKBController : NSObject +@end + +@implementation CCKBController +- (void)handleTextChanged:(id)sender { + UITextField* src = (UITextField*)sender; + const char* str = src.text.UTF8String; + + char tmpBuffer[NATIVE_STR_LEN]; + cc_string tmp = String_FromArray(tmpBuffer); + String_AppendUtf8(&tmp, str, String_Length(str)); + + Event_RaiseString(&InputEvents.TextChanged, &tmp); +} + +// === UITextFieldDelegate === +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + // textFieldShouldReturn - iOS 2.0 + Input_SetPressed(CCKEY_ENTER); + Input_SetReleased(CCKEY_ENTER); + return YES; +} +@end + +static UITextField* text_input; +static CCKBController* kb_controller; + +void OnscreenKeyboard_Open(struct OpenKeyboardArgs* args) { + if (!kb_controller) { + kb_controller = [[CCKBController alloc] init]; + CFBridgingRetain(kb_controller); // prevent GC TODO even needed? + } + DisplayInfo.ShowingSoftKeyboard = true; + + text_input = [[UITextField alloc] initWithFrame:CGRectZero]; + text_input.hidden = YES; + text_input.delegate = kb_controller; + [text_input addTarget:kb_controller action:@selector(handleTextChanged:) forControlEvents:UIControlEventEditingChanged]; + + LInput_SetKeyboardType(text_input, args->type); + LInput_SetPlaceholder(text_input, args->placeholder); + + [view_handle addSubview:text_input]; + [text_input becomeFirstResponder]; +} + +void OnscreenKeyboard_SetText(const cc_string* text) { + NSString* str = ToNSString(text); + NSString* cur = text_input.text; + + // otherwise on iOS 5, this causes an infinite loop + if (cur && [str isEqualToString:cur]) return; + text_input.text = str; +} + +void OnscreenKeyboard_Close(void) { + DisplayInfo.ShowingSoftKeyboard = false; + [text_input resignFirstResponder]; +} + +int Window_GetWindowState(void) { + return fullscreen ? WINDOW_STATE_FULLSCREEN : WINDOW_STATE_NORMAL; +} + +static void ToggleFullscreen(cc_bool isFullscreen) { + fullscreen = isFullscreen; + UpdateStatusBar(); + view_handle.frame = GetViewFrame(); +} + +cc_result Window_EnterFullscreen(void) { + ToggleFullscreen(true); return 0; +} +cc_result Window_ExitFullscreen(void) { + ToggleFullscreen(false); return 0; +} +int Window_IsObscured(void) { return 0; } + +void Window_EnableRawMouse(void) { DefaultEnableRawMouse(); } +void Window_UpdateRawMouse(void) { } +void Window_DisableRawMouse(void) { DefaultDisableRawMouse(); } + +void Window_LockLandscapeOrientation(cc_bool lock) { + // attemptRotationToDeviceOrientation - iOS 5.0 + // TODO doesn't work properly.. setting 'UIInterfaceOrientationUnknown' apparently + // restores orientation, but doesn't actually do that when I tried it + if (lock) { + //NSInteger ori = lock ? UIInterfaceOrientationLandscapeRight : UIInterfaceOrientationUnknown; + NSInteger ori = UIInterfaceOrientationLandscapeRight; + UIDevice* device = UIDevice.currentDevice; + NSNumber* value = [NSNumber numberWithInteger:ori]; + [device setValue:value forKey:@"orientation"]; + } + + landscape_locked = lock; + [UIViewController attemptRotationToDeviceOrientation]; +} + +cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* args) { + // UIDocumentPickerViewController - iOS 8.0 + // see the custom UTITypes declared in Info.plist + NSDictionary* fileExt_map = + @{ + @".cw" : @"com.classicube.client.ios-cw", + @".dat" : @"com.classicube.client.ios-dat", + @".lvl" : @"com.classicube.client.ios-lvl", + @".fcm" : @"com.classicube.client.ios-fcm", + @".zip" : @"public.zip-archive" + }; + NSMutableArray* types = [NSMutableArray array]; + const char* const* filters = args->filters; + + for (int i = 0; filters[i]; i++) + { + NSString* fileExt = [NSString stringWithUTF8String:filters[i]]; + NSString* utType = [fileExt_map objectForKey:fileExt]; + if (utType) [types addObject:utType]; + } + + UIDocumentPickerViewController* dlg; + dlg = [UIDocumentPickerViewController alloc]; + dlg = [dlg initWithDocumentTypes:types inMode:UIDocumentPickerModeOpen]; + //dlg = [dlg initWithDocumentTypes:types inMode:UIDocumentPickerModeImport]; + + open_dlg_callback = args->Callback; + dlg.delegate = cc_controller; + [cc_controller presentViewController:dlg animated:YES completion: Nil]; + return 0; // TODO still unfinished +} + +cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args) { + if (!args->defaultName.length) return SFD_ERR_NEED_DEFAULT_NAME; + // UIDocumentPickerViewController - iOS 8.0 + + // save the item to a temp file, which is then (usually) later deleted by picker callbacks + Directory_Create(FILEPATH_RAW("Exported")); + + save_path.length = 0; + String_Format2(&save_path, "Exported/%s%c", &args->defaultName, args->filters[0]); + args->Callback(&save_path); + + NSString* str = ToNSString(&save_path); + NSURL* url = [NSURL fileURLWithPath:str isDirectory:NO]; + + UIDocumentPickerViewController* dlg; + dlg = [UIDocumentPickerViewController alloc]; + dlg = [dlg initWithURL:url inMode:UIDocumentPickerModeExportToService]; + + dlg.delegate = cc_controller; + [cc_controller presentViewController:dlg animated:YES completion: Nil]; + return 0; +} + + +/*#########################################################################################################################* + *-----------------------------------------------------Window creation-----------------------------------------------------* + *#########################################################################################################################*/ +@interface CC3DView : UIView +@end +static void Init3DLayer(void); + +void Window_Create2D(int width, int height) { + launcherMode = true; + CGRect bounds = DoCreateWindow(); + + view_handle = [[UIView alloc] initWithFrame:bounds]; + view_handle.multipleTouchEnabled = true; + cc_controller.view = view_handle; +} + +void Window_Create3D(int width, int height) { + launcherMode = false; + CGRect bounds = DoCreateWindow(); + + view_handle = [[CC3DView alloc] initWithFrame:bounds]; + view_handle.multipleTouchEnabled = true; + cc_controller.view = view_handle; + + Init3DLayer(); +} + +void Window_Destroy(void) { } + + +/*########################################################################################################################* +*--------------------------------------------------------GLContext--------------------------------------------------------* +*#########################################################################################################################*/ +#if (CC_GFX_BACKEND & CC_GFX_BACKEND_GL_MASK) +#include +#include + +static EAGLContext* ctx_handle; +static GLuint framebuffer; +static GLuint color_renderbuffer, depth_renderbuffer; +static int fb_width, fb_height; + +static void UpdateColorbuffer(void) { + CAEAGLLayer* layer = (CAEAGLLayer*)view_handle.layer; + glBindRenderbuffer(GL_RENDERBUFFER, color_renderbuffer); + + if (![ctx_handle renderbufferStorage:GL_RENDERBUFFER fromDrawable:layer]) + Logger_Abort("Failed to link renderbuffer to window"); +} + +static void UpdateDepthbuffer(void) { + int backingW = 0, backingH = 0; + + // In case layer dimensions are different + glBindRenderbuffer(GL_RENDERBUFFER, color_renderbuffer); + glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingW); + glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingH); + + // Shouldn't happen but just in case + if (backingW <= 0) backingW = Window_Main.Width; + if (backingH <= 0) backingH = Window_Main.Height; + + glBindRenderbuffer(GL_RENDERBUFFER, depth_renderbuffer); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24_OES, backingW, backingH); +} + +static void CreateFramebuffer(void) { + glGenFramebuffers(1, &framebuffer); + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); + + glGenRenderbuffers(1, &color_renderbuffer); + UpdateColorbuffer(); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, color_renderbuffer); + + glGenRenderbuffers(1, &depth_renderbuffer); + UpdateDepthbuffer(); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth_renderbuffer); + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) + Logger_Abort2(status, "Failed to create renderbuffer"); + + fb_width = Window_Main.Width; + fb_height = Window_Main.Height; +} + +void GLContext_Create(void) { + ctx_handle = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; + [EAGLContext setCurrentContext:ctx_handle]; + + // unlike other platforms, have to manually setup render framebuffer + CreateFramebuffer(); +} + +void GLContext_Update(void) { + // trying to update renderbuffer here results in garbage output, + // so do instead when layoutSubviews method is called +} + +static void GLContext_OnLayout(void) { + // only resize buffers when absolutely have to + if (fb_width == Window_Main.Width && fb_height == Window_Main.Height) return; + fb_width = Window_Main.Width; + fb_height = Window_Main.Height; + + UpdateColorbuffer(); + UpdateDepthbuffer(); +} + +void GLContext_Free(void) { + glDeleteRenderbuffers(1, &color_renderbuffer); color_renderbuffer = 0; + glDeleteRenderbuffers(1, &depth_renderbuffer); depth_renderbuffer = 0; + glDeleteFramebuffers(1, &framebuffer); framebuffer = 0; + + [EAGLContext setCurrentContext:Nil]; +} + +cc_bool GLContext_TryRestore(void) { return false; } +void* GLContext_GetAddress(const char* function) { return NULL; } + +cc_bool GLContext_SwapBuffers(void) { + static GLenum discards[] = { GL_DEPTH_ATTACHMENT }; + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); + glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, discards); + glBindRenderbuffer(GL_RENDERBUFFER, color_renderbuffer); + [ctx_handle presentRenderbuffer:GL_RENDERBUFFER]; + return true; +} + +void GLContext_SetVSync(cc_bool vsync) { } +void GLContext_GetApiInfo(cc_string* info) { } + + +@implementation CC3DView + ++ (Class)layerClass { + return [CAEAGLLayer class]; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + GLContext_OnLayout(); +} +@end + +static void Init3DLayer(void) { + // CAEAGLLayer - iOS 2.0 + CAEAGLLayer* layer = (CAEAGLLayer*)view_handle.layer; + + layer.opaque = YES; + layer.drawableProperties = + @{ + kEAGLDrawablePropertyRetainedBacking : [NSNumber numberWithBool:NO], + kEAGLDrawablePropertyColorFormat : kEAGLColorFormatRGBA8 + }; +} +#endif + + +/*########################################################################################################################* +*-------------------------------------------------------Framebuffer-------------------------------------------------------* +*#########################################################################################################################*/ +void Window_AllocFramebuffer(struct Bitmap* bmp, int width, int height) { + bmp->width = width; + bmp->height = height; + bmp->scan0 = (BitmapCol*)Mem_Alloc(width * height, BITMAPCOLOR_SIZE, "window pixels"); + + win_ctx = CGBitmapContextCreate(bmp->scan0, width, height, 8, width * 4, + CGColorSpaceCreateDeviceRGB(), kCGBitmapByteOrder32Host | kCGImageAlphaNoneSkipFirst); +} + +void Window_DrawFramebuffer(Rect2D r, struct Bitmap* bmp) { + CGImageRef image = CGBitmapContextCreateImage(win_ctx); + view_handle.layer.contents = CFBridgingRelease(image); +} + +void Window_FreeFramebuffer(struct Bitmap* bmp) { + Mem_Free(bmp->scan0); + CGContextRelease(win_ctx); +} +#endif + diff --git a/src/interop_ios.m b/src/interop_ios.m index 8f8ea63a2..989ecd01d 100644 --- a/src/interop_ios.m +++ b/src/interop_ios.m @@ -3,7 +3,6 @@ #include "Core.h" #if defined CC_BUILD_IOS -#include "_WindowBase.h" #include "Bitmap.h" #include "Input.h" #include "Platform.h" @@ -13,6 +12,9 @@ #include "Launcher.h" #include "Funcs.h" #include "Gui.h" +#include "Window.h" +#include "Event.h" +#include "Logger.h" #include #include #include @@ -29,229 +31,19 @@ #define TEXT_ATTRIBUTE_COLOR UITextAttributeTextColor #endif -// shared state with LBackend_ios.m -UITextField* kb_widget; -CGContextRef win_ctx; -UIView* view_handle; +// shared state with Window_ios.m +extern UIViewController* cc_controller; UIColor* ToUIColor(BitmapCol color, float A); NSString* ToNSString(const cc_string* text); -void LInput_SetKeyboardType(UITextField* fld, int flags); -void LInput_SetPlaceholder(UITextField* fld, const char* placeholder); +UIInterfaceOrientationMask SupportedOrientations(void); +void LogUnhandledNSErrors(NSException* ex); -@interface CCWindow : UIWindow -@end - -@interface CCViewController : UIViewController -@end - @interface CCAppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @end -static CCViewController* cc_controller; -static UIWindow* win_handle; -static cc_bool launcherMode; - -static void AddTouch(UITouch* t) { - CGPoint loc = [t locationInView:view_handle]; - int x = loc.x, y = loc.y; long ui_id = (long)t; - Platform_Log3("POINTER %x - DOWN %i,%i", &ui_id, &x, &y); - Input_AddTouch((long)t, loc.x, loc.y); -} - -static void UpdateTouch(UITouch* t) { - CGPoint loc = [t locationInView:view_handle]; - int x = loc.x, y = loc.y; long ui_id = (long)t; - Platform_Log3("POINTER %x - MOVE %i,%i", &ui_id, &x, &y); - Input_UpdateTouch((long)t, loc.x, loc.y); -} - -static void RemoveTouch(UITouch* t) { - CGPoint loc = [t locationInView:view_handle]; - int x = loc.x, y = loc.y; long ui_id = (long)t; - Platform_Log3("POINTER %x - UP %i,%i", &ui_id, &x, &y); - Input_RemoveTouch((long)t, loc.x, loc.y); -} - -static cc_bool landscape_locked; -static UIInterfaceOrientationMask SupportedOrientations(void) { - if (landscape_locked) - return UIInterfaceOrientationMaskLandscape; - return UIInterfaceOrientationMaskAll; -} - -static cc_bool fullscreen = true; -static void UpdateStatusBar(void) { - if ([cc_controller respondsToSelector:@selector(setNeedsStatusBarAppearanceUpdate)]) { - // setNeedsStatusBarAppearanceUpdate - iOS 7.0 - [cc_controller setNeedsStatusBarAppearanceUpdate]; - } else { - [[UIApplication sharedApplication] setStatusBarHidden:fullscreen withAnimation:UIStatusBarAnimationNone]; - } -} - -static CGRect GetViewFrame(void) { - UIScreen* screen = UIScreen.mainScreen; - return fullscreen ? screen.bounds : screen.applicationFrame; -} - -@implementation CCWindow - -- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event { - // touchesBegan:withEvent - iOS 2.0 - for (UITouch* t in touches) AddTouch(t); - - // clicking on the background should dismiss onscren keyboard - if (launcherMode) { [view_handle endEditing:NO]; } -} - -- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent *)event { - // touchesMoved:withEvent - iOS 2.0 - for (UITouch* t in touches) UpdateTouch(t); -} - -- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent *)event { - // touchesEnded:withEvent - iOS 2.0 - for (UITouch* t in touches) RemoveTouch(t); -} - -- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent *)event { - // touchesCancelled:withEvent - iOS 2.0 - for (UITouch* t in touches) RemoveTouch(t); -} - -- (BOOL)isOpaque { return YES; } -@end - - -@implementation CCViewController -- (UIInterfaceOrientationMask)supportedInterfaceOrientations { - // supportedInterfaceOrientations - iOS 6.0 - return SupportedOrientations(); -} - -- (BOOL)shouldAutorotate { - // shouldAutorotate - iOS 6.0 - return YES; -} - -- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)ori { - // shouldAutorotateToInterfaceOrientation - iOS 2.0 - if (landscape_locked && !(ori == UIInterfaceOrientationLandscapeLeft || ori == UIInterfaceOrientationLandscapeRight)) - return NO; - return YES; -} - -- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { - // viewWillTransitionToSize:withTransitionCoordinator - iOS 8.0 - Window_Main.Width = size.width; - Window_Main.Height = size.height; - - Event_RaiseVoid(&WindowEvents.Resized); - [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; -} - -// ==== UIDocumentPickerDelegate ==== -static FileDialogCallback open_dlg_callback; -static char save_buffer[FILENAME_SIZE]; -static cc_string save_path = String_FromArray(save_buffer); - -static void DeleteExportTempFile(void) { - if (!save_path.length) return; - - char path[NATIVE_STR_LEN]; - String_EncodeUtf8(path, &save_path); - unlink(path); - save_path.length = 0; -} - -- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url { - // documentPicker:didPickDocumentAtURL - iOS 8.0 - NSString* str = url.path; - const char* utf8 = str.UTF8String; - - char tmpBuffer[NATIVE_STR_LEN]; - cc_string tmp = String_FromArray(tmpBuffer); - String_AppendUtf8(&tmp, utf8, String_Length(utf8)); - - DeleteExportTempFile(); - if (!open_dlg_callback) return; - open_dlg_callback(&tmp); - open_dlg_callback = NULL; -} - -- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller { - // documentPickerWasCancelled - iOS 8.0 - DeleteExportTempFile(); -} - -static cc_bool kb_active; -- (void)keyboardDidShow:(NSNotification*)notification { - NSDictionary* info = notification.userInfo; - if (kb_active) return; - // TODO this is wrong - // TODO this doesn't actually trigger view resize??? - kb_active = true; - - double interval = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; - NSInteger curve = [[info objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue]; - CGRect kbFrame = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; - CGRect winFrame = view_handle.frame; - - cc_bool can_shift = true; - // would the active input widget be pushed offscreen? - if (kb_widget) { - can_shift = kb_widget.frame.origin.y > kbFrame.size.height; - } - if (can_shift) winFrame.origin.y = -kbFrame.size.height; - kb_widget = nil; - - Platform_LogConst("APPEAR"); - [UIView animateWithDuration:interval delay: 0.0 options:curve animations:^{ - view_handle.frame = winFrame; - } completion:nil]; -} - -- (void)keyboardDidHide:(NSNotification*)notification { - NSDictionary* info = notification.userInfo; - if (!kb_active) return; - kb_active = false; - kb_widget = nil; - - double interval = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; - NSInteger curve = [[info objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue]; - CGRect winFrame = view_handle.frame; - winFrame.origin.y = 0; - - Platform_LogConst("VANISH"); - [UIView animateWithDuration:interval delay: 0.0 options:curve animations:^{ - view_handle.frame = winFrame; - } completion:nil]; -} - -- (BOOL)prefersStatusBarHidden { - // prefersStatusBarHidden - iOS 7.0 - return fullscreen; -} - -- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures { - // preferredScreenEdgesDeferringSystemGestures - iOS 11.0 - // recent iOS versions have a 'bottom home bar', which when swiped up, - // switches out of ClassiCube and to the app list menu - // overriding this forces the user to swipe up twice, which should - // significantly the chance of accidentally triggering this gesture - return UIRectEdgeBottom; -} - -// == UIAlertViewDelegate == -static int alert_completed; -- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex { - alert_completed = true; -} -@end - @implementation CCAppDelegate - (void)runMainLoop { @@ -321,7 +113,7 @@ static void LogUnhandled(NSString* str) { } // TODO: Should really be handled elsewhere, in Logger or ErrorHandler -static void LogUnhandledNSErrors(NSException* ex) { +void LogUnhandledNSErrors(NSException* ex) { // last chance to log exception details before process dies LogUnhandled(@"About to die from unhandled NSException.."); LogUnhandled([ex name]); @@ -336,10 +128,6 @@ int main(int argc, char * argv[]) { } } -// iOS textfields manage ctrl+c/v -void Clipboard_GetText(cc_string* value) { } -void Clipboard_SetText(const cc_string* value) { } - /*########################################################################################################################* *------------------------------------------------------Common helpers--------------------------------------------------------* @@ -371,434 +159,6 @@ void Platform_Log(const char* msg, int len) { } -/*########################################################################################################################* -*---------------------------------------------------------Window----------------------------------------------------------* -*#########################################################################################################################*/ -// no cursor on iOS -void Cursor_GetRawPos(int* x, int* y) { *x = 0; *y = 0; } -void Cursor_SetPosition(int x, int y) { } -void Cursor_DoSetVisible(cc_bool visible) { } - -void Window_SetTitle(const cc_string* title) { - // TODO: Implement this somehow -} - -void Window_PreInit(void) { - DisplayInfo.CursorVisible = true; -} - -void Window_Init(void) { - //Window_Main.SoftKeyboard = SOFT_KEYBOARD_RESIZE; - // keyboard now shifts up - Window_Main.SoftKeyboard = SOFT_KEYBOARD_SHIFT; - Input_SetTouchMode(true); - Input.Sources = INPUT_SOURCE_NORMAL; - Gui_SetTouchUI(true); - - DisplayInfo.Depth = 32; - DisplayInfo.ScaleX = 1; // TODO dpi scale - DisplayInfo.ScaleY = 1; // TODO dpi scale - NSSetUncaughtExceptionHandler(LogUnhandledNSErrors); -} - -void Window_Free(void) { } - -static UIColor* CalcBackgroundColor(void) { - // default to purple if no themed background color yet - if (!Launcher_Theme.BackgroundColor) - return UIColor.purpleColor; - return ToUIColor(Launcher_Theme.BackgroundColor, 1.0f); -} - -static CGRect DoCreateWindow(void) { - // UIKeyboardWillShowNotification - iOS 2.0 - cc_controller = [CCViewController alloc]; - UpdateStatusBar(); - - CGRect bounds = GetViewFrame(); - win_handle = [[CCWindow alloc] initWithFrame:bounds]; - - win_handle.rootViewController = cc_controller; - win_handle.backgroundColor = CalcBackgroundColor(); - Window_Main.Exists = true; - Window_Main.UIScaleX = DEFAULT_UI_SCALE_X; - Window_Main.UIScaleY = DEFAULT_UI_SCALE_Y; - - Window_Main.Width = bounds.size.width; - Window_Main.Height = bounds.size.height; - Window_Main.SoftKeyboardInstant = true; - - NSNotificationCenter* notifications = NSNotificationCenter.defaultCenter; - [notifications addObserver:cc_controller selector:@selector(keyboardDidShow:) name:UIKeyboardWillShowNotification object:nil]; - [notifications addObserver:cc_controller selector:@selector(keyboardDidHide:) name:UIKeyboardWillHideNotification object:nil]; - return bounds; -} -void Window_SetSize(int width, int height) { } - -void Window_Show(void) { - [win_handle makeKeyAndVisible]; -} - -void Window_RequestClose(void) { - Event_RaiseVoid(&WindowEvents.Closing); -} - -void Window_ProcessEvents(float delta) { - SInt32 res; - // manually tick event queue - do { - res = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, TRUE); - } while (res == kCFRunLoopRunHandledSource); -} - -void Gamepads_Init(void) { - -} - -void Gamepads_Process(float delta) { } - -void ShowDialogCore(const char* title, const char* msg) { - // UIAlertController - iOS 8.0 - // UIAlertAction - iOS 8.0 - // UIAlertView - iOS 2.0 - Platform_LogConst(title); - Platform_LogConst(msg); - NSString* _title = [NSString stringWithCString:title encoding:NSASCIIStringEncoding]; - NSString* _msg = [NSString stringWithCString:msg encoding:NSASCIIStringEncoding]; - alert_completed = false; - -#ifdef TARGET_OS_TV - UIAlertController* alert = [UIAlertController alertControllerWithTitle:_title message:_msg preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction* okBtn = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction* act) { alert_completed = true; }]; - [alert addAction:okBtn]; - [cc_controller presentViewController:alert animated:YES completion: Nil]; -#else - UIAlertView* alert = [UIAlertView alloc]; - alert = [alert initWithTitle:_title message:_msg delegate:cc_controller cancelButtonTitle:@"OK" otherButtonTitles:nil]; - [alert show]; -#endif - - // TODO clicking outside message box crashes launcher - // loop until alert is closed TODO avoid sleeping - while (!alert_completed) { - Window_ProcessEvents(0.0); - Thread_Sleep(16); - } -} - - -@interface CCKBController : NSObject -@end - -@implementation CCKBController -- (void)handleTextChanged:(id)sender { - UITextField* src = (UITextField*)sender; - const char* str = src.text.UTF8String; - - char tmpBuffer[NATIVE_STR_LEN]; - cc_string tmp = String_FromArray(tmpBuffer); - String_AppendUtf8(&tmp, str, String_Length(str)); - - Event_RaiseString(&InputEvents.TextChanged, &tmp); -} - -// === UITextFieldDelegate === -- (BOOL)textFieldShouldReturn:(UITextField *)textField { - // textFieldShouldReturn - iOS 2.0 - Input_SetPressed(CCKEY_ENTER); - Input_SetReleased(CCKEY_ENTER); - return YES; -} -@end - -static UITextField* text_input; -static CCKBController* kb_controller; - -void OnscreenKeyboard_Open(struct OpenKeyboardArgs* args) { - if (!kb_controller) { - kb_controller = [[CCKBController alloc] init]; - CFBridgingRetain(kb_controller); // prevent GC TODO even needed? - } - DisplayInfo.ShowingSoftKeyboard = true; - - text_input = [[UITextField alloc] initWithFrame:CGRectZero]; - text_input.hidden = YES; - text_input.delegate = kb_controller; - [text_input addTarget:kb_controller action:@selector(handleTextChanged:) forControlEvents:UIControlEventEditingChanged]; - - LInput_SetKeyboardType(text_input, args->type); - LInput_SetPlaceholder(text_input, args->placeholder); - - [view_handle addSubview:text_input]; - [text_input becomeFirstResponder]; -} - -void OnscreenKeyboard_SetText(const cc_string* text) { - NSString* str = ToNSString(text); - NSString* cur = text_input.text; - - // otherwise on iOS 5, this causes an infinite loop - if (cur && [str isEqualToString:cur]) return; - text_input.text = str; -} - -void OnscreenKeyboard_Close(void) { - DisplayInfo.ShowingSoftKeyboard = false; - [text_input resignFirstResponder]; -} - -int Window_GetWindowState(void) { - return fullscreen ? WINDOW_STATE_FULLSCREEN : WINDOW_STATE_NORMAL; -} - -static void ToggleFullscreen(cc_bool isFullscreen) { - fullscreen = isFullscreen; - UpdateStatusBar(); - view_handle.frame = GetViewFrame(); -} - -cc_result Window_EnterFullscreen(void) { - ToggleFullscreen(true); return 0; -} -cc_result Window_ExitFullscreen(void) { - ToggleFullscreen(false); return 0; -} -int Window_IsObscured(void) { return 0; } - -void Window_EnableRawMouse(void) { DefaultEnableRawMouse(); } -void Window_UpdateRawMouse(void) { } -void Window_DisableRawMouse(void) { DefaultDisableRawMouse(); } - -void Window_LockLandscapeOrientation(cc_bool lock) { - // attemptRotationToDeviceOrientation - iOS 5.0 - // TODO doesn't work properly.. setting 'UIInterfaceOrientationUnknown' apparently - // restores orientation, but doesn't actually do that when I tried it - if (lock) { - //NSInteger ori = lock ? UIInterfaceOrientationLandscapeRight : UIInterfaceOrientationUnknown; - NSInteger ori = UIInterfaceOrientationLandscapeRight; - UIDevice* device = UIDevice.currentDevice; - NSNumber* value = [NSNumber numberWithInteger:ori]; - [device setValue:value forKey:@"orientation"]; - } - - landscape_locked = lock; - [UIViewController attemptRotationToDeviceOrientation]; -} - -cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* args) { - // UIDocumentPickerViewController - iOS 8.0 - // see the custom UTITypes declared in Info.plist - NSDictionary* fileExt_map = - @{ - @".cw" : @"com.classicube.client.ios-cw", - @".dat" : @"com.classicube.client.ios-dat", - @".lvl" : @"com.classicube.client.ios-lvl", - @".fcm" : @"com.classicube.client.ios-fcm", - @".zip" : @"public.zip-archive" - }; - NSMutableArray* types = [NSMutableArray array]; - const char* const* filters = args->filters; - - for (int i = 0; filters[i]; i++) - { - NSString* fileExt = [NSString stringWithUTF8String:filters[i]]; - NSString* utType = [fileExt_map objectForKey:fileExt]; - if (utType) [types addObject:utType]; - } - - UIDocumentPickerViewController* dlg; - dlg = [UIDocumentPickerViewController alloc]; - dlg = [dlg initWithDocumentTypes:types inMode:UIDocumentPickerModeOpen]; - //dlg = [dlg initWithDocumentTypes:types inMode:UIDocumentPickerModeImport]; - - open_dlg_callback = args->Callback; - dlg.delegate = cc_controller; - [cc_controller presentViewController:dlg animated:YES completion: Nil]; - return 0; // TODO still unfinished -} - -cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args) { - if (!args->defaultName.length) return SFD_ERR_NEED_DEFAULT_NAME; - // UIDocumentPickerViewController - iOS 8.0 - - // save the item to a temp file, which is then (usually) later deleted by picker callbacks - Directory_Create(FILEPATH_RAW("Exported")); - - save_path.length = 0; - String_Format2(&save_path, "Exported/%s%c", &args->defaultName, args->filters[0]); - args->Callback(&save_path); - - NSString* str = ToNSString(&save_path); - NSURL* url = [NSURL fileURLWithPath:str isDirectory:NO]; - - UIDocumentPickerViewController* dlg; - dlg = [UIDocumentPickerViewController alloc]; - dlg = [dlg initWithURL:url inMode:UIDocumentPickerModeExportToService]; - - dlg.delegate = cc_controller; - [cc_controller presentViewController:dlg animated:YES completion: Nil]; - return 0; -} - - -/*#########################################################################################################################* - *-----------------------------------------------------Window creation-----------------------------------------------------* - *#########################################################################################################################*/ -@interface CC3DView : UIView -@end -static void Init3DLayer(void); - -void Window_Create2D(int width, int height) { - launcherMode = true; - CGRect bounds = DoCreateWindow(); - - view_handle = [[UIView alloc] initWithFrame:bounds]; - view_handle.multipleTouchEnabled = true; - cc_controller.view = view_handle; -} - -void Window_Create3D(int width, int height) { - launcherMode = false; - CGRect bounds = DoCreateWindow(); - - view_handle = [[CC3DView alloc] initWithFrame:bounds]; - view_handle.multipleTouchEnabled = true; - cc_controller.view = view_handle; - - Init3DLayer(); -} - -void Window_Destroy(void) { } - - -/*########################################################################################################################* -*--------------------------------------------------------GLContext--------------------------------------------------------* -*#########################################################################################################################*/ -#if (CC_GFX_BACKEND & CC_GFX_BACKEND_GL_MASK) -#include -#include - -static EAGLContext* ctx_handle; -static GLuint framebuffer; -static GLuint color_renderbuffer, depth_renderbuffer; -static int fb_width, fb_height; - -static void UpdateColorbuffer(void) { - CAEAGLLayer* layer = (CAEAGLLayer*)view_handle.layer; - glBindRenderbuffer(GL_RENDERBUFFER, color_renderbuffer); - - if (![ctx_handle renderbufferStorage:GL_RENDERBUFFER fromDrawable:layer]) - Logger_Abort("Failed to link renderbuffer to window"); -} - -static void UpdateDepthbuffer(void) { - int backingW = 0, backingH = 0; - - // In case layer dimensions are different - glBindRenderbuffer(GL_RENDERBUFFER, color_renderbuffer); - glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingW); - glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingH); - - // Shouldn't happen but just in case - if (backingW <= 0) backingW = Window_Main.Width; - if (backingH <= 0) backingH = Window_Main.Height; - - glBindRenderbuffer(GL_RENDERBUFFER, depth_renderbuffer); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24_OES, backingW, backingH); -} - -static void CreateFramebuffer(void) { - glGenFramebuffers(1, &framebuffer); - glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); - - glGenRenderbuffers(1, &color_renderbuffer); - UpdateColorbuffer(); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, color_renderbuffer); - - glGenRenderbuffers(1, &depth_renderbuffer); - UpdateDepthbuffer(); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth_renderbuffer); - - GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - if (status != GL_FRAMEBUFFER_COMPLETE) - Logger_Abort2(status, "Failed to create renderbuffer"); - - fb_width = Window_Main.Width; - fb_height = Window_Main.Height; -} - -void GLContext_Create(void) { - ctx_handle = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; - [EAGLContext setCurrentContext:ctx_handle]; - - // unlike other platforms, have to manually setup render framebuffer - CreateFramebuffer(); -} - -void GLContext_Update(void) { - // trying to update renderbuffer here results in garbage output, - // so do instead when layoutSubviews method is called -} - -static void GLContext_OnLayout(void) { - // only resize buffers when absolutely have to - if (fb_width == Window_Main.Width && fb_height == Window_Main.Height) return; - fb_width = Window_Main.Width; - fb_height = Window_Main.Height; - - UpdateColorbuffer(); - UpdateDepthbuffer(); -} - -void GLContext_Free(void) { - glDeleteRenderbuffers(1, &color_renderbuffer); color_renderbuffer = 0; - glDeleteRenderbuffers(1, &depth_renderbuffer); depth_renderbuffer = 0; - glDeleteFramebuffers(1, &framebuffer); framebuffer = 0; - - [EAGLContext setCurrentContext:Nil]; -} - -cc_bool GLContext_TryRestore(void) { return false; } -void* GLContext_GetAddress(const char* function) { return NULL; } - -cc_bool GLContext_SwapBuffers(void) { - static GLenum discards[] = { GL_DEPTH_ATTACHMENT }; - glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); - glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, discards); - glBindRenderbuffer(GL_RENDERBUFFER, color_renderbuffer); - [ctx_handle presentRenderbuffer:GL_RENDERBUFFER]; - return true; -} -void GLContext_SetVSync(cc_bool vsync) { } -void GLContext_GetApiInfo(cc_string* info) { } - - -@implementation CC3DView - -+ (Class)layerClass { - return [CAEAGLLayer class]; -} - -- (void)layoutSubviews { - [super layoutSubviews]; - GLContext_OnLayout(); -} -@end - -static void Init3DLayer(void) { - // CAEAGLLayer - iOS 2.0 - CAEAGLLayer* layer = (CAEAGLLayer*)view_handle.layer; - - layer.opaque = YES; - layer.drawableProperties = - @{ - kEAGLDrawablePropertyRetainedBacking : [NSNumber numberWithBool:NO], - kEAGLDrawablePropertyColorFormat : kEAGLColorFormatRGBA8 - }; -} -#endif - - /*########################################################################################################################* *--------------------------------------------------------Updater----------------------------------------------------------* *#########################################################################################################################*/ @@ -1165,23 +525,4 @@ void interop_SysTextDraw(struct DrawTextArgs* args, struct Context2D* ctx, int x }*/ #endif - -void Window_AllocFramebuffer(struct Bitmap* bmp, int width, int height) { - bmp->width = width; - bmp->height = height; - bmp->scan0 = (BitmapCol*)Mem_Alloc(width * height, BITMAPCOLOR_SIZE, "window pixels"); - - win_ctx = CGBitmapContextCreate(bmp->scan0, width, height, 8, width * 4, - CGColorSpaceCreateDeviceRGB(), kCGBitmapByteOrder32Host | kCGImageAlphaNoneSkipFirst); -} - -void Window_DrawFramebuffer(Rect2D r, struct Bitmap* bmp) { - CGImageRef image = CGBitmapContextCreateImage(win_ctx); - view_handle.layer.contents = CFBridgingRelease(image); -} - -void Window_FreeFramebuffer(struct Bitmap* bmp) { - Mem_Free(bmp->scan0); - CGContextRelease(win_ctx); -} #endif