blob: 7e9370fa3365760da05eaa27e52ec7f0bd22cf00 [file] [log] [blame]
/*
* 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