| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "sky_surface.h" |
| |
| #import <QuartzCore/QuartzCore.h> |
| #import <OpenGLES/EAGL.h> |
| #import <OpenGLES/EAGLDrawable.h> |
| |
| #include "base/time/time.h" |
| #include "mojo/public/cpp/bindings/interface_request.h" |
| #include "sky/services/engine/input_event.mojom.h" |
| #include "sky/shell/mac/platform_view_mac.h" |
| #include "sky/shell/shell_view.h" |
| #include "sky/shell/shell.h" |
| #include "sky/shell/ui_delegate.h" |
| |
| #ifndef NDEBUG |
| #include "document_watcher.h" |
| #endif |
| |
| static inline sky::EventType EventTypeFromUITouchPhase(UITouchPhase phase) { |
| switch (phase) { |
| case UITouchPhaseBegan: |
| return sky::EVENT_TYPE_POINTER_DOWN; |
| case UITouchPhaseMoved: |
| case UITouchPhaseStationary: |
| // There is no EVENT_TYPE_POINTER_STATIONARY. So we just pass a move type |
| // with the same coordinates |
| return sky::EVENT_TYPE_POINTER_MOVE; |
| case UITouchPhaseEnded: |
| return sky::EVENT_TYPE_POINTER_UP; |
| case UITouchPhaseCancelled: |
| return sky::EVENT_TYPE_POINTER_CANCEL; |
| } |
| |
| return sky::EVENT_TYPE_UNKNOWN; |
| } |
| |
| static inline int64 InputEventTimestampFromNSTimeInterval( |
| NSTimeInterval interval) { |
| return base::TimeDelta::FromSecondsD(interval).InMilliseconds(); |
| } |
| |
| static sky::InputEventPtr BasicInputEventFromRecognizer( |
| sky::EventType type, |
| UIGestureRecognizer* recognizer) { |
| auto input = sky::InputEvent::New(); |
| input->type = type; |
| input->time_stamp = InputEventTimestampFromNSTimeInterval( |
| CACurrentMediaTime()); |
| |
| input->gesture_data = sky::GestureData::New(); |
| |
| CGPoint windowCoordinates = [recognizer locationInView:recognizer.view]; |
| const CGFloat scale = [UIScreen mainScreen].scale; |
| input->gesture_data->x = windowCoordinates.x * scale; |
| input->gesture_data->y = windowCoordinates.y * scale; |
| return input.Pass(); |
| } |
| |
| @implementation SkySurface { |
| BOOL _platformViewInitialized; |
| CGPoint _lastScrollTranslation; |
| |
| sky::SkyEnginePtr _sky_engine; |
| scoped_ptr<sky::shell::ShellView> _shell_view; |
| |
| #ifndef NDEBUG |
| DocumentWatcher *_document_watcher; |
| #endif |
| } |
| |
| -(instancetype) initWithShellView:(sky::shell::ShellView *) shellView { |
| self = [super init]; |
| if (self) { |
| _shell_view.reset(shellView); |
| self.multipleTouchEnabled = YES; |
| [self installGestureRecognizers]; |
| } |
| return self; |
| } |
| |
| - (gfx::AcceleratedWidget)acceleratedWidget { |
| return (gfx::AcceleratedWidget)self.layer; |
| } |
| |
| - (void)layoutSubviews { |
| [super layoutSubviews]; |
| |
| [self configureLayerDefaults]; |
| |
| [self setupPlatformViewIfNecessary]; |
| |
| CGSize size = self.bounds.size; |
| CGFloat scale = [UIScreen mainScreen].scale; |
| |
| sky::ViewportMetricsPtr metrics = sky::ViewportMetrics::New(); |
| metrics->physical_width = size.width * scale; |
| metrics->physical_height = size.height * scale; |
| metrics->device_pixel_ratio = scale; |
| metrics->padding_top = |
| [UIApplication sharedApplication].statusBarFrame.size.height; |
| |
| [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent animated:NO]; |
| _sky_engine->OnViewportMetricsChanged(metrics.Pass()); |
| } |
| |
| - (void)configureLayerDefaults { |
| CAEAGLLayer* layer = reinterpret_cast<CAEAGLLayer*>(self.layer); |
| layer.allowsGroupOpacity = YES; |
| layer.opaque = YES; |
| CGFloat screenScale = [UIScreen mainScreen].scale; |
| layer.contentsScale = screenScale; |
| // Note: shouldRasterize is still NO. This is just a defensive measure |
| layer.rasterizationScale = screenScale; |
| } |
| |
| - (void)setupPlatformViewIfNecessary { |
| if (_platformViewInitialized) { |
| return; |
| } |
| |
| _platformViewInitialized = YES; |
| |
| [self notifySurfaceCreation]; |
| [self connectToEngineAndLoad]; |
| } |
| |
| - (sky::shell::PlatformViewMac*)platformView { |
| auto view = static_cast<sky::shell::PlatformViewMac*>(_shell_view->view()); |
| DCHECK(view); |
| return view; |
| } |
| |
| - (void)notifySurfaceCreation { |
| self.platformView->SurfaceCreated(self.acceleratedWidget); |
| } |
| |
| - (NSString*)skyInitialLoadURL { |
| NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults]; |
| NSString *target = [standardDefaults stringForKey:@"target"]; |
| NSString *server = [standardDefaults stringForKey:@"server"]; |
| if (server && target) { |
| return [NSString stringWithFormat:@"http://%@/%@", server, target]; |
| } |
| return [NSBundle mainBundle].infoDictionary[@"org.domokit.sky.load_url"]; |
| } |
| |
| - (NSString*)skyInitialBundleURL { |
| NSString *skyxBundlePath = [[NSBundle mainBundle] pathForResource:@"app" ofType:@"skyx"]; |
| #ifndef NDEBUG |
| NSFileManager *fileManager = [NSFileManager defaultManager]; |
| NSError *error = nil; |
| NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); |
| NSString *documentsDirectory = [paths objectAtIndex:0]; |
| NSString *skyxDocsPath = [documentsDirectory stringByAppendingPathComponent:@"app.skyx"]; |
| |
| // Write an empty file to help identify the correct simulator app by its bundle id. See sky_tool for its use. |
| NSString *bundleIDPath = [documentsDirectory stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]; |
| NSData *data = [[NSData alloc] initWithBytes:"" length:0]; |
| if (![data writeToFile:bundleIDPath options:NSDataWritingAtomic error:&error]) { |
| NSLog(@"Couldn't write the bundle id file %@: auto reloading on the iOS simulator won't work\n%@", bundleIDPath, error); |
| } |
| |
| if (skyxBundlePath != nil && [fileManager fileExistsAtPath:skyxDocsPath] == NO) { |
| if ([fileManager copyItemAtPath:skyxBundlePath toPath:skyxDocsPath error:&error]) { |
| return skyxDocsPath; |
| } |
| NSLog(@"Error encountered copying app.skyx from the Bundle to the Documents directory. Dynamic reloading will not be possible. %@", error); |
| return skyxBundlePath; |
| } |
| return skyxDocsPath; |
| #endif |
| return skyxBundlePath; |
| } |
| |
| - (void)connectToEngineAndLoad { |
| auto interface_request = mojo::GetProxy(&_sky_engine); |
| self.platformView->ConnectToEngine(interface_request.Pass()); |
| |
| NSString *endpoint = self.skyInitialBundleURL; |
| if (endpoint.length > 0) { |
| #ifndef NDEBUG |
| _document_watcher = [[DocumentWatcher alloc] initWithDocumentPath:endpoint callbackBlock:^{ |
| mojo::String string(endpoint.UTF8String); |
| _sky_engine->RunFromBundle(string); |
| }]; |
| #endif |
| // Load from bundle |
| mojo::String string(endpoint.UTF8String); |
| _sky_engine->RunFromBundle(string); |
| return; |
| } |
| |
| endpoint = self.skyInitialLoadURL; |
| if (endpoint.length > 0) { |
| // Load from URL |
| mojo::String string(endpoint.UTF8String); |
| _sky_engine->RunFromNetwork(string); |
| return; |
| } |
| } |
| |
| - (void)notifySurfaceDestruction { |
| self.platformView->SurfaceDestroyed(); |
| } |
| |
| #ifndef NDEBUG |
| - (void)didMoveToWindow { |
| if (self.window == nil) { |
| [_document_watcher cancel]; |
| [_document_watcher release]; |
| _document_watcher = nil; |
| } |
| } |
| #endif |
| |
| #pragma mark - UIResponder overrides for raw touches |
| |
| - (void)dispatchTouches:(NSSet*)touches phase:(UITouchPhase)phase { |
| auto eventType = EventTypeFromUITouchPhase(phase); |
| const CGFloat scale = [UIScreen mainScreen].scale; |
| |
| for (UITouch* touch in touches) { |
| auto input = sky::InputEvent::New(); |
| input->type = eventType; |
| input->time_stamp = InputEventTimestampFromNSTimeInterval(touch.timestamp); |
| |
| input->pointer_data = sky::PointerData::New(); |
| input->pointer_data->kind = sky::POINTER_KIND_TOUCH; |
| |
| #define LOWER_32(x) (*((int32_t *) &x)) |
| input->pointer_data->pointer = LOWER_32(touch); |
| #undef LOWER_32 |
| |
| CGPoint windowCoordinates = [touch locationInView:nil]; |
| |
| input->pointer_data->x = windowCoordinates.x * scale; |
| input->pointer_data->y = windowCoordinates.y * scale; |
| |
| _sky_engine->OnInputEvent(input.Pass()); |
| } |
| } |
| |
| - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { |
| [self dispatchTouches:touches phase:UITouchPhaseBegan]; |
| } |
| |
| - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event { |
| [self dispatchTouches:touches phase:UITouchPhaseMoved]; |
| } |
| |
| - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { |
| [self dispatchTouches:touches phase:UITouchPhaseEnded]; |
| } |
| |
| - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event { |
| [self dispatchTouches:touches phase:UITouchPhaseCancelled]; |
| } |
| |
| #pragma mark - Gesture Recognizers |
| |
| -(void) installGestureRecognizers { |
| // For: |
| // GESTURE_FLING_CANCEL |
| // GESTURE_FLING_START |
| UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] |
| initWithTarget:self action:@selector(onFling:)]; |
| swipe.cancelsTouchesInView = NO; |
| [self addGestureRecognizer: swipe]; |
| [swipe release]; |
| |
| // For: |
| // GESTURE_LONG_PRESS |
| // GESTURE_SHOW_PRESS |
| UILongPressGestureRecognizer *longPress = |
| [[UILongPressGestureRecognizer alloc] |
| initWithTarget:self action:@selector(onLongPress:)]; |
| longPress.cancelsTouchesInView = NO; |
| [self addGestureRecognizer: longPress]; |
| [longPress release]; |
| |
| // For: |
| // GESTURE_SCROLL_BEGIN |
| // GESTURE_SCROLL_END |
| // GESTURE_SCROLL_UPDATE |
| UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] |
| initWithTarget:self action:@selector(onScroll:)]; |
| pan.cancelsTouchesInView = NO; |
| [self addGestureRecognizer: pan]; |
| [pan release]; |
| |
| // For: |
| // GESTURE_TAP |
| // GESTURE_TAP_DOWN |
| UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] |
| initWithTarget:self action:@selector(onTap:)]; |
| tap.cancelsTouchesInView = NO; |
| [self addGestureRecognizer: tap]; |
| [tap release]; |
| } |
| |
| -(void) onFling:(UISwipeGestureRecognizer *) recognizer { |
| // Swipes are discrete gestures already. So there is no equivalent to a cancel |
| if (recognizer.state != UIGestureRecognizerStateEnded) { |
| return; |
| } |
| |
| auto input = BasicInputEventFromRecognizer( |
| sky::EVENT_TYPE_GESTURE_FLING_START, recognizer); |
| _sky_engine->OnInputEvent(input.Pass()); |
| } |
| |
| -(void) onLongPress:(UILongPressGestureRecognizer *) recognizer { |
| if (recognizer.state != UIGestureRecognizerStateEnded) { |
| return; |
| } |
| |
| auto input = BasicInputEventFromRecognizer(sky::EVENT_TYPE_GESTURE_LONG_PRESS, |
| recognizer); |
| _sky_engine->OnInputEvent(input.Pass()); |
| } |
| |
| -(void) onScroll:(UIPanGestureRecognizer *) recognizer { |
| sky::EventType type = sky::EVENT_TYPE_UNKNOWN; |
| switch (recognizer.state) { |
| case UIGestureRecognizerStateBegan: |
| _lastScrollTranslation = CGPointZero; |
| type = sky::EVENT_TYPE_GESTURE_SCROLL_BEGIN; |
| break; |
| case UIGestureRecognizerStateChanged: |
| type = sky::EVENT_TYPE_GESTURE_SCROLL_UPDATE; |
| break; |
| case UIGestureRecognizerStateEnded: |
| case UIGestureRecognizerStateCancelled: |
| case UIGestureRecognizerStateFailed: |
| type = sky::EVENT_TYPE_GESTURE_SCROLL_END; |
| break; |
| default: |
| break; |
| } |
| |
| if (type == sky::EVENT_TYPE_UNKNOWN) { |
| return; |
| } |
| |
| auto input = BasicInputEventFromRecognizer(type, recognizer); |
| auto scale = [UIScreen mainScreen].scale; |
| auto translation = [recognizer translationInView: self]; |
| auto velocity = [recognizer velocityInView: self]; |
| |
| input->gesture_data->dx = (translation.x - _lastScrollTranslation.x) * scale; |
| input->gesture_data->dy = (translation.y - _lastScrollTranslation.y) * scale; |
| |
| _lastScrollTranslation = translation; |
| |
| input->gesture_data->velocityX = velocity.x * scale; |
| input->gesture_data->velocityY = velocity.y * scale; |
| |
| _sky_engine->OnInputEvent(input.Pass()); |
| } |
| |
| -(void) onTap:(UITapGestureRecognizer *) recognizer { |
| |
| if (recognizer.state != UIGestureRecognizerStateEnded) { |
| return; |
| } |
| |
| auto input = BasicInputEventFromRecognizer(sky::EVENT_TYPE_GESTURE_TAP, |
| recognizer); |
| _sky_engine->OnInputEvent(input.Pass()); |
| } |
| |
| #pragma mark - Misc. |
| |
| + (Class)layerClass { |
| return [CAEAGLLayer class]; |
| } |
| |
| - (void)dealloc { |
| [self notifySurfaceDestruction]; |
| [super dealloc]; |
| } |
| |
| @end |