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