os_ios.m raw

   1  // SPDX-License-Identifier: Unlicense OR MIT
   2  
   3  // +build darwin,ios
   4  
   5  @import UIKit;
   6  
   7  #include <stdint.h>
   8  #include "_cgo_export.h"
   9  #include "framework_ios.h"
  10  
  11  @interface GioView: UIView <UIKeyInput>
  12  @end
  13  
  14  @implementation GioViewController
  15  
  16  CGFloat _keyboardHeight;
  17  
  18  - (void)loadView {
  19  	gio_runMain();
  20  
  21  	CGRect zeroFrame = CGRectMake(0, 0, 0, 0);
  22  	self.view = [[UIView alloc] initWithFrame:zeroFrame];
  23  	self.view.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
  24  	UIView *drawView = [[GioView alloc] initWithFrame:zeroFrame];
  25  	[self.view addSubview: drawView];
  26  #ifndef TARGET_OS_TV
  27  	drawView.multipleTouchEnabled = YES;
  28  #endif
  29  	drawView.preservesSuperviewLayoutMargins = YES;
  30  	drawView.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
  31  	onCreate((__bridge CFTypeRef)drawView);
  32  	[[NSNotificationCenter defaultCenter] addObserver:self
  33  											 selector:@selector(keyboardWillChange:)
  34  												 name:UIKeyboardWillShowNotification
  35  											   object:nil];
  36  	[[NSNotificationCenter defaultCenter] addObserver:self
  37  											 selector:@selector(keyboardWillChange:)
  38  												 name:UIKeyboardWillChangeFrameNotification
  39  											   object:nil];
  40  	[[NSNotificationCenter defaultCenter] addObserver:self
  41  											 selector:@selector(keyboardWillHide:)
  42  												 name:UIKeyboardWillHideNotification
  43  											   object:nil];
  44  	[[NSNotificationCenter defaultCenter] addObserver: self
  45  											 selector: @selector(applicationDidEnterBackground:)
  46  												 name: UIApplicationDidEnterBackgroundNotification
  47  											   object: nil];
  48  	[[NSNotificationCenter defaultCenter] addObserver: self
  49  											 selector: @selector(applicationWillEnterForeground:)
  50  												 name: UIApplicationWillEnterForegroundNotification
  51  											   object: nil];
  52  }
  53  
  54  - (void)applicationWillEnterForeground:(UIApplication *)application {
  55  	UIView *drawView = self.view.subviews[0];
  56  	if (drawView != nil) {
  57  		gio_onDraw((__bridge CFTypeRef)drawView);
  58  	}
  59  }
  60  
  61  - (void)applicationDidEnterBackground:(UIApplication *)application {
  62  	UIView *drawView = self.view.subviews[0];
  63  	if (drawView != nil) {
  64  		onStop((__bridge CFTypeRef)drawView);
  65  	}
  66  }
  67  
  68  - (void)viewDidDisappear:(BOOL)animated {
  69  	[super viewDidDisappear:animated];
  70  	CFTypeRef viewRef = (__bridge CFTypeRef)self.view.subviews[0];
  71  	onDestroy(viewRef);
  72  }
  73  
  74  - (void)viewDidLayoutSubviews {
  75  	[super viewDidLayoutSubviews];
  76  	UIView *view = self.view.subviews[0];
  77  	CGRect frame = self.view.bounds;
  78  	// Adjust view bounds to make room for the keyboard.
  79  	frame.size.height -= _keyboardHeight;
  80  	view.frame = frame;
  81  	gio_onDraw((__bridge CFTypeRef)view);
  82  }
  83  
  84  - (void)didReceiveMemoryWarning {
  85  	onLowMemory();
  86  	[super didReceiveMemoryWarning];
  87  }
  88  
  89  - (void)keyboardWillChange:(NSNotification *)note {
  90  	NSDictionary *userInfo = note.userInfo;
  91  	CGRect f = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
  92  	_keyboardHeight = f.size.height;
  93  	[self.view setNeedsLayout];
  94  }
  95  
  96  - (void)keyboardWillHide:(NSNotification *)note {
  97  	_keyboardHeight = 0.0;
  98  	[self.view setNeedsLayout];
  99  }
 100  @end
 101  
 102  static void handleTouches(int last, UIView *view, NSSet<UITouch *> *touches, UIEvent *event) {
 103  	CGFloat scale = view.contentScaleFactor;
 104  	NSUInteger i = 0;
 105  	NSUInteger n = [touches count];
 106  	CFTypeRef viewRef = (__bridge CFTypeRef)view;
 107  	for (UITouch *touch in touches) {
 108  		CFTypeRef touchRef = (__bridge CFTypeRef)touch;
 109  		i++;
 110  		NSArray<UITouch *> *coalescedTouches = [event coalescedTouchesForTouch:touch];
 111  		NSUInteger j = 0;
 112  		NSUInteger m = [coalescedTouches count];
 113  		for (UITouch *coalescedTouch in [event coalescedTouchesForTouch:touch]) {
 114  			CGPoint loc = [coalescedTouch locationInView:view];
 115  			j++;
 116  			int lastTouch = last && i == n && j == m;
 117  			onTouch(lastTouch, viewRef, touchRef, touch.phase, loc.x*scale, loc.y*scale, [coalescedTouch timestamp]);
 118  		}
 119  	}
 120  }
 121  
 122  @implementation GioView
 123  NSArray<UIKeyCommand *> *_keyCommands;
 124  + (void)onFrameCallback:(CADisplayLink *)link {
 125  	gio_onFrameCallback((__bridge CFTypeRef)link);
 126  }
 127  
 128  - (void)willMoveToWindow:(UIWindow *)newWindow {
 129  	if (self.window != nil) {
 130  		[[NSNotificationCenter defaultCenter] removeObserver:self
 131  														name:UIWindowDidBecomeKeyNotification
 132  													  object:self.window];
 133  		[[NSNotificationCenter defaultCenter] removeObserver:self
 134  														name:UIWindowDidResignKeyNotification
 135  													  object:self.window];
 136  	}
 137  	self.contentScaleFactor = newWindow.screen.nativeScale;
 138  	[[NSNotificationCenter defaultCenter] addObserver:self
 139  											 selector:@selector(onWindowDidBecomeKey:)
 140  												 name:UIWindowDidBecomeKeyNotification
 141  											   object:newWindow];
 142  	[[NSNotificationCenter defaultCenter] addObserver:self
 143  											 selector:@selector(onWindowDidResignKey:)
 144  												 name:UIWindowDidResignKeyNotification
 145  											   object:newWindow];
 146  }
 147  
 148  - (void)onWindowDidBecomeKey:(NSNotification *)note {
 149  	if (self.isFirstResponder) {
 150  		onFocus((__bridge CFTypeRef)self, YES);
 151  	}
 152  }
 153  
 154  - (void)onWindowDidResignKey:(NSNotification *)note {
 155  	if (self.isFirstResponder) {
 156  		onFocus((__bridge CFTypeRef)self, NO);
 157  	}
 158  }
 159  
 160  - (void)dealloc {
 161  }
 162  
 163  - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
 164  	handleTouches(0, self, touches, event);
 165  }
 166  
 167  - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
 168  	handleTouches(0, self, touches, event);
 169  }
 170  
 171  - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
 172  	handleTouches(1, self, touches, event);
 173  }
 174  
 175  - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
 176  	handleTouches(1, self, touches, event);
 177  }
 178  
 179  - (void)insertText:(NSString *)text {
 180  	onText((__bridge CFTypeRef)self, (char *)text.UTF8String);
 181  }
 182  
 183  - (BOOL)canBecomeFirstResponder {
 184  	return YES;
 185  }
 186  
 187  - (BOOL)hasText {
 188  	return YES;
 189  }
 190  
 191  - (void)deleteBackward {
 192  	onDeleteBackward((__bridge CFTypeRef)self);
 193  }
 194  
 195  - (void)onUpArrow {
 196  	onUpArrow((__bridge CFTypeRef)self);
 197  }
 198  
 199  - (void)onDownArrow {
 200  	onDownArrow((__bridge CFTypeRef)self);
 201  }
 202  
 203  - (void)onLeftArrow {
 204  	onLeftArrow((__bridge CFTypeRef)self);
 205  }
 206  
 207  - (void)onRightArrow {
 208  	onRightArrow((__bridge CFTypeRef)self);
 209  }
 210  
 211  - (NSArray<UIKeyCommand *> *)keyCommands {
 212  	if (_keyCommands == nil) {
 213  		_keyCommands = @[
 214  			[UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow
 215  								modifierFlags:0
 216  									   action:@selector(onUpArrow)],
 217  			[UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow
 218  								modifierFlags:0
 219  									   action:@selector(onDownArrow)],
 220  			[UIKeyCommand keyCommandWithInput:UIKeyInputLeftArrow
 221  								modifierFlags:0
 222  									   action:@selector(onLeftArrow)],
 223  			[UIKeyCommand keyCommandWithInput:UIKeyInputRightArrow
 224  								modifierFlags:0
 225  									   action:@selector(onRightArrow)]
 226  		];
 227  	}
 228  	return _keyCommands;
 229  }
 230  @end
 231  
 232  void gio_writeClipboard(unichar *chars, NSUInteger length) {
 233  	@autoreleasepool {
 234  		NSString *s = [NSString string];
 235  		if (length > 0) {
 236  			s = [NSString stringWithCharacters:chars length:length];
 237  		}
 238  		UIPasteboard *p = UIPasteboard.generalPasteboard;
 239  		p.string = s;
 240  	}
 241  }
 242  
 243  CFTypeRef gio_readClipboard(void) {
 244  	@autoreleasepool {
 245  		UIPasteboard *p = UIPasteboard.generalPasteboard;
 246  		return (__bridge_retained CFTypeRef)p.string;
 247  	}
 248  }
 249  
 250  void gio_showTextInput(CFTypeRef viewRef) {
 251  	UIView *view = (__bridge UIView *)viewRef;
 252  	[view becomeFirstResponder];
 253  }
 254  
 255  void gio_hideTextInput(CFTypeRef viewRef) {
 256  	UIView *view = (__bridge UIView *)viewRef;
 257  	[view resignFirstResponder];
 258  }
 259  
 260  void gio_addLayerToView(CFTypeRef viewRef, CFTypeRef layerRef) {
 261  	UIView *view = (__bridge UIView *)viewRef;
 262  	CALayer *layer = (__bridge CALayer *)layerRef;
 263  	[view.layer addSublayer:layer];
 264  }
 265  
 266  void gio_updateView(CFTypeRef viewRef, CFTypeRef layerRef) {
 267  	UIView *view = (__bridge UIView *)viewRef;
 268  	CAEAGLLayer *layer = (__bridge CAEAGLLayer *)layerRef;
 269  	layer.contentsScale = view.contentScaleFactor;
 270  	layer.bounds = view.bounds;
 271  }
 272  
 273  void gio_removeLayer(CFTypeRef layerRef) {
 274  	CALayer *layer = (__bridge CALayer *)layerRef;
 275  	[layer removeFromSuperlayer];
 276  }
 277  
 278  struct drawParams gio_viewDrawParams(CFTypeRef viewRef) {
 279  	UIView *v = (__bridge UIView *)viewRef;
 280  	struct drawParams params;
 281  	CGFloat scale = v.layer.contentsScale;
 282  	// Use 163 as the standard ppi on iOS.
 283  	params.dpi = 163*scale;
 284  	params.sdpi = params.dpi;
 285  	UIEdgeInsets insets = v.layoutMargins;
 286  	if (@available(iOS 11.0, tvOS 11.0, *)) {
 287  		UIFontMetrics *metrics = [UIFontMetrics defaultMetrics];
 288  		params.sdpi = [metrics scaledValueForValue:params.sdpi];
 289  		insets = v.safeAreaInsets;
 290  	}
 291  	params.width = v.bounds.size.width*scale;
 292  	params.height = v.bounds.size.height*scale;
 293  	params.top = insets.top*scale;
 294  	params.right = insets.right*scale;
 295  	params.bottom = insets.bottom*scale;
 296  	params.left = insets.left*scale;
 297  	return params;
 298  }
 299  
 300  CFTypeRef gio_createDisplayLink(void) {
 301  	CADisplayLink *dl = [CADisplayLink displayLinkWithTarget:[GioView class] selector:@selector(onFrameCallback:)];
 302  	dl.paused = YES;
 303  	NSRunLoop *runLoop = [NSRunLoop mainRunLoop];
 304  	[dl addToRunLoop:runLoop forMode:[runLoop currentMode]];
 305  	return (__bridge_retained CFTypeRef)dl;
 306  }
 307  
 308  int gio_startDisplayLink(CFTypeRef dlref) {
 309  	CADisplayLink *dl = (__bridge CADisplayLink *)dlref;
 310  	dl.paused = NO;
 311  	return 0;
 312  }
 313  
 314  int gio_stopDisplayLink(CFTypeRef dlref) {
 315  	CADisplayLink *dl = (__bridge CADisplayLink *)dlref;
 316  	dl.paused = YES;
 317  	return 0;
 318  }
 319  
 320  void gio_releaseDisplayLink(CFTypeRef dlref) {
 321  	CADisplayLink *dl = (__bridge CADisplayLink *)dlref;
 322  	[dl invalidate];
 323  	CFRelease(dlref);
 324  }
 325  
 326  void gio_setDisplayLinkDisplay(CFTypeRef dl, uint64_t did) {
 327  	// Nothing to do on iOS.
 328  }
 329  
 330  void gio_hideCursor() {
 331  	// Not supported.
 332  }
 333  
 334  void gio_showCursor() {
 335  	// Not supported.
 336  }
 337  
 338  void gio_setCursor(NSUInteger curID) {
 339  	// Not supported.
 340  }
 341