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