18#import <Foundation/Foundation.h>
21#import <Cocoa/Cocoa.h>
23#import <UIKit/UIKit.h>
26#import <Metal/Metal.h>
27#import <MetalKit/MetalKit.h>
28#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
54#include "imgui/backends/imgui_impl_sdl2.h"
55#include "imgui/backends/imgui_impl_sdlrenderer2.h"
56#include "imgui/imgui.h"
67@property(nonatomic, strong) UIViewController *overlayController;
68- (void)queueOpenURL:(NSURL *)url;
82- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil
83 bundle:(nullable NSBundle *)nibBundleOrNil {
84 self = [
super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
86 _device = MTLCreateSystemDefaultDevice();
87 _commandQueue = [_device newCommandQueue];
90 NSLog(
@"Metal is not supported");
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());
102#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
103 SDL_iOSSetEventPump(SDL_TRUE);
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);
116 std::string rom_filename =
"";
118 rom_filename = argv[1];
122 for (
int i = 0; i < argc; i++) {
127#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
128 SDL_iOSSetEventPump(SDL_FALSE);
132 SDL_SetHint(SDL_HINT_IME_SHOW_UI,
"1");
137 app_config_ = config;
142 _hoverGestureRecognizer =
143 [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(HoverGesture:)];
144 _hoverGestureRecognizer.cancelsTouchesInView = NO;
145 _hoverGestureRecognizer.delegate = self;
146 [
self.view addGestureRecognizer:_hoverGestureRecognizer];
148 _pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self
149 action:@selector(HandlePinch:)];
150 _pinchRecognizer.cancelsTouchesInView = NO;
151 _pinchRecognizer.delegate = self;
152 [
self.view addGestureRecognizer:_pinchRecognizer];
154 _longPressRecognizer =
155 [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
156 _longPressRecognizer.cancelsTouchesInView = NO;
157 _longPressRecognizer.delegate = self;
158 [
self.view addGestureRecognizer:_longPressRecognizer];
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];
169 if (@available(iOS 9.0, *)) {
170 NSArray<NSNumber *> *directTouches = @[ @(UITouchTypeDirect) ];
171 _pinchRecognizer.allowedTouchTypes = directTouches;
172 _longPressRecognizer.allowedTouchTypes = directTouches;
173 _twoFingerPanRecognizer.allowedTouchTypes = directTouches;
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];
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];
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];
209- (MTKView *)mtkView {
210 return (MTKView *)self.view;
214 self.view = [[MTKView alloc] initWithFrame:CGRectMake(0, 0, 1200, 720)];
220 self.view.multipleTouchEnabled = YES;
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;
234 NSLog(
@"Failed to initialize iOS host: %s",
235 std::string(status.message()).c_str());
240 if (!self.controller) {
241 NSLog(
@"Failed to initialize application controller");
247 [
self attachSwiftUIOverlayIfNeeded];
253 [
self queueOpenURL:pending_open_url_];
258#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
259- (void)viewDidAppear:(BOOL)animated {
260 [
super viewDidAppear:animated];
261 [
self becomeFirstResponder];
264- (BOOL)canBecomeFirstResponder {
273- (void)insertText:(NSString *)text {
274 ImGuiIO &io = ImGui::GetIO();
275 const char *utf8 = [text UTF8String];
277 io.AddInputCharactersUTF8(utf8);
281- (void)deleteBackward {
282 ImGuiIO &io = ImGui::GetIO();
283 io.AddKeyEvent(ImGuiKey_Backspace,
true);
284 io.AddKeyEvent(ImGuiKey_Backspace,
false);
287- (UITextInputMode *)textInputMode {
289 return [UITextInputMode currentInputMode];
292- (void)postOverlayCommand:(NSString *)command {
293 if (!command || command.length == 0) {
296 [[NSNotificationCenter defaultCenter]
297 postNotificationName:@"yaze.overlay.command"
299 userInfo:@{@"command": command}];
302- (void)handleShowMenuCommand:(
id)sender {
303 [
self postOverlayCommand:@"show_menu"];
306- (void)handleOpenRomCommand:(
id)sender {
307 [
self postOverlayCommand:@"open_rom"];
310- (void)handleOpenProjectCommand:(
id)sender {
311 [
self postOverlayCommand:@"open_project"];
314- (void)handleShowProjectBrowserCommand:(
id)sender {
315 [
self postOverlayCommand:@"show_project_browser"];
318- (void)handleShowOracleToolsCommand:(
id)sender {
319 [
self postOverlayCommand:@"show_oracle_tools"];
322- (void)handleShowPanelBrowserCommand:(
id)sender {
323 [
self postOverlayCommand:@"show_panel_browser"];
326- (void)handleShowCommandPaletteCommand:(
id)sender {
327 [
self postOverlayCommand:@"show_command_palette"];
330- (void)handleOpenSettingsCommand:(
id)sender {
331 [
self postOverlayCommand:@"open_settings"];
334- (void)handleOpenAiCommand:(
id)sender {
335 [
self postOverlayCommand:@"open_ai"];
338- (void)handleOpenBuildCommand:(
id)sender {
339 [
self postOverlayCommand:@"open_build"];
342- (void)handleOpenFilesCommand:(
id)sender {
343 [
self postOverlayCommand:@"open_files"];
346- (void)handleHideOverlayCommand:(
id)sender {
347 [
self postOverlayCommand:@"hide_overlay"];
350- (UIKeyCommand *)yazeKeyCommandWithTitle:(NSString *)title
351 imageName:(NSString *)imageName
352 input:(NSString *)input
353 modifierFlags:(UIKeyModifierFlags)flags
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
364 command.discoverabilityTitle = title;
366 command = [UIKeyCommand keyCommandWithInput:input
369 command.discoverabilityTitle = title;
374- (NSArray<UIKeyCommand *> *)keyCommands {
375 if (@available(iOS 13.0, *)) {
377 [
self yazeKeyCommandWithTitle:@"Yaze Menu"
378 imageName:@"line.3.horizontal"
380 modifierFlags:UIKeyModifierCommand | UIKeyModifierShift
381 action:@selector(handleShowMenuCommand:)];
382 UIKeyCommand *openRom =
383 [
self yazeKeyCommandWithTitle:@"Open ROM"
386 modifierFlags:UIKeyModifierCommand
387 action:@selector(handleOpenRomCommand:)];
388 UIKeyCommand *openProject =
389 [
self yazeKeyCommandWithTitle:@"Open Project"
390 imageName:@"folder.badge.person.crop"
392 modifierFlags:UIKeyModifierCommand | UIKeyModifierShift
393 action:@selector(handleOpenProjectCommand:)];
394 UIKeyCommand *projectBrowser =
395 [
self yazeKeyCommandWithTitle:@"Projects"
396 imageName:@"folder.badge.gearshape"
398 modifierFlags:UIKeyModifierCommand | UIKeyModifierShift
399 action:@selector(handleShowProjectBrowserCommand:)];
400 UIKeyCommand *oracleTools =
401 [
self yazeKeyCommandWithTitle:@"Oracle Tools"
402 imageName:@"wand.and.stars"
404 modifierFlags:UIKeyModifierCommand | UIKeyModifierShift
405 action:@selector(handleShowOracleToolsCommand:)];
406 UIKeyCommand *panelBrowser =
407 [
self yazeKeyCommandWithTitle:@"Panel Browser"
408 imageName:@"rectangle.stack"
410 modifierFlags:UIKeyModifierCommand | UIKeyModifierShift
411 action:@selector(handleShowPanelBrowserCommand:)];
412 UIKeyCommand *commandPalette =
413 [
self yazeKeyCommandWithTitle:@"Command Palette"
416 modifierFlags:UIKeyModifierCommand | UIKeyModifierShift
417 action:@selector(handleShowCommandPaletteCommand:)];
418 UIKeyCommand *settings =
419 [
self yazeKeyCommandWithTitle:@"Settings"
420 imageName:@"gearshape"
422 modifierFlags:UIKeyModifierCommand
423 action:@selector(handleOpenSettingsCommand:)];
425 menu, openRom, openProject, projectBrowser, oracleTools, panelBrowser,
426 commandPalette, settings
432- (void)buildMenuWithBuilder:(
id<UIMenuBuilder>)builder {
433 [
super buildMenuWithBuilder:builder];
434 if (@available(iOS 13.0, *)) {
436 [
self yazeKeyCommandWithTitle:@"Yaze Menu"
437 imageName:@"line.3.horizontal"
439 modifierFlags:UIKeyModifierCommand | UIKeyModifierShift
440 action:@selector(handleShowMenuCommand:)];
441 UIKeyCommand *openRom =
442 [
self yazeKeyCommandWithTitle:@"Open ROM"
445 modifierFlags:UIKeyModifierCommand
446 action:@selector(handleOpenRomCommand:)];
447 UIKeyCommand *openProject =
448 [
self yazeKeyCommandWithTitle:@"Open Project"
449 imageName:@"folder.badge.person.crop"
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:)
458 UICommand *oracleTools =
459 [UICommand commandWithTitle:@"Oracle Tools"
460 image:[UIImage systemImageNamed:@"wand.and.stars"]
461 action:@selector(handleShowOracleToolsCommand:)
463 UIKeyCommand *panelBrowser =
464 [
self yazeKeyCommandWithTitle:@"Panel Browser"
465 imageName:@"rectangle.stack"
467 modifierFlags:UIKeyModifierCommand | UIKeyModifierShift
468 action:@selector(handleShowPanelBrowserCommand:)];
469 UIKeyCommand *commandPalette =
470 [
self yazeKeyCommandWithTitle:@"Command Palette"
473 modifierFlags:UIKeyModifierCommand | UIKeyModifierShift
474 action:@selector(handleShowCommandPaletteCommand:)];
475 UICommand *settings =
476 [UICommand commandWithTitle:@"Settings"
477 image:[UIImage systemImageNamed:@"gearshape"]
478 action:@selector(handleOpenSettingsCommand:)
481 [UICommand commandWithTitle:@"AI Hosts"
482 image:[UIImage systemImageNamed:@"sparkles"]
483 action:@selector(handleOpenAiCommand:)
485 UICommand *remoteBuild =
486 [UICommand commandWithTitle:@"Remote Build"
487 image:[UIImage systemImageNamed:@"hammer"]
488 action:@selector(handleOpenBuildCommand:)
491 [UICommand commandWithTitle:@"Files"
492 image:[UIImage systemImageNamed:@"doc.on.doc"]
493 action:@selector(handleOpenFilesCommand:)
495 UICommand *hideOverlay =
496 [UICommand commandWithTitle:@"Hide Top Bar"
497 image:[UIImage systemImageNamed:@"chevron.up"]
498 action:@selector(handleHideOverlayCommand:)
501 UIMenu *yazeMenu = [UIMenu menuWithTitle:@"Yaze"
502 image:[UIImage systemImageNamed:@"line.3.horizontal"]
503 identifier:@"org.halext.yaze.menu"
504 options:UIMenuOptionsDisplayInline
520 [builder insertChildMenu:yazeMenu atStartOfMenuForIdentifier:UIMenuFile];
525- (void)drawInMTKView:(MTKView *)view {
536 ImGuiIO &io = ImGui::GetIO();
537 const CGSize bounds = view.bounds.size;
538 io.DisplaySize = ImVec2(bounds.width, bounds.height);
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);
549 if (io.WantTextInput && ![self isFirstResponder]) {
550 [
self becomeFirstResponder];
551 }
else if (!io.WantTextInput && [self isFirstResponder]) {
552 [
self resignFirstResponder];
556- (void)viewDidLayoutSubviews {
557 [
super viewDidLayoutSubviews];
558 if (self.overlayController) {
559 self.overlayController.view.frame = self.view.bounds;
563- (void)attachSwiftUIOverlayIfNeeded {
564 if (self.overlayController) {
567 Class overlayClass = NSClassFromString(
@"YazeOverlayHostingController");
569 NSLog(
@"SwiftUI overlay controller not found");
572 UIViewController *overlay = [[overlayClass alloc] init];
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;
591- (NSURL *)yazeSettingsFileURL {
592 NSArray<NSURL *> *urls =
593 [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
594 inDomains:NSUserDomainMask];
595 NSURL *documents = urls.firstObject;
600 NSURL *root = [documents URLByAppendingPathComponent:@"Yaze" isDirectory:YES];
601 [[NSFileManager defaultManager] createDirectoryAtURL:root
602 withIntermediateDirectories:YES
605 return [root URLByAppendingPathComponent:@"settings.json"];
608- (void)updateSettingsLastProjectPath:(NSString *)projectPath
609 romPath:(NSString *)romPath {
610 NSURL *settingsURL = [
self yazeSettingsFileURL];
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];
627 NSMutableDictionary *general = nil;
628 id existing_general = root[@"general"];
629 if ([existing_general isKindOfClass:[NSDictionary class]]) {
630 general = [existing_general mutableCopy];
632 general = [NSMutableDictionary dictionary];
635 if (projectPath.length > 0) {
636 general[@"last_project_path"] = projectPath;
638 if (romPath.length > 0) {
639 general[@"last_rom_path"] = romPath;
641 root[@"general"] = general;
642 if (!root[
@"version"]) {
643 root[@"version"] = @(1);
646 NSJSONWritingOptions options = 0;
647 if (@available(iOS 11.0, *)) {
648 options = NSJSONWritingPrettyPrinted | NSJSONWritingSortedKeys;
650 options = NSJSONWritingPrettyPrinted;
653 NSData *out = [NSJSONSerialization dataWithJSONObject:root options:options error:nil];
657 [out writeToURL:settingsURL atomically:YES];
661 [[NSNotificationCenter defaultCenter]
662 postNotificationName:@"yaze.settings.reload"
666- (void)beginSecurityScopeForURL:(NSURL *)url {
668 [security_scoped_url_ stopAccessingSecurityScopedResource];
681- (NSURL *)resolveYazeProjectBundleRootForURL:(NSURL *)url {
686 NSString *ext = [[url pathExtension] lowercaseString];
687 if ([ext isEqualToString:
@"yazeproj"]) {
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]) {
697 NSString *parent_ext = [[parent pathExtension] lowercaseString];
698 if ([parent_ext isEqualToString:
@"yazeproj"]) {
706 if ([[NSFileManager defaultManager] fileExistsAtPath:url.path
707 isDirectory:&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]) {
720- (void)openURLNow:(NSURL *)url {
725 url = [
self resolveYazeProjectBundleRootForURL:url] ?: url;
728 [[NSFileManager defaultManager] startDownloadingUbiquitousItemAtURL:url error:nil];
729 if ([[[url pathExtension] lowercaseString] isEqualToString:
@"yazeproj"]) {
730 [[NSFileManager defaultManager]
731 startDownloadingUbiquitousItemAtURL:[url URLByAppendingPathComponent:@"project.yaze"]
733 [[NSFileManager defaultManager]
734 startDownloadingUbiquitousItemAtURL:[url URLByAppendingPathComponent:@"rom"]
738 NSString *path = url.path;
739 if (!path || path.length == 0) {
744 if ([[url.pathExtension lowercaseString] isEqualToString:
@"yazeproj"]) {
745 NSString *romPath = [[url URLByAppendingPathComponent:@"rom"] path];
746 [
self updateSettingsLastProjectPath:path romPath:romPath ?: @""];
749 if (!self.controller || !self.controller->editor_manager()) {
752 std::string cpp_path([path UTF8String]);
753 (void)self.controller->editor_manager()->OpenRomOrProject(cpp_path);
756- (void)queueOpenURL:(NSURL *)url {
761 NSURL *resolved = [
self resolveYazeProjectBundleRootForURL:url] ?: url;
765 [
self beginSecurityScopeForURL:resolved];
768 [
self beginSecurityScopeForURL:url];
779 dispatch_async(dispatch_get_main_queue(), ^{
780 [
self openURLNow:resolved];
784- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size {
798- (void)UpdateIOWithTouchEvent:(UIEvent *)event {
799 ImGuiIO &io = ImGui::GetIO();
801 UITouch *active_touch = nil;
802 const bool primary_was_stylus =
806 for (UITouch *touch in event.allTouches) {
807 if (touch.type == UITouchTypeStylus &&
808 touch.phase != UITouchPhaseEnded &&
809 touch.phase != UITouchPhaseCancelled) {
810 active_touch = touch;
828 if (!active_touch && primary_was_stylus) {
830 io.AddMouseSourceEvent(ImGuiMouseSource_Pen);
831 io.AddMouseButtonEvent(0,
false);
837 for (UITouch *touch in event.allTouches) {
838 if (touch.type == UITouchTypeDirect &&
839 touch.phase == UITouchPhaseBegan) {
840 active_touch = touch;
847 for (UITouch *touch in event.allTouches) {
848 if (touch.phase == UITouchPhaseBegan) {
849 active_touch = 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;
866 ImGuiMouseSource source = ImGuiMouseSource_TouchScreen;
867 if (active_touch.type == UITouchTypeStylus) {
868 source = ImGuiMouseSource_Pen;
870 io.AddMouseSourceEvent(source);
872 CGPoint touchLocation = CGPointZero;
873 if (@available(iOS 9.0, *)) {
874 if (active_touch.type == UITouchTypeStylus) {
875 touchLocation = [sample_touch preciseLocationInView:self.view];
877 touchLocation = [sample_touch locationInView:self.view];
880 touchLocation = [sample_touch locationInView:self.view];
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);
889 io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen);
890 io.AddMouseButtonEvent(0,
false);
894- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
895 [
self UpdateIOWithTouchEvent:event];
897- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
898 [
self UpdateIOWithTouchEvent:event];
900- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
901 [
self UpdateIOWithTouchEvent:event];
903- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
904 [
self UpdateIOWithTouchEvent:event];
907- (void)HoverGesture:(UIHoverGestureRecognizer *)gesture {
908 ImGuiIO &io = ImGui::GetIO();
909 io.AddMouseSourceEvent(ImGuiMouseSource_Mouse);
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);
918- (void)HandlePinch:(UIPinchGestureRecognizer *)gesture {
919 ImGuiIO &io = ImGui::GetIO();
920 io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen);
923 CGPoint location = [gesture locationInView:self.view];
924 io.AddMousePosEvent(location.x, location.y);
926 if (gesture.state == UIGestureRecognizerStateBegan) {
929 io.AddKeyEvent(ImGuiKey_ModCtrl,
true);
930 }
else if (gesture.state == UIGestureRecognizerStateChanged) {
931 io.AddKeyEvent(ImGuiKey_ModCtrl,
true);
935 constexpr float kSmoothingFactor = 0.3f;
937 raw_delta * kSmoothingFactor;
941 }
else if (gesture.state == UIGestureRecognizerStateEnded ||
942 gesture.state == UIGestureRecognizerStateCancelled) {
943 io.AddKeyEvent(ImGuiKey_ModCtrl,
false);
949- (void)HandleTwoFingerPan:(UIPanGestureRecognizer *)gesture {
950 ImGuiIO &io = ImGui::GetIO();
951 io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen);
953 CGPoint translation = [gesture translationInView:self.view];
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);
961 [gesture setTranslation:CGPointZero inView:self.view];
963 CGPoint location = [gesture locationInView:self.view];
964 io.AddMousePosEvent(location.x, location.y);
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) {
979- (void)handleThreeFingerSwipeLeft:(UISwipeGestureRecognizer *)gesture {
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);
990- (void)handleThreeFingerSwipeRight:(UISwipeGestureRecognizer *)gesture {
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);
1003- (void)handleEdgeSwipe:(UIScreenEdgePanGestureRecognizer *)gesture {
1004 if (gesture.state == UIGestureRecognizerStateBegan) {
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);
1015- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
1016 shouldRecognizeSimultaneouslyWithGestureRecognizer:
1017 (UIGestureRecognizer *)otherGestureRecognizer {
1018 (void)gestureRecognizer;
1019 (void)otherGestureRecognizer;
1034@property(nonatomic, strong) UIWindow *
window;
1039- (void)scene:(UIScene *)scene
1040 willConnectToSession:(UISceneSession *)session
1041 options:(UISceneConnectionOptions *)connectionOptions {
1042 if (![scene isKindOfClass:[UIWindowScene class]]) {
1045 UIWindowScene *windowScene = (UIWindowScene *)scene;
1047 self.window = [[UIWindow alloc] initWithWindowScene:windowScene];
1048 self.window.rootViewController = rootViewController;
1049 [
self.window makeKeyAndVisible];
1052 if (@available(iOS 13.0, *)) {
1053 for (UIOpenURLContext *context in connectionOptions.URLContexts) {
1055 [rootViewController queueOpenURL:context.URL];
1062- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts {
1069 for (UIOpenURLContext *context in URLContexts) {
1071 [root queueOpenURL:context.URL];
1087@interface AppDelegate : NSObject <NSApplicationDelegate>
1088@property(nonatomic, strong) NSWindow *window;
1093- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
1097- (instancetype)init {
1098 if (self = [super init]) {
1099 NSViewController *rootViewController = [[
AppViewController alloc] initWithNibName:nil
1101 self.window = [[NSWindow alloc]
1102 initWithContentRect:NSZeroRect
1103 styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
1104 NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable
1105 backing:NSBackingStoreBuffered
1107 self.window.contentViewController = rootViewController;
1108 [
self.window center];
1109 [
self.window makeKeyAndOrderFront:self];
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;
1126- (BOOL)application:(UIApplication *)application
1127 didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey,
id> *)launchOptions {
1128 if (@available(iOS 13.0, *)) {
1132 self.
window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
1133 self.window.rootViewController = rootViewController;
1134 [
self.window makeKeyAndVisible];
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];
1146 configuration.sceneClass = [UIWindowScene class];
1147 return configuration;
1152- (BOOL)application:(UIApplication *)application
1153 openURL:(NSURL *)url
1154 options:(NSDictionary<UIApplicationOpenURLOptionsKey,
id> *)options {
1162 UIViewController *rootViewController = [
self RootViewControllerForPresenting];
1170- (void)applicationWillTerminate:(UIApplication *)application {
1174- (UIViewController *)RootViewControllerForPresenting {
1175 if (@available(iOS 13.0, *)) {
1176 for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
1177 if (scene.activationState != UISceneActivationStateForegroundActive) {
1180 if (![scene isKindOfClass:[UIWindowScene class]]) {
1183 UIWindowScene *windowScene = (UIWindowScene *)scene;
1184 for (UIWindow *window in windowScene.windows) {
1185 if (window.isKeyWindow && window.rootViewController) {
1186 return window.rootViewController;
1189 if (windowScene.windows.count > 0 &&
1190 windowScene.windows.firstObject.rootViewController) {
1191 return windowScene.windows.firstObject.rootViewController;
1196 return self.window.rootViewController;
1199- (void)PresentDocumentPickerWithCompletionHandler:
1200 (
void (^)(NSString *selectedFile))completionHandler
1201 allowedTypes:(NSArray<UTType*> *)allowedTypes {
1202 self.completionHandler = completionHandler;
1204 NSArray<UTType*>* documentTypes = allowedTypes;
1205 if (!documentTypes || documentTypes.count == 0) {
1206 documentTypes = @[ UTTypeData ];
1208 UIViewController *rootViewController = [
self RootViewControllerForPresenting];
1209 if (!rootViewController) {
1210 if (self.completionHandler) {
1211 self.completionHandler(
@"");
1213 self.completionHandler = nil;
1217 [[UIDocumentPickerViewController alloc] initForOpeningContentTypes:documentTypes];
1218 _documentPicker.delegate = self;
1219 _documentPicker.modalPresentationStyle = UIModalPresentationFormSheet;
1221 [rootViewController presentViewController:_documentPicker animated:YES completion:nil];
1224- (void)documentPicker:(UIDocumentPickerViewController *)controller
1225 didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
1226 NSURL *selectedFileURL = [urls firstObject];
1228 if (self.completionHandler) {
1229 if (selectedFileURL) {
1231 NSString *tempDir = NSTemporaryDirectory();
1232 NSString *fileName = [selectedFileURL lastPathComponent];
1233 NSString *tempPath = [tempDir stringByAppendingPathComponent:fileName];
1234 NSURL *tempURL = [NSURL fileURLWithPath:tempPath];
1237 NSError *error = nil;
1238 [[NSFileManager defaultManager] removeItemAtURL:tempURL error:nil];
1240 [selectedFileURL startAccessingSecurityScopedResource];
1241 BOOL success = [[NSFileManager defaultManager] copyItemAtURL:selectedFileURL toURL:tempURL error:&error];
1242 [selectedFileURL stopAccessingSecurityScopedResource];
1245 std::string cppPath = std::string([tempPath UTF8String]);
1246 NSLog(
@"File copied to temporary path: %s", cppPath.c_str());
1247 self.completionHandler(tempPath);
1249 NSLog(
@"Failed to copy ROM to temp directory: %@", error);
1250 self.completionHandler(
@"");
1253 self.completionHandler(
@"");
1256 self.completionHandler = nil;
1257 [controller dismissViewControllerAnimated:YES completion:nil];
1260- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller {
1261 if (self.completionHandler) {
1262 self.completionHandler(
@"");
1264 self.completionHandler = nil;
1265 [controller dismissViewControllerAnimated:YES completion:nil];
1277int main(
int argc,
const char *argv[]) {
return NSApplicationMain(argc, argv); }
1282 return UIApplicationMain(argc, argv, nil, NSStringFromClass([
AppDelegate class]));
Controller * GetController()
static Application & Instance()
absl::Status Initialize(const IOSHostConfig &config)
void SetMetalView(void *view)
int main(int argc, char **argv)
float previous_pinch_scale_
NSURL * security_scoped_url_
BOOL security_scope_granted_
NSURL * pending_open_url_
yaze::ios::IOSHost g_ios_host
SDL2/SDL3 compatibility layer.
Configuration options for the application startup.