yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
main.mm
Go to the documentation of this file.
1// yaze iOS Application
2// Uses SDL2 and ImGui
3//
4// This file implements the iOS-specific entry point and UI management for yaze.
5// It integrates with the modern Controller API and EditorManager infrastructure.
6//
7// Key components:
8// - AppViewController: Main view controller managing the MTKView and Controller lifecycle
9// - AppDelegate: iOS app lifecycle management and document picker integration
10// - Touch gesture handlers: Maps iOS gestures to ImGui input events
11//
12// Updated to use:
13// - Modern Controller::OnEntry/OnLoad/DoRender API
14// - EditorManager for ROM management (no SharedRom singleton)
15// - Proper SDL2 initialization for iOS
16// - Updated ImGui backends (SDL2 renderer, not Metal directly)
17
18#import <Foundation/Foundation.h>
19
20#if TARGET_OS_OSX
21#import <Cocoa/Cocoa.h>
22#else
23#import <UIKit/UIKit.h>
24#endif
25
26#import <Metal/Metal.h>
27#import <MetalKit/MetalKit.h>
28#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
29
30#include <string>
31#include <vector>
32#include <algorithm>
33
34#include "app/controller.h"
37#include "app/platform/window.h"
38#include "app/rom.h"
39
40#include <SDL.h>
41
42#ifdef main
43#undef main
44#endif
45
47#include "imgui/backends/imgui_impl_sdl2.h"
48#include "imgui/backends/imgui_impl_sdlrenderer2.h"
49#include "imgui/imgui.h"
50
51// ----------------------------------------------------------------------------
52// AppViewController
53// ----------------------------------------------------------------------------
54
55@implementation AppViewController
56
57- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil
58 bundle:(nullable NSBundle *)nibBundleOrNil {
59 self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
60
61 _device = MTLCreateSystemDefaultDevice();
62 _commandQueue = [_device newCommandQueue];
63
64 if (!self.device) {
65 NSLog(@"Metal is not supported");
66 abort();
67 }
68
69 // Initialize SDL for iOS
70 SDL_SetMainReady();
71#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
72 SDL_iOSSetEventPump(SDL_TRUE);
73#endif
74
75 // Parse command line arguments
76 int argc = NSProcessInfo.processInfo.arguments.count;
77 char **argv = new char *[argc];
78 for (int i = 0; i < argc; i++) {
79 NSString *arg = NSProcessInfo.processInfo.arguments[i];
80 const char *cString = [arg UTF8String];
81 argv[i] = new char[strlen(cString) + 1];
82 strcpy(argv[i], cString);
83 }
84
85 std::string rom_filename = "";
86 if (argc > 1) {
87 rom_filename = argv[1];
88 }
89
90 // Clean up argv
91 for (int i = 0; i < argc; i++) {
92 delete[] argv[i];
93 }
94 delete[] argv;
95
96#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
97 SDL_iOSSetEventPump(SDL_FALSE);
98#endif
99
100 // Enable native IME
101 SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
102
103 // Create and initialize controller with modern API
104 _controller = new yaze::Controller();
105 auto init_status = _controller->OnEntry(rom_filename);
106 if (!init_status.ok()) {
107 NSLog(@"Failed to initialize controller: %s", init_status.message().data());
108 abort();
109 }
110
111 // Setup gesture recognizers
112 _hoverGestureRecognizer =
113 [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(HoverGesture:)];
114 [self.view addGestureRecognizer:_hoverGestureRecognizer];
115
116 _pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self
117 action:@selector(HandlePinch:)];
118 [self.view addGestureRecognizer:_pinchRecognizer];
119
120 _longPressRecognizer =
121 [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
122 [self.view addGestureRecognizer:_longPressRecognizer];
123
124 _swipeRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self
125 action:@selector(HandleSwipe:)];
126 _swipeRecognizer.direction =
127 UISwipeGestureRecognizerDirectionRight | UISwipeGestureRecognizerDirectionLeft;
128 [self.view addGestureRecognizer:_swipeRecognizer];
129
130 return self;
131}
132
133- (MTKView *)mtkView {
134 return (MTKView *)self.view;
135}
136
137- (void)loadView {
138 self.view = [[MTKView alloc] initWithFrame:CGRectMake(0, 0, 1200, 720)];
139}
140
141- (void)viewDidLoad {
142 [super viewDidLoad];
143
144 self.mtkView.device = self.device;
145 self.mtkView.delegate = self;
146}
147
148- (void)drawInMTKView:(MTKView *)view {
149 if (!_controller->IsActive()) return;
150
151 // Handle SDL input events
152 _controller->OnInput();
153
154 // Update ImGui display size for iOS
155 ImGuiIO &io = ImGui::GetIO();
156 io.DisplaySize.x = view.bounds.size.width;
157 io.DisplaySize.y = view.bounds.size.height;
158
159 CGFloat framebufferScale = view.window.screen.scale ?: UIScreen.mainScreen.scale;
160 io.DisplayFramebufferScale = ImVec2(framebufferScale, framebufferScale);
161
162 // Process frame and render using Controller's API
163 auto load_status = _controller->OnLoad();
164 if (!load_status.ok()) {
165 NSLog(@"Controller OnLoad failed: %s", load_status.message().data());
166 return;
167 }
168
169 _controller->DoRender();
170}
171
172- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size {
173}
174
175// ----------------------------------------------------------------------------
176// Input processing
177// ----------------------------------------------------------------------------
178
179#if !TARGET_OS_OSX
180
181// This touch mapping is super cheesy/hacky. We treat any touch on the screen
182// as if it were a depressed left mouse button, and we don't bother handling
183// multitouch correctly at all. This causes the "cursor" to behave very erratically
184// when there are multiple active touches. But for demo purposes, single-touch
185// interaction actually works surprisingly well.
186- (void)UpdateIOWithTouchEvent:(UIEvent *)event {
187 UITouch *anyTouch = event.allTouches.anyObject;
188 CGPoint touchLocation = [anyTouch locationInView:self.view];
189 ImGuiIO &io = ImGui::GetIO();
190 io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen);
191 io.AddMousePosEvent(touchLocation.x, touchLocation.y);
192
193 BOOL hasActiveTouch = NO;
194 for (UITouch *touch in event.allTouches) {
195 if (touch.phase != UITouchPhaseEnded && touch.phase != UITouchPhaseCancelled) {
196 hasActiveTouch = YES;
197 break;
198 }
199 }
200 io.AddMouseButtonEvent(0, hasActiveTouch);
201}
202
203- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
204 [self UpdateIOWithTouchEvent:event];
205}
206- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
207 [self UpdateIOWithTouchEvent:event];
208}
209- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
210 [self UpdateIOWithTouchEvent:event];
211}
212- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
213 [self UpdateIOWithTouchEvent:event];
214}
215
216- (void)HoverGesture:(UIHoverGestureRecognizer *)gesture {
217 ImGuiIO &io = ImGui::GetIO();
218 io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen);
219 // Cast to UIGestureRecognizer to UIGestureRecognizer to get locationInView
220 UIGestureRecognizer *gestureRecognizer = (UIGestureRecognizer *)gesture;
221 if (gesture.zOffset < 0.50) {
222 io.AddMousePosEvent([gestureRecognizer locationInView:self.view].x,
223 [gestureRecognizer locationInView:self.view].y);
224 }
225}
226
227- (void)HandlePinch:(UIPinchGestureRecognizer *)gesture {
228 ImGuiIO &io = ImGui::GetIO();
229 io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen);
230 io.AddMouseWheelEvent(0.0f, gesture.scale);
231 UIGestureRecognizer *gestureRecognizer = (UIGestureRecognizer *)gesture;
232 io.AddMousePosEvent([gestureRecognizer locationInView:self.view].x,
233 [gestureRecognizer locationInView:self.view].y);
234}
235
236- (void)HandleSwipe:(UISwipeGestureRecognizer *)gesture {
237 ImGuiIO &io = ImGui::GetIO();
238 io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen);
239 if (gesture.direction == UISwipeGestureRecognizerDirectionRight) {
240 io.AddMouseWheelEvent(1.0f, 0.0f); // Swipe Right
241 } else if (gesture.direction == UISwipeGestureRecognizerDirectionLeft) {
242 io.AddMouseWheelEvent(-1.0f, 0.0f); // Swipe Left
243 }
244 UIGestureRecognizer *gestureRecognizer = (UIGestureRecognizer *)gesture;
245 io.AddMousePosEvent([gestureRecognizer locationInView:self.view].x,
246 [gestureRecognizer locationInView:self.view].y);
247}
248
249- (void)handleLongPress:(UILongPressGestureRecognizer *)gesture {
250 ImGuiIO &io = ImGui::GetIO();
251 io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen);
252 io.AddMouseButtonEvent(1, gesture.state == UIGestureRecognizerStateBegan);
253 UIGestureRecognizer *gestureRecognizer = (UIGestureRecognizer *)gesture;
254 io.AddMousePosEvent([gestureRecognizer locationInView:self.view].x,
255 [gestureRecognizer locationInView:self.view].y);
256}
257
258#endif
259
260@end
261
262// ----------------------------------------------------------------------------
263// AppDelegate
264// ----------------------------------------------------------------------------
265
266#if TARGET_OS_OSX
267
268@interface AppDelegate : NSObject <NSApplicationDelegate>
269@property(nonatomic, strong) NSWindow *window;
270@end
271
272@implementation AppDelegate
273
274- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
275 return YES;
276}
277
278- (instancetype)init {
279 if (self = [super init]) {
280 NSViewController *rootViewController = [[AppViewController alloc] initWithNibName:nil
281 bundle:nil];
282 self.window = [[NSWindow alloc]
283 initWithContentRect:NSZeroRect
284 styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
285 NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable
286 backing:NSBackingStoreBuffered
287 defer:NO];
288 self.window.contentViewController = rootViewController;
289 [self.window center];
290 [self.window makeKeyAndOrderFront:self];
291 }
292 return self;
293}
294
295@end
296
297#else
298
299@implementation AppDelegate
300
301- (BOOL)application:(UIApplication *)application
302 didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
303 UIViewController *rootViewController = [[AppViewController alloc] init];
304 self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
305 self.window.rootViewController = rootViewController;
306 [self.window makeKeyAndVisible];
307 return YES;
308}
309
310- (void)applicationWillTerminate:(UIApplication *)application {
311 // Controller OnExit handles cleanup
312 AppViewController *viewController = (AppViewController *)self.window.rootViewController;
313 if (viewController.controller) {
314 viewController.controller->OnExit();
315 delete viewController.controller;
316 viewController.controller = nullptr;
317 }
318}
319
320- (void)PresentDocumentPickerWithCompletionHandler:
321 (void (^)(NSString *selectedFile))completionHandler {
322 self.completionHandler = completionHandler;
323
324 NSArray *documentTypes = @[ [UTType typeWithIdentifier:@"org.halext.sfc"] ];
325 UIViewController *rootViewController = self.window.rootViewController;
326 _documentPicker =
327 [[UIDocumentPickerViewController alloc] initForOpeningContentTypes:documentTypes];
328 _documentPicker.delegate = self;
329 _documentPicker.modalPresentationStyle = UIModalPresentationFormSheet;
330
331 [rootViewController presentViewController:_documentPicker animated:YES completion:nil];
332}
333
334- (void)documentPicker:(UIDocumentPickerViewController *)controller
335 didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
336 NSURL *selectedFileURL = [urls firstObject];
337
338 if (self.completionHandler) {
339 if (selectedFileURL) {
340 self.completionHandler(selectedFileURL.path);
341 std::string fileName = std::string([selectedFileURL.path UTF8String]);
342
343 // Extract the data from the file
344 [selectedFileURL startAccessingSecurityScopedResource];
345
346 auto data = [NSData dataWithContentsOfURL:selectedFileURL];
347 uint8_t *bytes = (uint8_t *)[data bytes];
348 size_t size = [data length];
349
350 std::vector<uint8_t> rom_data;
351 rom_data.resize(size);
352 std::copy(bytes, bytes + size, rom_data.begin());
353
354 // Load ROM using modern API
355 // Get the AppViewController which has the controller
356 AppViewController *viewController = (AppViewController *)self.window.rootViewController;
357 if (viewController && viewController.controller) {
358 // Access the controller's EditorManager to get the current ROM
359 auto* current_rom = viewController.controller->GetCurrentRom();
360 if (current_rom) {
361 auto load_status = current_rom->LoadFromData(rom_data);
362 if (load_status.ok()) {
363 current_rom->set_filename(fileName);
364 NSLog(@"ROM loaded successfully from %s", fileName.c_str());
365 } else {
366 NSLog(@"Failed to load ROM: %s", load_status.message().data());
367 }
368 } else {
369 NSLog(@"No ROM instance available");
370 }
371 } else {
372 NSLog(@"Controller not available");
373 }
374
375 [selectedFileURL stopAccessingSecurityScopedResource];
376 } else {
377 self.completionHandler(@"");
378 }
379 }
380 self.completionHandler = nil;
381 [controller dismissViewControllerAnimated:YES completion:nil];
382}
383
384- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller {
385 if (self.completionHandler) {
386 self.completionHandler(@"");
387 }
388 self.completionHandler = nil;
389 [controller dismissViewControllerAnimated:YES completion:nil];
390}
391
392@end
393
394#endif
395
396// ----------------------------------------------------------------------------
397// Application main() function
398// ----------------------------------------------------------------------------
399
400#if TARGET_OS_OSX
401int main(int argc, const char *argv[]) { return NSApplicationMain(argc, argv); }
402#else
403
404int main(int argc, char *argv[]) {
405 @autoreleasepool {
406 return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
407 }
408}
409
410#endif
Main controller for the application.
Definition controller.h:24