// ZcatguiBridge.m - Objective-C bridge implementation for zcatgui iOS backend // // This provides the UIKit integration for zcatgui. // Add this file to your Xcode iOS project. #import "ZcatguiBridge.h" #import #import // Global state static ZcatguiView *g_view = nil; static NSMutableArray *g_eventQueue = nil; static uint32_t g_width = 0; static uint32_t g_height = 0; static mach_timebase_info_data_t g_timebaseInfo; // Helper to create event static ZcatguiEvent *createEvent(ZcatguiEventType type) { ZcatguiEvent *event = [[ZcatguiEvent alloc] init]; event->type = type; memset(event->data, 0, sizeof(event->data)); return event; } // ============================================================================= // ZcatguiEvent wrapper (for NSMutableArray) // ============================================================================= @interface ZcatguiEventWrapper : NSObject @property (nonatomic) ZcatguiEventType type; @property (nonatomic) uint8_t data[64]; @end @implementation ZcatguiEventWrapper @end // ============================================================================= // ZcatguiView Implementation // ============================================================================= @implementation ZcatguiView { CGContextRef _bitmapContext; uint32_t *_pixels; uint32_t _pixelWidth; uint32_t _pixelHeight; } - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self.backgroundColor = [UIColor blackColor]; self.multipleTouchEnabled = YES; self.userInteractionEnabled = YES; _bitmapContext = NULL; _pixels = NULL; _pixelWidth = 0; _pixelHeight = 0; // Initialize event queue if (!g_eventQueue) { g_eventQueue = [[NSMutableArray alloc] init]; } } return self; } - (void)dealloc { if (_bitmapContext) { CGContextRelease(_bitmapContext); } if (_pixels) { free(_pixels); } } - (void)presentPixels:(const uint32_t *)pixels width:(uint32_t)width height:(uint32_t)height { // Resize buffer if needed if (width != _pixelWidth || height != _pixelHeight) { if (_bitmapContext) { CGContextRelease(_bitmapContext); _bitmapContext = NULL; } if (_pixels) { free(_pixels); _pixels = NULL; } _pixelWidth = width; _pixelHeight = height; _pixels = malloc(width * height * 4); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); _bitmapContext = CGBitmapContextCreate( _pixels, width, height, 8, width * 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Little ); CGColorSpaceRelease(colorSpace); } // Copy pixels memcpy(_pixels, pixels, width * height * 4); // Trigger redraw dispatch_async(dispatch_get_main_queue(), ^{ [self setNeedsDisplay]; }); } - (void)drawRect:(CGRect)rect { if (!_bitmapContext || !_pixels) { return; } CGContextRef ctx = UIGraphicsGetCurrentContext(); if (!ctx) { return; } // Create image from bitmap context CGImageRef image = CGBitmapContextCreateImage(_bitmapContext); if (image) { // Flip coordinate system CGContextTranslateCTM(ctx, 0, self.bounds.size.height); CGContextScaleCTM(ctx, 1.0, -1.0); CGContextDrawImage(ctx, self.bounds, image); CGImageRelease(image); } } - (CGSize)framebufferSize { return CGSizeMake(_pixelWidth, _pixelHeight); } // Touch handling - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; CGPoint location = [touch locationInView:self]; ZcatguiEventWrapper *evt = [[ZcatguiEventWrapper alloc] init]; evt.type = ZcatguiEventTouchDown; int32_t x = (int32_t)location.x; int32_t y = (int32_t)location.y; memcpy(&evt.data[0], &x, 4); memcpy(&evt.data[4], &y, 4); @synchronized(g_eventQueue) { [g_eventQueue addObject:evt]; } } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; CGPoint location = [touch locationInView:self]; ZcatguiEventWrapper *evt = [[ZcatguiEventWrapper alloc] init]; evt.type = ZcatguiEventTouchMove; int32_t x = (int32_t)location.x; int32_t y = (int32_t)location.y; memcpy(&evt.data[0], &x, 4); memcpy(&evt.data[4], &y, 4); @synchronized(g_eventQueue) { [g_eventQueue addObject:evt]; } } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; CGPoint location = [touch locationInView:self]; ZcatguiEventWrapper *evt = [[ZcatguiEventWrapper alloc] init]; evt.type = ZcatguiEventTouchUp; int32_t x = (int32_t)location.x; int32_t y = (int32_t)location.y; memcpy(&evt.data[0], &x, 4); memcpy(&evt.data[4], &y, 4); @synchronized(g_eventQueue) { [g_eventQueue addObject:evt]; } } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { [self touchesEnded:touches withEvent:event]; } @end // ============================================================================= // ZcatguiViewController Implementation // ============================================================================= @implementation ZcatguiViewController { CADisplayLink *_displayLink; } - (void)viewDidLoad { [super viewDidLoad]; self.zcatguiView = [[ZcatguiView alloc] initWithFrame:self.view.bounds]; self.zcatguiView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; [self.view addSubview:self.zcatguiView]; g_view = self.zcatguiView; g_width = (uint32_t)self.view.bounds.size.width; g_height = (uint32_t)self.view.bounds.size.height; self.running = YES; } - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; uint32_t newWidth = (uint32_t)self.view.bounds.size.width; uint32_t newHeight = (uint32_t)self.view.bounds.size.height; if (newWidth != g_width || newHeight != g_height) { g_width = newWidth; g_height = newHeight; // Queue resize event ZcatguiEventWrapper *evt = [[ZcatguiEventWrapper alloc] init]; evt.type = ZcatguiEventResize; memcpy(&evt.data[0], &newWidth, 4); memcpy(&evt.data[4], &newHeight, 4); @synchronized(g_eventQueue) { [g_eventQueue addObject:evt]; } } } - (void)startRenderLoop { _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(renderFrame:)]; [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; } - (void)stopRenderLoop { [_displayLink invalidate]; _displayLink = nil; } - (void)renderFrame:(CADisplayLink *)displayLink { // Override this in your subclass to call your Zig frame function } - (BOOL)prefersStatusBarHidden { return YES; } @end // ============================================================================= // Bridge Functions (called by Zig) // ============================================================================= void ios_view_init(uint32_t width, uint32_t height) { g_width = width; g_height = height; // Initialize timebase for timing mach_timebase_info(&g_timebaseInfo); NSLog(@"[zcatgui] ios_view_init: %ux%u", width, height); } uint32_t ios_view_get_width(void) { return g_width; } uint32_t ios_view_get_height(void) { return g_height; } void ios_view_present(const uint32_t *pixels, uint32_t width, uint32_t height) { if (g_view) { [g_view presentPixels:pixels width:width height:height]; } } uint32_t ios_poll_event(uint8_t *buffer) { ZcatguiEventWrapper *evt = nil; @synchronized(g_eventQueue) { if (g_eventQueue.count > 0) { evt = g_eventQueue.firstObject; [g_eventQueue removeObjectAtIndex:0]; } } if (!evt) { return ZcatguiEventNone; } memcpy(buffer, evt.data, 64); return evt.type; } void ios_log(const uint8_t *ptr, size_t len) { NSString *msg = [[NSString alloc] initWithBytes:ptr length:len encoding:NSUTF8StringEncoding]; NSLog(@"[zcatgui] %@", msg); } uint64_t ios_get_time_ms(void) { uint64_t time = mach_absolute_time(); uint64_t nanos = time * g_timebaseInfo.numer / g_timebaseInfo.denom; return nanos / 1000000; // Convert to milliseconds }