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#if !TARGET_OS_OSX
31#import "yaze-Swift.h"
32#endif
33
34#include <string>
35#include <vector>
36#include <algorithm>
37
38#include "app/controller.h"
39#include "app/application.h"
44#include "app/platform/window.h"
45#include "rom/rom.h"
46
48
49#ifdef main
50#undef main
51#endif
52
54#include "imgui/backends/imgui_impl_sdl2.h"
55#include "imgui/backends/imgui_impl_sdlrenderer2.h"
56#include "imgui/imgui.h"
57
58namespace {
60} // namespace
61
62// ----------------------------------------------------------------------------
63// AppViewController
64// ----------------------------------------------------------------------------
65
66@interface AppViewController ()
67@property(nonatomic, strong) UIViewController *overlayController;
68- (void)queueOpenURL:(NSURL *)url;
69@end
70
71@implementation AppViewController {
72 yaze::AppConfig app_config_;
79 float pinch_velocity_; // Smoothed pinch velocity for zoom
80}
81
82- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil
83 bundle:(nullable NSBundle *)nibBundleOrNil {
84 self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
85
86 _device = MTLCreateSystemDefaultDevice();
87 _commandQueue = [_device newCommandQueue];
88
89 if (!self.device) {
90 NSLog(@"Metal is not supported");
91 abort();
92 }
93
94 // Initialize SDL for iOS
95 SDL_SetMainReady();
96#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
97 SDL_SetHint(SDL_HINT_AUDIO_CATEGORY, "ambient");
98 if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_TIMER) != 0) {
99 NSLog(@"SDL_Init failed: %s", SDL_GetError());
100 }
101#endif
102#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
103 SDL_iOSSetEventPump(SDL_TRUE);
104#endif
105
106 // Parse command line arguments
107 int argc = NSProcessInfo.processInfo.arguments.count;
108 char **argv = new char *[argc];
109 for (int i = 0; i < argc; i++) {
110 NSString *arg = NSProcessInfo.processInfo.arguments[i];
111 const char *cString = [arg UTF8String];
112 argv[i] = new char[strlen(cString) + 1];
113 strcpy(argv[i], cString);
114 }
115
116 std::string rom_filename = "";
117 if (argc > 1) {
118 rom_filename = argv[1];
119 }
120
121 // Clean up argv
122 for (int i = 0; i < argc; i++) {
123 delete[] argv[i];
124 }
125 delete[] argv;
126
127#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
128 SDL_iOSSetEventPump(SDL_FALSE);
129#endif
130
131 // Enable native IME
132 SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
133
134 // Initialize Application singleton
135 yaze::AppConfig config;
136 config.rom_file = rom_filename;
137 app_config_ = config;
138 host_initialized_ = false;
140
141 // Setup gesture recognizers
142 _hoverGestureRecognizer =
143 [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(HoverGesture:)];
144 _hoverGestureRecognizer.cancelsTouchesInView = NO;
145 _hoverGestureRecognizer.delegate = self;
146 [self.view addGestureRecognizer:_hoverGestureRecognizer];
147
148 _pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self
149 action:@selector(HandlePinch:)];
150 _pinchRecognizer.cancelsTouchesInView = NO;
151 _pinchRecognizer.delegate = self;
152 [self.view addGestureRecognizer:_pinchRecognizer];
153
154 _longPressRecognizer =
155 [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
156 _longPressRecognizer.cancelsTouchesInView = NO;
157 _longPressRecognizer.delegate = self;
158 [self.view addGestureRecognizer:_longPressRecognizer];
159
160 _twoFingerPanRecognizer =
161 [[UIPanGestureRecognizer alloc] initWithTarget:self
162 action:@selector(HandleTwoFingerPan:)];
163 _twoFingerPanRecognizer.minimumNumberOfTouches = 2;
164 _twoFingerPanRecognizer.maximumNumberOfTouches = 2;
165 _twoFingerPanRecognizer.cancelsTouchesInView = NO;
166 _twoFingerPanRecognizer.delegate = self;
167 [self.view addGestureRecognizer:_twoFingerPanRecognizer];
168
169 if (@available(iOS 9.0, *)) {
170 NSArray<NSNumber *> *directTouches = @[ @(UITouchTypeDirect) ];
171 _pinchRecognizer.allowedTouchTypes = directTouches;
172 _longPressRecognizer.allowedTouchTypes = directTouches;
173 _twoFingerPanRecognizer.allowedTouchTypes = directTouches;
174 }
175
176 // Three-finger swipe left → Undo (iOS 13+ convention)
177 UISwipeGestureRecognizer *threeFingerSwipeLeft =
178 [[UISwipeGestureRecognizer alloc] initWithTarget:self
179 action:@selector(handleThreeFingerSwipeLeft:)];
180 threeFingerSwipeLeft.numberOfTouchesRequired = 3;
181 threeFingerSwipeLeft.direction = UISwipeGestureRecognizerDirectionLeft;
182 threeFingerSwipeLeft.cancelsTouchesInView = NO;
183 threeFingerSwipeLeft.delegate = self;
184 [self.view addGestureRecognizer:threeFingerSwipeLeft];
185
186 // Three-finger swipe right → Redo
187 UISwipeGestureRecognizer *threeFingerSwipeRight =
188 [[UISwipeGestureRecognizer alloc] initWithTarget:self
189 action:@selector(handleThreeFingerSwipeRight:)];
190 threeFingerSwipeRight.numberOfTouchesRequired = 3;
191 threeFingerSwipeRight.direction = UISwipeGestureRecognizerDirectionRight;
192 threeFingerSwipeRight.cancelsTouchesInView = NO;
193 threeFingerSwipeRight.delegate = self;
194 [self.view addGestureRecognizer:threeFingerSwipeRight];
195
196 // Left-edge swipe → Toggle panel sidebar
197 UIScreenEdgePanGestureRecognizer *edgeSwipe =
198 [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self
199 action:@selector(handleEdgeSwipe:)];
200 edgeSwipe.edges = UIRectEdgeLeft;
201 edgeSwipe.delegate = self;
202 [self.view addGestureRecognizer:edgeSwipe];
203
204 pinch_velocity_ = 0.0f;
205
206 return self;
207}
208
209- (MTKView *)mtkView {
210 return (MTKView *)self.view;
211}
212
213- (void)loadView {
214 self.view = [[MTKView alloc] initWithFrame:CGRectMake(0, 0, 1200, 720)];
215}
216
217- (void)viewDidLoad {
218 [super viewDidLoad];
219
220 self.view.multipleTouchEnabled = YES;
221
222 self.mtkView.device = self.device;
223 self.mtkView.colorPixelFormat = MTLPixelFormatBGRA8Unorm;
224 self.mtkView.clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1.0);
225 self.mtkView.framebufferOnly = NO;
226 self.mtkView.delegate = self;
227
228 if (!host_initialized_) {
229 g_ios_host.SetMetalView((__bridge void *)self.view);
230 yaze::ios::IOSHostConfig host_config;
231 host_config.app_config = app_config_;
232 auto status = g_ios_host.Initialize(host_config);
233 if (!status.ok()) {
234 NSLog(@"Failed to initialize iOS host: %s",
235 std::string(status.message()).c_str());
236 abort();
237 }
238
239 self.controller = yaze::Application::Instance().GetController();
240 if (!self.controller) {
241 NSLog(@"Failed to initialize application controller");
242 abort();
243 }
244 host_initialized_ = true;
245 }
246
247 [self attachSwiftUIOverlayIfNeeded];
248
249 // If the app was launched by opening a document (Files app), we may have been
250 // handed a URL before the host/controller existed. Open it now that the host
251 // is initialized.
252 if (pending_open_url_) {
253 [self queueOpenURL:pending_open_url_];
254 pending_open_url_ = nil;
255 }
256}
257
258#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
259- (void)viewDidAppear:(BOOL)animated {
260 [super viewDidAppear:animated];
261 [self becomeFirstResponder];
262}
263
264- (BOOL)canBecomeFirstResponder {
265 return YES;
266}
267
268// UIKeyInput protocol — bridges the iOS software keyboard to ImGui.
269- (BOOL)hasText {
270 return YES;
271}
272
273- (void)insertText:(NSString *)text {
274 ImGuiIO &io = ImGui::GetIO();
275 const char *utf8 = [text UTF8String];
276 if (utf8) {
277 io.AddInputCharactersUTF8(utf8);
278 }
279}
280
281- (void)deleteBackward {
282 ImGuiIO &io = ImGui::GetIO();
283 io.AddKeyEvent(ImGuiKey_Backspace, true);
284 io.AddKeyEvent(ImGuiKey_Backspace, false);
285}
286
287- (UITextInputMode *)textInputMode {
288 // Use default input mode (full keyboard)
289 return [UITextInputMode currentInputMode];
290}
291
292- (void)postOverlayCommand:(NSString *)command {
293 if (!command || command.length == 0) {
294 return;
295 }
296 [[NSNotificationCenter defaultCenter]
297 postNotificationName:@"yaze.overlay.command"
298 object:nil
299 userInfo:@{@"command": command}];
300}
301
302- (void)handleShowMenuCommand:(id)sender {
303 [self postOverlayCommand:@"show_menu"];
304}
305
306- (void)handleOpenRomCommand:(id)sender {
307 [self postOverlayCommand:@"open_rom"];
308}
309
310- (void)handleOpenProjectCommand:(id)sender {
311 [self postOverlayCommand:@"open_project"];
312}
313
314- (void)handleShowProjectBrowserCommand:(id)sender {
315 [self postOverlayCommand:@"show_project_browser"];
316}
317
318- (void)handleShowOracleToolsCommand:(id)sender {
319 [self postOverlayCommand:@"show_oracle_tools"];
320}
321
322- (void)handleShowPanelBrowserCommand:(id)sender {
323 [self postOverlayCommand:@"show_panel_browser"];
324}
325
326- (void)handleShowCommandPaletteCommand:(id)sender {
327 [self postOverlayCommand:@"show_command_palette"];
328}
329
330- (void)handleOpenSettingsCommand:(id)sender {
331 [self postOverlayCommand:@"open_settings"];
332}
333
334- (void)handleOpenAiCommand:(id)sender {
335 [self postOverlayCommand:@"open_ai"];
336}
337
338- (void)handleOpenBuildCommand:(id)sender {
339 [self postOverlayCommand:@"open_build"];
340}
341
342- (void)handleOpenFilesCommand:(id)sender {
343 [self postOverlayCommand:@"open_files"];
344}
345
346- (void)handleHideOverlayCommand:(id)sender {
347 [self postOverlayCommand:@"hide_overlay"];
348}
349
350- (UIKeyCommand *)yazeKeyCommandWithTitle:(NSString *)title
351 imageName:(NSString *)imageName
352 input:(NSString *)input
353 modifierFlags:(UIKeyModifierFlags)flags
354 action:(SEL)action {
355 UIKeyCommand *command = nil;
356 if (@available(iOS 13.0, *)) {
357 UIImage *image = imageName.length > 0 ? [UIImage systemImageNamed:imageName] : nil;
358 command = [UIKeyCommand commandWithTitle:title
359 image:image
360 action:action
361 input:input
362 modifierFlags:flags
363 propertyList:nil];
364 command.discoverabilityTitle = title;
365 } else {
366 command = [UIKeyCommand keyCommandWithInput:input
367 modifierFlags:flags
368 action:action];
369 command.discoverabilityTitle = title;
370 }
371 return command;
372}
373
374- (NSArray<UIKeyCommand *> *)keyCommands {
375 if (@available(iOS 13.0, *)) {
376 UIKeyCommand *menu =
377 [self yazeKeyCommandWithTitle:@"Yaze Menu"
378 imageName:@"line.3.horizontal"
379 input:@"M"
380 modifierFlags:UIKeyModifierCommand | UIKeyModifierShift
381 action:@selector(handleShowMenuCommand:)];
382 UIKeyCommand *openRom =
383 [self yazeKeyCommandWithTitle:@"Open ROM"
384 imageName:@"folder"
385 input:@"O"
386 modifierFlags:UIKeyModifierCommand
387 action:@selector(handleOpenRomCommand:)];
388 UIKeyCommand *openProject =
389 [self yazeKeyCommandWithTitle:@"Open Project"
390 imageName:@"folder.badge.person.crop"
391 input:@"O"
392 modifierFlags:UIKeyModifierCommand | UIKeyModifierShift
393 action:@selector(handleOpenProjectCommand:)];
394 UIKeyCommand *projectBrowser =
395 [self yazeKeyCommandWithTitle:@"Projects"
396 imageName:@"folder.badge.gearshape"
397 input:@"J"
398 modifierFlags:UIKeyModifierCommand | UIKeyModifierShift
399 action:@selector(handleShowProjectBrowserCommand:)];
400 UIKeyCommand *oracleTools =
401 [self yazeKeyCommandWithTitle:@"Oracle Tools"
402 imageName:@"wand.and.stars"
403 input:@"T"
404 modifierFlags:UIKeyModifierCommand | UIKeyModifierShift
405 action:@selector(handleShowOracleToolsCommand:)];
406 UIKeyCommand *panelBrowser =
407 [self yazeKeyCommandWithTitle:@"Panel Browser"
408 imageName:@"rectangle.stack"
409 input:@"B"
410 modifierFlags:UIKeyModifierCommand | UIKeyModifierShift
411 action:@selector(handleShowPanelBrowserCommand:)];
412 UIKeyCommand *commandPalette =
413 [self yazeKeyCommandWithTitle:@"Command Palette"
414 imageName:@"command"
415 input:@"P"
416 modifierFlags:UIKeyModifierCommand | UIKeyModifierShift
417 action:@selector(handleShowCommandPaletteCommand:)];
418 UIKeyCommand *settings =
419 [self yazeKeyCommandWithTitle:@"Settings"
420 imageName:@"gearshape"
421 input:@","
422 modifierFlags:UIKeyModifierCommand
423 action:@selector(handleOpenSettingsCommand:)];
424 return @[
425 menu, openRom, openProject, projectBrowser, oracleTools, panelBrowser,
426 commandPalette, settings
427 ];
428 }
429 return @[];
430}
431
432- (void)buildMenuWithBuilder:(id<UIMenuBuilder>)builder {
433 [super buildMenuWithBuilder:builder];
434 if (@available(iOS 13.0, *)) {
435 UIKeyCommand *menu =
436 [self yazeKeyCommandWithTitle:@"Yaze Menu"
437 imageName:@"line.3.horizontal"
438 input:@"M"
439 modifierFlags:UIKeyModifierCommand | UIKeyModifierShift
440 action:@selector(handleShowMenuCommand:)];
441 UIKeyCommand *openRom =
442 [self yazeKeyCommandWithTitle:@"Open ROM"
443 imageName:@"folder"
444 input:@"O"
445 modifierFlags:UIKeyModifierCommand
446 action:@selector(handleOpenRomCommand:)];
447 UIKeyCommand *openProject =
448 [self yazeKeyCommandWithTitle:@"Open Project"
449 imageName:@"folder.badge.person.crop"
450 input:@"O"
451 modifierFlags:UIKeyModifierCommand | UIKeyModifierShift
452 action:@selector(handleOpenProjectCommand:)];
453 UICommand *projectBrowser =
454 [UICommand commandWithTitle:@"Projects"
455 image:[UIImage systemImageNamed:@"folder.badge.gearshape"]
456 action:@selector(handleShowProjectBrowserCommand:)
457 propertyList:nil];
458 UICommand *oracleTools =
459 [UICommand commandWithTitle:@"Oracle Tools"
460 image:[UIImage systemImageNamed:@"wand.and.stars"]
461 action:@selector(handleShowOracleToolsCommand:)
462 propertyList:nil];
463 UIKeyCommand *panelBrowser =
464 [self yazeKeyCommandWithTitle:@"Panel Browser"
465 imageName:@"rectangle.stack"
466 input:@"B"
467 modifierFlags:UIKeyModifierCommand | UIKeyModifierShift
468 action:@selector(handleShowPanelBrowserCommand:)];
469 UIKeyCommand *commandPalette =
470 [self yazeKeyCommandWithTitle:@"Command Palette"
471 imageName:@"command"
472 input:@"P"
473 modifierFlags:UIKeyModifierCommand | UIKeyModifierShift
474 action:@selector(handleShowCommandPaletteCommand:)];
475 UICommand *settings =
476 [UICommand commandWithTitle:@"Settings"
477 image:[UIImage systemImageNamed:@"gearshape"]
478 action:@selector(handleOpenSettingsCommand:)
479 propertyList:nil];
480 UICommand *aiHosts =
481 [UICommand commandWithTitle:@"AI Hosts"
482 image:[UIImage systemImageNamed:@"sparkles"]
483 action:@selector(handleOpenAiCommand:)
484 propertyList:nil];
485 UICommand *remoteBuild =
486 [UICommand commandWithTitle:@"Remote Build"
487 image:[UIImage systemImageNamed:@"hammer"]
488 action:@selector(handleOpenBuildCommand:)
489 propertyList:nil];
490 UICommand *files =
491 [UICommand commandWithTitle:@"Files"
492 image:[UIImage systemImageNamed:@"doc.on.doc"]
493 action:@selector(handleOpenFilesCommand:)
494 propertyList:nil];
495 UICommand *hideOverlay =
496 [UICommand commandWithTitle:@"Hide Top Bar"
497 image:[UIImage systemImageNamed:@"chevron.up"]
498 action:@selector(handleHideOverlayCommand:)
499 propertyList:nil];
500
501 UIMenu *yazeMenu = [UIMenu menuWithTitle:@"Yaze"
502 image:[UIImage systemImageNamed:@"line.3.horizontal"]
503 identifier:@"org.halext.yaze.menu"
504 options:UIMenuOptionsDisplayInline
505 children:@[
506 menu,
507 openRom,
508 openProject,
509 projectBrowser,
510 oracleTools,
511 panelBrowser,
512 commandPalette,
513 settings,
514 aiHosts,
515 remoteBuild,
516 files,
517 hideOverlay
518 ]];
519
520 [builder insertChildMenu:yazeMenu atStartOfMenuForIdentifier:UIMenuFile];
521 }
522}
523#endif
524
525- (void)drawInMTKView:(MTKView *)view {
526 auto& app = yaze::Application::Instance();
527 if (!host_initialized_ || !app.IsReady() || !app.GetController()->IsActive()) {
528 return;
529 }
530
531 // Update ImGui display size for iOS before Tick
532 // Note: Tick() calls OnInput() then OnLoad() (NewFrame) then DoRender()
533 // We want to update IO before NewFrame.
534 // OnInput handles SDL events.
535
536 ImGuiIO &io = ImGui::GetIO();
537 const CGSize bounds = view.bounds.size;
538 io.DisplaySize = ImVec2(bounds.width, bounds.height);
539
540 const float scale_x =
541 bounds.width > 0.0f ? view.drawableSize.width / bounds.width : 1.0f;
542 const float scale_y =
543 bounds.height > 0.0f ? view.drawableSize.height / bounds.height : 1.0f;
544 io.DisplayFramebufferScale = ImVec2(scale_x, scale_y);
545
547
548 // Show/hide iOS software keyboard based on ImGui text input focus.
549 if (io.WantTextInput && ![self isFirstResponder]) {
550 [self becomeFirstResponder];
551 } else if (!io.WantTextInput && [self isFirstResponder]) {
552 [self resignFirstResponder];
553 }
554}
555
556- (void)viewDidLayoutSubviews {
557 [super viewDidLayoutSubviews];
558 if (self.overlayController) {
559 self.overlayController.view.frame = self.view.bounds;
560 }
561}
562
563- (void)attachSwiftUIOverlayIfNeeded {
564 if (self.overlayController) {
565 return;
566 }
567 Class overlayClass = NSClassFromString(@"YazeOverlayHostingController");
568 if (!overlayClass) {
569 NSLog(@"SwiftUI overlay controller not found");
570 return;
571 }
572 UIViewController *overlay = [[overlayClass alloc] init];
573 if (!overlay) {
574 return;
575 }
576 overlay.view.backgroundColor = [UIColor clearColor];
577 overlay.view.opaque = NO;
578 overlay.view.frame = self.view.bounds;
579 overlay.view.autoresizingMask =
580 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
581 [self addChildViewController:overlay];
582 [self.view addSubview:overlay.view];
583 [overlay didMoveToParentViewController:self];
584 self.overlayController = overlay;
585}
586
587// ----------------------------------------------------------------------------
588// Open-In-Place (.yazeproj) document handling
589// ----------------------------------------------------------------------------
590
591- (NSURL *)yazeSettingsFileURL {
592 NSArray<NSURL *> *urls =
593 [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
594 inDomains:NSUserDomainMask];
595 NSURL *documents = urls.firstObject;
596 if (!documents) {
597 return nil;
598 }
599
600 NSURL *root = [documents URLByAppendingPathComponent:@"Yaze" isDirectory:YES];
601 [[NSFileManager defaultManager] createDirectoryAtURL:root
602 withIntermediateDirectories:YES
603 attributes:nil
604 error:nil];
605 return [root URLByAppendingPathComponent:@"settings.json"];
606}
607
608- (void)updateSettingsLastProjectPath:(NSString *)projectPath
609 romPath:(NSString *)romPath {
610 NSURL *settingsURL = [self yazeSettingsFileURL];
611 if (!settingsURL) {
612 return;
613 }
614
615 NSFileManager *fm = [NSFileManager defaultManager];
616 NSMutableDictionary *root = [NSMutableDictionary dictionary];
617 if ([fm fileExistsAtPath:settingsURL.path]) {
618 NSData *data = [NSData dataWithContentsOfURL:settingsURL];
619 if (data.length > 0) {
620 id obj = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
621 if ([obj isKindOfClass:[NSDictionary class]]) {
622 root = [obj mutableCopy];
623 }
624 }
625 }
626
627 NSMutableDictionary *general = nil;
628 id existing_general = root[@"general"];
629 if ([existing_general isKindOfClass:[NSDictionary class]]) {
630 general = [existing_general mutableCopy];
631 } else {
632 general = [NSMutableDictionary dictionary];
633 }
634
635 if (projectPath.length > 0) {
636 general[@"last_project_path"] = projectPath;
637 }
638 if (romPath.length > 0) {
639 general[@"last_rom_path"] = romPath;
640 }
641 root[@"general"] = general;
642 if (!root[@"version"]) {
643 root[@"version"] = @(1);
644 }
645
646 NSJSONWritingOptions options = 0;
647 if (@available(iOS 11.0, *)) {
648 options = NSJSONWritingPrettyPrinted | NSJSONWritingSortedKeys;
649 } else {
650 options = NSJSONWritingPrettyPrinted;
651 }
652
653 NSData *out = [NSJSONSerialization dataWithJSONObject:root options:options error:nil];
654 if (!out) {
655 return;
656 }
657 [out writeToURL:settingsURL atomically:YES];
658
659 // Ask the live settings store (Swift) to reload, so the overlay updates
660 // immediately when the app is already running.
661 [[NSNotificationCenter defaultCenter]
662 postNotificationName:@"yaze.settings.reload"
663 object:nil];
664}
665
666- (void)beginSecurityScopeForURL:(NSURL *)url {
668 [security_scoped_url_ stopAccessingSecurityScopedResource];
669 }
672 if (!url) {
673 return;
674 }
675 security_scope_granted_ = [url startAccessingSecurityScopedResource];
676}
677
678// If the system hands us a URL inside a `.yazeproj` package (common with file
679// provider/iCloud workflows), walk up to the bundle root so subsequent file
680// access (rom, project snapshot) succeeds under a single security scope.
681- (NSURL *)resolveYazeProjectBundleRootForURL:(NSURL *)url {
682 if (!url) {
683 return nil;
684 }
685
686 NSString *ext = [[url pathExtension] lowercaseString];
687 if ([ext isEqualToString:@"yazeproj"]) {
688 return url;
689 }
690
691 NSURL *current = url;
692 for (int i = 0; i < 10; i++) {
693 NSURL *parent = [current URLByDeletingLastPathComponent];
694 if (!parent || [parent.path isEqualToString:current.path]) {
695 break;
696 }
697 NSString *parent_ext = [[parent pathExtension] lowercaseString];
698 if ([parent_ext isEqualToString:@"yazeproj"]) {
699 return parent;
700 }
701 current = parent;
702 }
703
704 // Heuristic fallback: directory containing expected bundle markers.
705 BOOL is_dir = NO;
706 if ([[NSFileManager defaultManager] fileExistsAtPath:url.path
707 isDirectory:&is_dir] &&
708 is_dir) {
709 NSURL *project_file = [url URLByAppendingPathComponent:@"project.yaze"];
710 NSURL *rom_file = [url URLByAppendingPathComponent:@"rom"];
711 if ([[NSFileManager defaultManager] fileExistsAtPath:project_file.path] &&
712 [[NSFileManager defaultManager] fileExistsAtPath:rom_file.path]) {
713 return url;
714 }
715 }
716
717 return url;
718}
719
720- (void)openURLNow:(NSURL *)url {
721 if (!url) {
722 return;
723 }
724
725 url = [self resolveYazeProjectBundleRootForURL:url] ?: url;
726
727 // iCloud Drive items may be lazily downloaded. Request a download if needed.
728 [[NSFileManager defaultManager] startDownloadingUbiquitousItemAtURL:url error:nil];
729 if ([[[url pathExtension] lowercaseString] isEqualToString:@"yazeproj"]) {
730 [[NSFileManager defaultManager]
731 startDownloadingUbiquitousItemAtURL:[url URLByAppendingPathComponent:@"project.yaze"]
732 error:nil];
733 [[NSFileManager defaultManager]
734 startDownloadingUbiquitousItemAtURL:[url URLByAppendingPathComponent:@"rom"]
735 error:nil];
736 }
737
738 NSString *path = url.path;
739 if (!path || path.length == 0) {
740 return;
741 }
742
743 // Update settings for UX (overlay status + "restore last session").
744 if ([[url.pathExtension lowercaseString] isEqualToString:@"yazeproj"]) {
745 NSString *romPath = [[url URLByAppendingPathComponent:@"rom"] path];
746 [self updateSettingsLastProjectPath:path romPath:romPath ?: @""];
747 }
748
749 if (!self.controller || !self.controller->editor_manager()) {
750 return;
751 }
752 std::string cpp_path([path UTF8String]);
753 (void)self.controller->editor_manager()->OpenRomOrProject(cpp_path);
754}
755
756- (void)queueOpenURL:(NSURL *)url {
757 if (!url) {
758 return;
759 }
760
761 NSURL *resolved = [self resolveYazeProjectBundleRootForURL:url] ?: url;
762
763 // Keep security-scoped access alive for the duration of the opened project.
764 // Prefer the bundle root so child reads succeed.
765 [self beginSecurityScopeForURL:resolved];
766 if (!security_scope_granted_ && resolved != url) {
767 // Fallback: keep access to the picked URL if root access wasn't granted.
768 [self beginSecurityScopeForURL:url];
769 resolved = url;
770 }
771
772 if (!host_initialized_) {
773 pending_open_url_ = resolved;
774 return;
775 }
776
777 // Avoid scene-update watchdog termination: return quickly from the UIKit
778 // callback, then perform project loading on the next run loop tick.
779 dispatch_async(dispatch_get_main_queue(), ^{
780 [self openURLNow:resolved];
781 });
782}
783
784- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size {
785}
786
787// ----------------------------------------------------------------------------
788// Input processing
789// ----------------------------------------------------------------------------
790
791#if !TARGET_OS_OSX
792
793// This touch mapping is super cheesy/hacky. We treat any touch on the screen
794// as if it were a depressed left mouse button, and we don't bother handling
795// multitouch correctly at all. This causes the "cursor" to behave very erratically
796// when there are multiple active touches. But for demo purposes, single-touch
797// interaction actually works surprisingly well.
798- (void)UpdateIOWithTouchEvent:(UIEvent *)event {
799 ImGuiIO &io = ImGui::GetIO();
800
801 UITouch *active_touch = nil;
802 const bool primary_was_stylus =
803 primary_touch_ != nil && primary_touch_.type == UITouchTypeStylus;
804
805 // Prefer Apple Pencil/stylus if present for precision input.
806 for (UITouch *touch in event.allTouches) {
807 if (touch.type == UITouchTypeStylus &&
808 touch.phase != UITouchPhaseEnded &&
809 touch.phase != UITouchPhaseCancelled) {
810 active_touch = touch;
811 break;
812 }
813 }
814
815 // If no stylus, keep the primary touch if it's still active.
816 if (!active_touch && primary_touch_ &&
817 [event.allTouches containsObject:primary_touch_]) {
818 if (primary_touch_.phase != UITouchPhaseEnded &&
819 primary_touch_.phase != UITouchPhaseCancelled) {
820 active_touch = primary_touch_;
821 }
822 }
823
824 // If we were interacting with the stylus and it just lifted, do NOT adopt a
825 // different in-flight touch (e.g. palm/finger resting on the screen). This
826 // prevents ImGui drags (like moving windows) from snapping back when the
827 // stylus ends.
828 if (!active_touch && primary_was_stylus) {
829 primary_touch_ = nil;
830 io.AddMouseSourceEvent(ImGuiMouseSource_Pen);
831 io.AddMouseButtonEvent(0, false);
832 return;
833 }
834
835 // Otherwise, fall back to a new direct touch (only on Began).
836 if (!active_touch) {
837 for (UITouch *touch in event.allTouches) {
838 if (touch.type == UITouchTypeDirect &&
839 touch.phase == UITouchPhaseBegan) {
840 active_touch = touch;
841 break;
842 }
843 }
844 }
845
846 if (!active_touch) {
847 for (UITouch *touch in event.allTouches) {
848 if (touch.phase == UITouchPhaseBegan) {
849 active_touch = touch;
850 break;
851 }
852 }
853 }
854
855 if (active_touch) {
856 UITouch *sample_touch = active_touch;
857 if (@available(iOS 9.0, *)) {
858 NSArray<UITouch *> *coalesced =
859 [event coalescedTouchesForTouch:active_touch];
860 if (coalesced.count > 0) {
861 sample_touch = coalesced.lastObject;
862 }
863 }
864
865 primary_touch_ = active_touch;
866 ImGuiMouseSource source = ImGuiMouseSource_TouchScreen;
867 if (active_touch.type == UITouchTypeStylus) {
868 source = ImGuiMouseSource_Pen;
869 }
870 io.AddMouseSourceEvent(source);
871
872 CGPoint touchLocation = CGPointZero;
873 if (@available(iOS 9.0, *)) {
874 if (active_touch.type == UITouchTypeStylus) {
875 touchLocation = [sample_touch preciseLocationInView:self.view];
876 } else {
877 touchLocation = [sample_touch locationInView:self.view];
878 }
879 } else {
880 touchLocation = [sample_touch locationInView:self.view];
881 }
882
883 io.AddMousePosEvent(touchLocation.x, touchLocation.y);
884 bool is_down = active_touch.phase != UITouchPhaseEnded &&
885 active_touch.phase != UITouchPhaseCancelled;
886 io.AddMouseButtonEvent(0, is_down);
887 } else {
888 primary_touch_ = nil;
889 io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen);
890 io.AddMouseButtonEvent(0, false);
891 }
892}
893
894- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
895 [self UpdateIOWithTouchEvent:event];
896}
897- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
898 [self UpdateIOWithTouchEvent:event];
899}
900- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
901 [self UpdateIOWithTouchEvent:event];
902}
903- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
904 [self UpdateIOWithTouchEvent:event];
905}
906
907- (void)HoverGesture:(UIHoverGestureRecognizer *)gesture {
908 ImGuiIO &io = ImGui::GetIO();
909 io.AddMouseSourceEvent(ImGuiMouseSource_Mouse);
910 // Cast to UIGestureRecognizer to UIGestureRecognizer to get locationInView
911 UIGestureRecognizer *gestureRecognizer = (UIGestureRecognizer *)gesture;
912 if (gesture.zOffset < 0.50) {
913 io.AddMousePosEvent([gestureRecognizer locationInView:self.view].x,
914 [gestureRecognizer locationInView:self.view].y);
915 }
916}
917
918- (void)HandlePinch:(UIPinchGestureRecognizer *)gesture {
919 ImGuiIO &io = ImGui::GetIO();
920 io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen);
921
922 // Update cursor position to pinch midpoint for zoom-toward-center behavior
923 CGPoint location = [gesture locationInView:self.view];
924 io.AddMousePosEvent(location.x, location.y);
925
926 if (gesture.state == UIGestureRecognizerStateBegan) {
927 previous_pinch_scale_ = gesture.scale;
928 pinch_velocity_ = 0.0f;
929 io.AddKeyEvent(ImGuiKey_ModCtrl, true);
930 } else if (gesture.state == UIGestureRecognizerStateChanged) {
931 io.AddKeyEvent(ImGuiKey_ModCtrl, true);
932 float raw_delta = gesture.scale - previous_pinch_scale_;
933
934 // Exponential moving average for smoother zoom
935 constexpr float kSmoothingFactor = 0.3f;
936 pinch_velocity_ = pinch_velocity_ * (1.0f - kSmoothingFactor) +
937 raw_delta * kSmoothingFactor;
938
939 io.AddMouseWheelEvent(0.0f, pinch_velocity_);
940 previous_pinch_scale_ = gesture.scale;
941 } else if (gesture.state == UIGestureRecognizerStateEnded ||
942 gesture.state == UIGestureRecognizerStateCancelled) {
943 io.AddKeyEvent(ImGuiKey_ModCtrl, false);
945 pinch_velocity_ = 0.0f;
946 }
947}
948
949- (void)HandleTwoFingerPan:(UIPanGestureRecognizer *)gesture {
950 ImGuiIO &io = ImGui::GetIO();
951 io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen);
952
953 CGPoint translation = [gesture translationInView:self.view];
954 // Send as mouse wheel events so Canvas pan responds
955 // Scale down to get reasonable scroll speed
956 float dx = static_cast<float>(translation.x) * 0.05f;
957 float dy = static_cast<float>(translation.y) * 0.05f;
958 io.AddMouseWheelEvent(dx, dy);
959
960 // Reset translation so we get deltas each callback
961 [gesture setTranslation:CGPointZero inView:self.view];
962
963 CGPoint location = [gesture locationInView:self.view];
964 io.AddMousePosEvent(location.x, location.y);
965}
966
967- (void)handleLongPress:(UILongPressGestureRecognizer *)gesture {
968 ImGuiIO &io = ImGui::GetIO();
969 io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen);
970 io.AddMouseButtonEvent(1, gesture.state == UIGestureRecognizerStateBegan);
971 UIGestureRecognizer *gestureRecognizer = (UIGestureRecognizer *)gesture;
972 io.AddMousePosEvent([gestureRecognizer locationInView:self.view].x,
973 [gestureRecognizer locationInView:self.view].y);
974 if (gesture.state == UIGestureRecognizerStateBegan) {
976 }
977}
978
979- (void)handleThreeFingerSwipeLeft:(UISwipeGestureRecognizer *)gesture {
980 // Three-finger swipe left = Undo (iOS convention)
981 // Inject Ctrl+Z — the shortcut system treats Ctrl/Super as equivalent on macOS.
982 ImGuiIO &io = ImGui::GetIO();
983 io.AddKeyEvent(ImGuiMod_Ctrl, true);
984 io.AddKeyEvent(ImGuiKey_Z, true);
985 io.AddKeyEvent(ImGuiKey_Z, false);
986 io.AddKeyEvent(ImGuiMod_Ctrl, false);
988}
989
990- (void)handleThreeFingerSwipeRight:(UISwipeGestureRecognizer *)gesture {
991 // Three-finger swipe right = Redo (iOS convention)
992 // Inject Ctrl+Shift+Z — mapped to redo in the shortcut configurator.
993 ImGuiIO &io = ImGui::GetIO();
994 io.AddKeyEvent(ImGuiMod_Ctrl, true);
995 io.AddKeyEvent(ImGuiMod_Shift, true);
996 io.AddKeyEvent(ImGuiKey_Z, true);
997 io.AddKeyEvent(ImGuiKey_Z, false);
998 io.AddKeyEvent(ImGuiMod_Shift, false);
999 io.AddKeyEvent(ImGuiMod_Ctrl, false);
1001}
1002
1003- (void)handleEdgeSwipe:(UIScreenEdgePanGestureRecognizer *)gesture {
1004 if (gesture.state == UIGestureRecognizerStateBegan) {
1005 // Inject Ctrl+B — the shortcut for view.toggle_activity_bar.
1006 ImGuiIO &io = ImGui::GetIO();
1007 io.AddKeyEvent(ImGuiMod_Ctrl, true);
1008 io.AddKeyEvent(ImGuiKey_B, true);
1009 io.AddKeyEvent(ImGuiKey_B, false);
1010 io.AddKeyEvent(ImGuiMod_Ctrl, false);
1012 }
1013}
1014
1015- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
1016 shouldRecognizeSimultaneouslyWithGestureRecognizer:
1017 (UIGestureRecognizer *)otherGestureRecognizer {
1018 (void)gestureRecognizer;
1019 (void)otherGestureRecognizer;
1020 return YES;
1021}
1022
1023#endif
1024
1025@end
1026
1027// ----------------------------------------------------------------------------
1028// SceneDelegate (UIScene lifecycle)
1029// ----------------------------------------------------------------------------
1030
1031#if !TARGET_OS_OSX
1032
1033@interface SceneDelegate : UIResponder <UIWindowSceneDelegate>
1034@property(nonatomic, strong) UIWindow *window;
1035@end
1036
1037@implementation SceneDelegate
1038
1039- (void)scene:(UIScene *)scene
1040 willConnectToSession:(UISceneSession *)session
1041 options:(UISceneConnectionOptions *)connectionOptions {
1042 if (![scene isKindOfClass:[UIWindowScene class]]) {
1043 return;
1044 }
1045 UIWindowScene *windowScene = (UIWindowScene *)scene;
1046 AppViewController *rootViewController = [[AppViewController alloc] init];
1047 self.window = [[UIWindow alloc] initWithWindowScene:windowScene];
1048 self.window.rootViewController = rootViewController;
1049 [self.window makeKeyAndVisible];
1050
1051 // Handle "tap to open" from Files app at launch.
1052 if (@available(iOS 13.0, *)) {
1053 for (UIOpenURLContext *context in connectionOptions.URLContexts) {
1054 if (context.URL) {
1055 [rootViewController queueOpenURL:context.URL];
1056 break;
1057 }
1058 }
1059 }
1060}
1061
1062- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts {
1063 (void)scene;
1064 AppViewController *root =
1065 (AppViewController *)self.window.rootViewController;
1066 if (![root isKindOfClass:[AppViewController class]]) {
1067 return;
1068 }
1069 for (UIOpenURLContext *context in URLContexts) {
1070 if (context.URL) {
1071 [root queueOpenURL:context.URL];
1072 break;
1073 }
1074 }
1075}
1076
1077@end
1078
1079#endif
1080
1081// ----------------------------------------------------------------------------
1082// AppDelegate
1083// ----------------------------------------------------------------------------
1084
1085#if TARGET_OS_OSX
1086
1087@interface AppDelegate : NSObject <NSApplicationDelegate>
1088@property(nonatomic, strong) NSWindow *window;
1089@end
1090
1091@implementation AppDelegate
1092
1093- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
1094 return YES;
1095}
1096
1097- (instancetype)init {
1098 if (self = [super init]) {
1099 NSViewController *rootViewController = [[AppViewController alloc] initWithNibName:nil
1100 bundle:nil];
1101 self.window = [[NSWindow alloc]
1102 initWithContentRect:NSZeroRect
1103 styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
1104 NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable
1105 backing:NSBackingStoreBuffered
1106 defer:NO];
1107 self.window.contentViewController = rootViewController;
1108 [self.window center];
1109 [self.window makeKeyAndOrderFront:self];
1110 }
1111 return self;
1112}
1113
1114@end
1115
1116#else
1117
1118@interface AppDelegate : UIResponder <UIApplicationDelegate, UIDocumentPickerDelegate>
1119@property(nonatomic, strong) UIWindow *window;
1120@property(nonatomic, copy) void (^completionHandler)(NSString *selectedFile);
1121@property(nonatomic, strong) UIDocumentPickerViewController *documentPicker;
1122@end
1123
1124@implementation AppDelegate
1125
1126- (BOOL)application:(UIApplication *)application
1127 didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
1128 if (@available(iOS 13.0, *)) {
1129 return YES;
1130 }
1131 UIViewController *rootViewController = [[AppViewController alloc] init];
1132 self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
1133 self.window.rootViewController = rootViewController;
1134 [self.window makeKeyAndVisible];
1135 return YES;
1136}
1137
1138- (UISceneConfiguration *)application:(UIApplication *)application
1139 configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession
1140 options:(UISceneConnectionOptions *)options {
1141 if (@available(iOS 13.0, *)) {
1142 UISceneConfiguration *configuration =
1143 [[UISceneConfiguration alloc] initWithName:@"Default Configuration"
1144 sessionRole:connectingSceneSession.role];
1145 configuration.delegateClass = [SceneDelegate class];
1146 configuration.sceneClass = [UIWindowScene class];
1147 return configuration;
1148 }
1149 return nil;
1150}
1151
1152- (BOOL)application:(UIApplication *)application
1153 openURL:(NSURL *)url
1154 options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
1155 (void)application;
1156 (void)options;
1157
1158 if (!url) {
1159 return NO;
1160 }
1161
1162 UIViewController *rootViewController = [self RootViewControllerForPresenting];
1163 if ([rootViewController isKindOfClass:[AppViewController class]]) {
1164 [(AppViewController *)rootViewController queueOpenURL:url];
1165 return YES;
1166 }
1167 return NO;
1168}
1169
1170- (void)applicationWillTerminate:(UIApplication *)application {
1172}
1173
1174- (UIViewController *)RootViewControllerForPresenting {
1175 if (@available(iOS 13.0, *)) {
1176 for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
1177 if (scene.activationState != UISceneActivationStateForegroundActive) {
1178 continue;
1179 }
1180 if (![scene isKindOfClass:[UIWindowScene class]]) {
1181 continue;
1182 }
1183 UIWindowScene *windowScene = (UIWindowScene *)scene;
1184 for (UIWindow *window in windowScene.windows) {
1185 if (window.isKeyWindow && window.rootViewController) {
1186 return window.rootViewController;
1187 }
1188 }
1189 if (windowScene.windows.count > 0 &&
1190 windowScene.windows.firstObject.rootViewController) {
1191 return windowScene.windows.firstObject.rootViewController;
1192 }
1193 }
1194 }
1195
1196 return self.window.rootViewController;
1197}
1198
1199- (void)PresentDocumentPickerWithCompletionHandler:
1200 (void (^)(NSString *selectedFile))completionHandler
1201 allowedTypes:(NSArray<UTType*> *)allowedTypes {
1202 self.completionHandler = completionHandler;
1203
1204 NSArray<UTType*>* documentTypes = allowedTypes;
1205 if (!documentTypes || documentTypes.count == 0) {
1206 documentTypes = @[ UTTypeData ];
1207 }
1208 UIViewController *rootViewController = [self RootViewControllerForPresenting];
1209 if (!rootViewController) {
1210 if (self.completionHandler) {
1211 self.completionHandler(@"");
1212 }
1213 self.completionHandler = nil;
1214 return;
1215 }
1216 _documentPicker =
1217 [[UIDocumentPickerViewController alloc] initForOpeningContentTypes:documentTypes];
1218 _documentPicker.delegate = self;
1219 _documentPicker.modalPresentationStyle = UIModalPresentationFormSheet;
1220
1221 [rootViewController presentViewController:_documentPicker animated:YES completion:nil];
1222}
1223
1224- (void)documentPicker:(UIDocumentPickerViewController *)controller
1225 didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
1226 NSURL *selectedFileURL = [urls firstObject];
1227
1228 if (self.completionHandler) {
1229 if (selectedFileURL) {
1230 // Create a temporary file path
1231 NSString *tempDir = NSTemporaryDirectory();
1232 NSString *fileName = [selectedFileURL lastPathComponent];
1233 NSString *tempPath = [tempDir stringByAppendingPathComponent:fileName];
1234 NSURL *tempURL = [NSURL fileURLWithPath:tempPath];
1235
1236 // Copy the file to the temporary location
1237 NSError *error = nil;
1238 [[NSFileManager defaultManager] removeItemAtURL:tempURL error:nil]; // Remove if exists
1239
1240 [selectedFileURL startAccessingSecurityScopedResource];
1241 BOOL success = [[NSFileManager defaultManager] copyItemAtURL:selectedFileURL toURL:tempURL error:&error];
1242 [selectedFileURL stopAccessingSecurityScopedResource];
1243
1244 if (success) {
1245 std::string cppPath = std::string([tempPath UTF8String]);
1246 NSLog(@"File copied to temporary path: %s", cppPath.c_str());
1247 self.completionHandler(tempPath);
1248 } else {
1249 NSLog(@"Failed to copy ROM to temp directory: %@", error);
1250 self.completionHandler(@"");
1251 }
1252 } else {
1253 self.completionHandler(@"");
1254 }
1255 }
1256 self.completionHandler = nil;
1257 [controller dismissViewControllerAnimated:YES completion:nil];
1258}
1259
1260- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller {
1261 if (self.completionHandler) {
1262 self.completionHandler(@"");
1263 }
1264 self.completionHandler = nil;
1265 [controller dismissViewControllerAnimated:YES completion:nil];
1266}
1267
1268@end
1269
1270#endif
1271
1272// ----------------------------------------------------------------------------
1273// Application main() function
1274// ----------------------------------------------------------------------------
1275
1276#if TARGET_OS_OSX
1277int main(int argc, const char *argv[]) { return NSApplicationMain(argc, argv); }
1278#else
1279
1280int main(int argc, char *argv[]) {
1281 @autoreleasepool {
1282 return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
1283 }
1284}
1285
1286#endif
Controller * GetController()
Definition application.h:81
static Application & Instance()
absl::Status Initialize(const IOSHostConfig &config)
Definition ios_host.mm:20
void SetMetalView(void *view)
Definition ios_host.mm:53
int main(int argc, char **argv)
Definition emu.cc:43
UIWindow * window
Definition main.mm:1119
UIWindow * window
Definition main.mm:1034
float previous_pinch_scale_
Definition main.mm:78
bool host_initialized_
Definition main.mm:73
NSURL * security_scoped_url_
Definition main.mm:76
BOOL security_scope_granted_
Definition main.mm:77
float pinch_velocity_
Definition main.mm:79
NSURL * pending_open_url_
Definition main.mm:75
UITouch * primary_touch_
Definition main.mm:74
yaze::ios::IOSHost g_ios_host
Definition main.mm:59
void TriggerHaptic(HapticStyle style)
SDL2/SDL3 compatibility layer.
Configuration options for the application startup.
Definition application.h:26
std::string rom_file
Definition application.h:28