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"
35#include "app/application.h"
38#include "app/platform/window.h"
39#include "rom/rom.h"
40
42
43#ifdef main
44#undef main
45#endif
46
48#include "imgui/backends/imgui_impl_sdl2.h"
49#include "imgui/backends/imgui_impl_sdlrenderer2.h"
50#include "imgui/imgui.h"
51
52// ----------------------------------------------------------------------------
53// AppViewController
54// ----------------------------------------------------------------------------
55
56@implementation AppViewController
57
58- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil
59 bundle:(nullable NSBundle *)nibBundleOrNil {
60 self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
61
62 _device = MTLCreateSystemDefaultDevice();
63 _commandQueue = [_device newCommandQueue];
64
65 if (!self.device) {
66 NSLog(@"Metal is not supported");
67 abort();
68 }
69
70 // Initialize SDL for iOS
71 SDL_SetMainReady();
72#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
73 SDL_iOSSetEventPump(SDL_TRUE);
74#endif
75
76 // Parse command line arguments
77 int argc = NSProcessInfo.processInfo.arguments.count;
78 char **argv = new char *[argc];
79 for (int i = 0; i < argc; i++) {
80 NSString *arg = NSProcessInfo.processInfo.arguments[i];
81 const char *cString = [arg UTF8String];
82 argv[i] = new char[strlen(cString) + 1];
83 strcpy(argv[i], cString);
84 }
85
86 std::string rom_filename = "";
87 if (argc > 1) {
88 rom_filename = argv[1];
89 }
90
91 // Clean up argv
92 for (int i = 0; i < argc; i++) {
93 delete[] argv[i];
94 }
95 delete[] argv;
96
97#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
98 SDL_iOSSetEventPump(SDL_FALSE);
99#endif
100
101 // Enable native IME
102 SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
103
104 // Initialize Application singleton
105 yaze::AppConfig config;
106 config.rom_file = rom_filename;
108
109 // Keep local reference to controller for property compatibility
110 self.controller = yaze::Application::Instance().GetController();
111
112 if (!self.controller) {
113 NSLog(@"Failed to initialize application controller");
114 abort();
115 }
116
117 // Setup gesture recognizers
118 _hoverGestureRecognizer =
119 [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(HoverGesture:)];
120 [self.view addGestureRecognizer:_hoverGestureRecognizer];
121
122 _pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self
123 action:@selector(HandlePinch:)];
124 [self.view addGestureRecognizer:_pinchRecognizer];
125
126 _longPressRecognizer =
127 [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
128 [self.view addGestureRecognizer:_longPressRecognizer];
129
130 _swipeRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self
131 action:@selector(HandleSwipe:)];
132 _swipeRecognizer.direction =
133 UISwipeGestureRecognizerDirectionRight | UISwipeGestureRecognizerDirectionLeft;
134 [self.view addGestureRecognizer:_swipeRecognizer];
135
136 return self;
137}
138
139- (MTKView *)mtkView {
140 return (MTKView *)self.view;
141}
142
143- (void)loadView {
144 self.view = [[MTKView alloc] initWithFrame:CGRectMake(0, 0, 1200, 720)];
145}
146
147- (void)viewDidLoad {
148 [super viewDidLoad];
149
150 self.mtkView.device = self.device;
151 self.mtkView.delegate = self;
152}
153
154- (void)drawInMTKView:(MTKView *)view {
155 auto& app = yaze::Application::Instance();
156 if (!app.IsReady() || !app.GetController()->IsActive()) return;
157
158 // Update ImGui display size for iOS before Tick
159 // Note: Tick() calls OnInput() then OnLoad() (NewFrame) then DoRender()
160 // We want to update IO before NewFrame.
161 // OnInput handles SDL events.
162
163 ImGuiIO &io = ImGui::GetIO();
164 io.DisplaySize.x = view.bounds.size.width;
165 io.DisplaySize.y = view.bounds.size.height;
166
167 CGFloat framebufferScale = view.window.screen.scale ?: UIScreen.mainScreen.scale;
168 io.DisplayFramebufferScale = ImVec2(framebufferScale, framebufferScale);
169
170 app.Tick();
171}
172
173- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size {
174}
175
176// ----------------------------------------------------------------------------
177// Input processing
178// ----------------------------------------------------------------------------
179
180#if !TARGET_OS_OSX
181
182// This touch mapping is super cheesy/hacky. We treat any touch on the screen
183// as if it were a depressed left mouse button, and we don't bother handling
184// multitouch correctly at all. This causes the "cursor" to behave very erratically
185// when there are multiple active touches. But for demo purposes, single-touch
186// interaction actually works surprisingly well.
187- (void)UpdateIOWithTouchEvent:(UIEvent *)event {
188 UITouch *anyTouch = event.allTouches.anyObject;
189 CGPoint touchLocation = [anyTouch locationInView:self.view];
190 ImGuiIO &io = ImGui::GetIO();
191 io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen);
192 io.AddMousePosEvent(touchLocation.x, touchLocation.y);
193
194 BOOL hasActiveTouch = NO;
195 for (UITouch *touch in event.allTouches) {
196 if (touch.phase != UITouchPhaseEnded && touch.phase != UITouchPhaseCancelled) {
197 hasActiveTouch = YES;
198 break;
199 }
200 }
201 io.AddMouseButtonEvent(0, hasActiveTouch);
202}
203
204- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
205 [self UpdateIOWithTouchEvent:event];
206}
207- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
208 [self UpdateIOWithTouchEvent:event];
209}
210- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
211 [self UpdateIOWithTouchEvent:event];
212}
213- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
214 [self UpdateIOWithTouchEvent:event];
215}
216
217- (void)HoverGesture:(UIHoverGestureRecognizer *)gesture {
218 ImGuiIO &io = ImGui::GetIO();
219 io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen);
220 // Cast to UIGestureRecognizer to UIGestureRecognizer to get locationInView
221 UIGestureRecognizer *gestureRecognizer = (UIGestureRecognizer *)gesture;
222 if (gesture.zOffset < 0.50) {
223 io.AddMousePosEvent([gestureRecognizer locationInView:self.view].x,
224 [gestureRecognizer locationInView:self.view].y);
225 }
226}
227
228- (void)HandlePinch:(UIPinchGestureRecognizer *)gesture {
229 ImGuiIO &io = ImGui::GetIO();
230 io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen);
231 io.AddMouseWheelEvent(0.0f, gesture.scale);
232 UIGestureRecognizer *gestureRecognizer = (UIGestureRecognizer *)gesture;
233 io.AddMousePosEvent([gestureRecognizer locationInView:self.view].x,
234 [gestureRecognizer locationInView:self.view].y);
235}
236
237- (void)HandleSwipe:(UISwipeGestureRecognizer *)gesture {
238 ImGuiIO &io = ImGui::GetIO();
239 io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen);
240 if (gesture.direction == UISwipeGestureRecognizerDirectionRight) {
241 io.AddMouseWheelEvent(1.0f, 0.0f); // Swipe Right
242 } else if (gesture.direction == UISwipeGestureRecognizerDirectionLeft) {
243 io.AddMouseWheelEvent(-1.0f, 0.0f); // Swipe Left
244 }
245 UIGestureRecognizer *gestureRecognizer = (UIGestureRecognizer *)gesture;
246 io.AddMousePosEvent([gestureRecognizer locationInView:self.view].x,
247 [gestureRecognizer locationInView:self.view].y);
248}
249
250- (void)handleLongPress:(UILongPressGestureRecognizer *)gesture {
251 ImGuiIO &io = ImGui::GetIO();
252 io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen);
253 io.AddMouseButtonEvent(1, gesture.state == UIGestureRecognizerStateBegan);
254 UIGestureRecognizer *gestureRecognizer = (UIGestureRecognizer *)gesture;
255 io.AddMousePosEvent([gestureRecognizer locationInView:self.view].x,
256 [gestureRecognizer locationInView:self.view].y);
257}
258
259#endif
260
261@end
262
263// ----------------------------------------------------------------------------
264// AppDelegate
265// ----------------------------------------------------------------------------
266
267#if TARGET_OS_OSX
268
269@interface AppDelegate : NSObject <NSApplicationDelegate>
270@property(nonatomic, strong) NSWindow *window;
271@end
272
273@implementation AppDelegate
274
275- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
276 return YES;
277}
278
279- (instancetype)init {
280 if (self = [super init]) {
281 NSViewController *rootViewController = [[AppViewController alloc] initWithNibName:nil
282 bundle:nil];
283 self.window = [[NSWindow alloc]
284 initWithContentRect:NSZeroRect
285 styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
286 NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable
287 backing:NSBackingStoreBuffered
288 defer:NO];
289 self.window.contentViewController = rootViewController;
290 [self.window center];
291 [self.window makeKeyAndOrderFront:self];
292 }
293 return self;
294}
295
296@end
297
298#else
299
300@implementation AppDelegate
301
302- (BOOL)application:(UIApplication *)application
303 didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
304 UIViewController *rootViewController = [[AppViewController alloc] init];
305 self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
306 self.window.rootViewController = rootViewController;
307 [self.window makeKeyAndVisible];
308 return YES;
309}
310
311- (void)applicationWillTerminate:(UIApplication *)application {
313}
314
315- (void)PresentDocumentPickerWithCompletionHandler:
316 (void (^)(NSString *selectedFile))completionHandler {
317 self.completionHandler = completionHandler;
318
319 NSArray *documentTypes = @[ [UTType typeWithIdentifier:@"org.halext.sfc"] ];
320 UIViewController *rootViewController = self.window.rootViewController;
321 _documentPicker =
322 [[UIDocumentPickerViewController alloc] initForOpeningContentTypes:documentTypes];
323 _documentPicker.delegate = self;
324 _documentPicker.modalPresentationStyle = UIModalPresentationFormSheet;
325
326 [rootViewController presentViewController:_documentPicker animated:YES completion:nil];
327}
328
329- (void)documentPicker:(UIDocumentPickerViewController *)controller
330 didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
331 NSURL *selectedFileURL = [urls firstObject];
332
333 if (self.completionHandler) {
334 if (selectedFileURL) {
335 self.completionHandler(selectedFileURL.path);
336
337 // Create a temporary file path
338 NSString *tempDir = NSTemporaryDirectory();
339 NSString *fileName = [selectedFileURL lastPathComponent];
340 NSString *tempPath = [tempDir stringByAppendingPathComponent:fileName];
341 NSURL *tempURL = [NSURL fileURLWithPath:tempPath];
342
343 // Copy the file to the temporary location
344 NSError *error = nil;
345 [[NSFileManager defaultManager] removeItemAtURL:tempURL error:nil]; // Remove if exists
346
347 [selectedFileURL startAccessingSecurityScopedResource];
348 BOOL success = [[NSFileManager defaultManager] copyItemAtURL:selectedFileURL toURL:tempURL error:&error];
349 [selectedFileURL stopAccessingSecurityScopedResource];
350
351 if (success) {
352 std::string cppPath = std::string([tempPath UTF8String]);
353 NSLog(@"ROM copied to temporary path: %s", cppPath.c_str());
354
355 // Load ROM using modern API via Application singleton
356 // This triggers the full editor loading pipeline (sessions, startup actions, etc.)
358 } else {
359 NSLog(@"Failed to copy ROM to temp directory: %@", error);
360 }
361 } else {
362 self.completionHandler(@"");
363 }
364 }
365 self.completionHandler = nil;
366 [controller dismissViewControllerAnimated:YES completion:nil];
367}
368
369- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller {
370 if (self.completionHandler) {
371 self.completionHandler(@"");
372 }
373 self.completionHandler = nil;
374 [controller dismissViewControllerAnimated:YES completion:nil];
375}
376
377@end
378
379#endif
380
381// ----------------------------------------------------------------------------
382// Application main() function
383// ----------------------------------------------------------------------------
384
385#if TARGET_OS_OSX
386int main(int argc, const char *argv[]) { return NSApplicationMain(argc, argv); }
387#else
388
389int main(int argc, char *argv[]) {
390 @autoreleasepool {
391 return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
392 }
393}
394
395#endif
Controller * GetController()
Definition application.h:72
static Application & Instance()
void Initialize(const AppConfig &config)
void LoadRom(const std::string &path)
int main(int argc, char **argv)
Definition emu.cc:39
SDL2/SDL3 compatibility layer.
Configuration options for the application startup.
Definition application.h:23
std::string rom_file
Definition application.h:25