| /* | |
| * Copyright 2026 Rive | |
| */ | |
| #if defined(RIVE_IOS) || defined(RIVE_IOS_SIMULATOR) | |
| #include "common/rive_ios_app.hpp" | |
| #import <QuartzCore/CAMetalLayer.h> | |
| #import <UIKit/UIKit.h> | |
| #include <atomic> | |
| #include <condition_variable> | |
| #include <mutex> | |
| #include <queue> | |
| static std::mutex s_windowMutex; | |
| static std::condition_variable s_windowReady; | |
| static CAMetalLayer* s_metalLayer = nil; | |
| static std::mutex s_inputMutex; | |
| static std::queue<TestingWindow::InputEventData> s_inputEvents; | |
| static std::mutex s_lifecycleMutex; | |
| static std::condition_variable s_lifecycleChanged; | |
| static bool s_active = true; | |
| static std::atomic<bool> s_shouldQuit{false}; | |
| static void push_touch_event( | |
| UIView* view, UITouch* touch, TestingWindow::InputEvent eventType) | |
| { | |
| if (touch == nil) | |
| { | |
| return; | |
| } | |
| CGPoint location = [touch locationInView:view]; | |
| CGFloat scale = view.contentScaleFactor; | |
| std::lock_guard<std::mutex> lock(s_inputMutex); | |
| s_inputEvents.push(TestingWindow::InputEventData( | |
| eventType, | |
| static_cast<float>(location.x * scale), | |
| static_cast<float>(location.y * scale))); | |
| } | |
| static void set_active(bool active) | |
| { | |
| std::lock_guard<std::mutex> lock(s_lifecycleMutex); | |
| s_active = active; | |
| s_lifecycleChanged.notify_all(); | |
| } | |
| @interface RiveMetalView : UIView | |
| @end | |
| @implementation RiveMetalView | |
| + (Class)layerClass | |
| { | |
| return [CAMetalLayer class]; | |
| } | |
| - (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event | |
| { | |
| push_touch_event( | |
| self, touches.anyObject, TestingWindow::InputEvent::MouseMove); | |
| push_touch_event( | |
| self, touches.anyObject, TestingWindow::InputEvent::MouseDown); | |
| } | |
| - (void)touchesMoved:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event | |
| { | |
| push_touch_event( | |
| self, touches.anyObject, TestingWindow::InputEvent::MouseMove); | |
| } | |
| - (void)touchesEnded:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event | |
| { | |
| push_touch_event(self, touches.anyObject, TestingWindow::InputEvent::MouseUp); | |
| } | |
| - (void)touchesCancelled:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event | |
| { | |
| push_touch_event(self, touches.anyObject, TestingWindow::InputEvent::MouseUp); | |
| } | |
| @end | |
| @interface GameViewController : UIViewController | |
| @end | |
| @implementation GameViewController | |
| - (void)loadView | |
| { | |
| self.view = [[RiveMetalView alloc] initWithFrame:UIScreen.mainScreen.bounds]; | |
| self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | | |
| UIViewAutoresizingFlexibleHeight; | |
| } | |
| - (void)viewDidLayoutSubviews | |
| { | |
| [super viewDidLayoutSubviews]; | |
| if (![self.view.layer isKindOfClass:[CAMetalLayer class]]) | |
| { | |
| return; | |
| } | |
| UIScreen* screen = self.view.window.screen ?: UIScreen.mainScreen; | |
| CGFloat scale = screen.scale; | |
| self.view.contentScaleFactor = scale; | |
| CAMetalLayer* layer = (CAMetalLayer*)self.view.layer; | |
| CGSize bounds = self.view.bounds.size; | |
| layer.contentsScale = scale; | |
| layer.drawableSize = CGSizeMake(bounds.width * scale, bounds.height * scale); | |
| std::lock_guard<std::mutex> lock(s_windowMutex); | |
| if (s_metalLayer == nil) | |
| { | |
| s_metalLayer = layer; | |
| s_windowReady.notify_all(); | |
| } | |
| } | |
| @end | |
| @interface RiveAppDelegate : UIResponder <UIApplicationDelegate> | |
| @property(strong, nonatomic) UIWindow* window; | |
| @end | |
| @implementation RiveAppDelegate | |
| - (BOOL)application:(UIApplication*)application | |
| didFinishLaunchingWithOptions:(NSDictionary*)launchOptions | |
| { | |
| self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; | |
| self.window.rootViewController = [[GameViewController alloc] init]; | |
| [self.window makeKeyAndVisible]; | |
| return YES; | |
| } | |
| - (void)applicationDidBecomeActive:(UIApplication*)application | |
| { | |
| set_active(true); | |
| } | |
| - (void)applicationWillResignActive:(UIApplication*)application | |
| { | |
| set_active(false); | |
| } | |
| - (void)applicationWillTerminate:(UIApplication*)application | |
| { | |
| s_shouldQuit = true; | |
| set_active(true); | |
| } | |
| @end | |
| void* rive_ios_app_wait_for_window() | |
| { | |
| std::unique_lock<std::mutex> lock(s_windowMutex); | |
| s_windowReady.wait(lock, [] { return s_metalLayer != nil; }); | |
| return (__bridge void*)s_metalLayer; | |
| } | |
| bool rive_ios_app_poll_input_event(TestingWindow::InputEventData& eventData) | |
| { | |
| std::lock_guard<std::mutex> lock(s_inputMutex); | |
| if (s_inputEvents.empty()) | |
| { | |
| return false; | |
| } | |
| eventData = s_inputEvents.front(); | |
| s_inputEvents.pop(); | |
| return true; | |
| } | |
| bool rive_ios_app_should_quit() { return s_shouldQuit; } | |
| void rive_ios_app_wait_while_inactive() | |
| { | |
| std::unique_lock<std::mutex> lock(s_lifecycleMutex); | |
| s_lifecycleChanged.wait(lock, [] { return s_active || s_shouldQuit; }); | |
| } | |
| #endif |