| /* |
| * Copyright (C) 2021 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #import "config.h" |
| #import "WKModelView.h" |
| |
| #if ENABLE(ARKIT_INLINE_PREVIEW_IOS) |
| |
| #import "Logging.h" |
| #import "RemoteLayerTreeViews.h" |
| #import "WKModelInteractionGestureRecognizer.h" |
| #import "WebsiteDataStore.h" |
| #import <WebCore/Model.h> |
| #import <pal/spi/cocoa/QuartzCoreSPI.h> |
| #import <pal/spi/ios/SystemPreviewSPI.h> |
| #import <wtf/Assertions.h> |
| #import <wtf/FileSystem.h> |
| #import <wtf/RetainPtr.h> |
| #import <wtf/SoftLinking.h> |
| #import <wtf/UUID.h> |
| |
| SOFT_LINK_PRIVATE_FRAMEWORK(AssetViewer); |
| SOFT_LINK_CLASS(AssetViewer, ASVInlinePreview); |
| |
| @implementation WKModelView { |
| RetainPtr<ASVInlinePreview> _preview; |
| RetainPtr<WKModelInteractionGestureRecognizer> _modelInteractionGestureRecognizer; |
| String _filePath; |
| CGRect _lastBounds; |
| } |
| |
| - (ASVInlinePreview *)preview |
| { |
| return _preview.get(); |
| } |
| |
| - (instancetype)initWithFrame:(CGRect)frame |
| { |
| return nil; |
| } |
| |
| - (instancetype)initWithCoder:(NSCoder *)coder |
| { |
| return nil; |
| } |
| |
| - (instancetype)initWithModel:(WebCore::Model&)model |
| { |
| self = [super initWithFrame:CGRectZero]; |
| if (!self) |
| return nil; |
| |
| if (![self createFileForModel:model]) |
| return self; |
| |
| _preview = adoptNS([allocASVInlinePreviewInstance() initWithFrame:self.bounds]); |
| [self.layer addSublayer:[_preview layer]]; |
| |
| auto url = adoptNS([[NSURL alloc] initFileURLWithPath:_filePath]); |
| |
| [_preview setupRemoteConnectionWithCompletionHandler:^(NSError *contextError) { |
| if (contextError) { |
| LOG(ModelElement, "Unable to create remote connection, error: %@", [contextError localizedDescription]); |
| return; |
| } |
| |
| [_preview preparePreviewOfFileAtURL:url.get() completionHandler:^(NSError *loadError) { |
| if (loadError) { |
| LOG(ModelElement, "Unable to load file, error: %@", [loadError localizedDescription]); |
| return; |
| } |
| |
| LOG(ModelElement, "File loaded successfully."); |
| [self updateBounds]; |
| }]; |
| }]; |
| |
| _modelInteractionGestureRecognizer = adoptNS([[WKModelInteractionGestureRecognizer alloc] init]); |
| [self addGestureRecognizer:_modelInteractionGestureRecognizer.get()]; |
| |
| return self; |
| } |
| |
| - (BOOL)createFileForModel:(WebCore::Model&)model |
| { |
| auto pathToDirectory = WebKit::WebsiteDataStore::defaultModelElementCacheDirectory(); |
| if (pathToDirectory.isEmpty()) |
| return NO; |
| |
| auto directoryExists = FileSystem::fileExists(pathToDirectory); |
| if (directoryExists && FileSystem::fileTypeFollowingSymlinks(pathToDirectory) != FileSystem::FileType::Directory) { |
| ASSERT_NOT_REACHED(); |
| return NO; |
| } |
| |
| if (!directoryExists && !FileSystem::makeAllDirectories(pathToDirectory)) { |
| ASSERT_NOT_REACHED(); |
| return NO; |
| } |
| |
| auto fileName = FileSystem::encodeForFileName(createCanonicalUUIDString()) + ".usdz"; |
| auto filePath = FileSystem::pathByAppendingComponent(pathToDirectory, fileName); |
| auto file = FileSystem::openFile(filePath, FileSystem::FileOpenMode::Write); |
| if (file <= 0) |
| return NO; |
| |
| auto byteCount = static_cast<std::size_t>(FileSystem::writeToFile(file, model.data()->data(), model.data()->size())); |
| ASSERT_UNUSED(byteCount, byteCount == model.data()->size()); |
| FileSystem::closeFile(file); |
| _filePath = filePath; |
| |
| return YES; |
| } |
| |
| - (void)layoutSubviews |
| { |
| [super layoutSubviews]; |
| if (!CGRectEqualToRect(_lastBounds, CGRectZero)) |
| [self updateBounds]; |
| } |
| |
| - (void)updateBounds |
| { |
| auto bounds = self.bounds; |
| if (CGRectEqualToRect(_lastBounds, bounds)) |
| return; |
| |
| _lastBounds = bounds; |
| |
| [_preview updateFrame:bounds completionHandler:^(CAFenceHandle *fenceHandle, NSError *error) { |
| if (error) { |
| LOG(ModelElement, "Unable to update frame, error: %@", [error localizedDescription]); |
| [fenceHandle invalidate]; |
| return; |
| } |
| |
| [self.layer.context addFence:fenceHandle]; |
| [fenceHandle invalidate]; |
| }]; |
| } |
| |
| - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event |
| { |
| // The layer of this view is empty and the sublayer is rendered remotely, so the basic implementation |
| // of hitTest:withEvent: will return nil due to ignoring empty subviews. So we can simply check whether |
| // the hit-testing point is within bounds. |
| return [self pointInside:point withEvent:event] ? self : nil; |
| } |
| |
| @end |
| |
| #endif |
| |