blob: 64c8f06dadfe11621b7134ae709b012ecef9efce [file] [log] [blame]
/*
* Copyright (C) 2017 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. AND ITS CONTRIBUTORS ``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 ITS 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.
*/
#include "config.h"
#include "DragDropInteractionState.h"
#if ENABLE(DRAG_SUPPORT) && PLATFORM(IOS)
#import <WebCore/DragItem.h>
#import <WebCore/Image.h>
using namespace WebCore;
using namespace WebKit;
namespace WebKit {
static UIDragItem *dragItemMatchingIdentifier(id <UIDragSession> session, NSInteger identifier)
{
for (UIDragItem *item in session.items) {
id context = item.privateLocalContext;
if ([context isKindOfClass:[NSNumber class]] && [context integerValue] == identifier)
return item;
}
return nil;
}
static UITargetedDragPreview *createTargetedDragPreview(UIImage *image, UIView *rootView, UIView *previewContainer, const FloatRect& frameInRootViewCoordinates, const Vector<FloatRect>& clippingRectsInFrameCoordinates, UIColor *backgroundColor)
{
if (frameInRootViewCoordinates.isEmpty() || !image)
return nullptr;
NSMutableArray *clippingRectValuesInFrameCoordinates = [NSMutableArray arrayWithCapacity:clippingRectsInFrameCoordinates.size()];
FloatRect frameInContainerCoordinates = [rootView convertRect:frameInRootViewCoordinates toView:previewContainer];
if (frameInContainerCoordinates.isEmpty())
return nullptr;
FloatSize scalingRatio = frameInContainerCoordinates.size() / frameInRootViewCoordinates.size();
for (auto rect : clippingRectsInFrameCoordinates) {
rect.scale(scalingRatio);
[clippingRectValuesInFrameCoordinates addObject:[NSValue valueWithCGRect:rect]];
}
auto imageView = adoptNS([[UIImageView alloc] initWithImage:image]);
[imageView setFrame:frameInContainerCoordinates];
RetainPtr<UIDragPreviewParameters> parameters;
if (clippingRectValuesInFrameCoordinates.count)
parameters = adoptNS([[UIDragPreviewParameters alloc] initWithTextLineRects:clippingRectValuesInFrameCoordinates]);
else
parameters = adoptNS([[UIDragPreviewParameters alloc] init]);
if (backgroundColor)
[parameters setBackgroundColor:backgroundColor];
CGPoint centerInContainerCoordinates = { CGRectGetMidX(frameInContainerCoordinates), CGRectGetMidY(frameInContainerCoordinates) };
auto target = adoptNS([[UIDragPreviewTarget alloc] initWithContainer:previewContainer center:centerInContainerCoordinates]);
auto dragPreview = adoptNS([[UITargetedDragPreview alloc] initWithView:imageView.get() parameters:parameters.get() target:target.get()]);
return dragPreview.autorelease();
}
static RetainPtr<UIImage> uiImageForImage(Image* image)
{
if (!image)
return nullptr;
auto cgImage = image->nativeImage();
if (!cgImage)
return nullptr;
return adoptNS([[UIImage alloc] initWithCGImage:cgImage.get()]);
}
static bool shouldUseDragImageToCreatePreviewForDragSource(const DragSourceState& source)
{
if (!source.image)
return false;
return source.action & (DragSourceActionDHTML | DragSourceActionImage);
}
static bool shouldUseTextIndicatorToCreatePreviewForDragSource(const DragSourceState& source)
{
if (!source.indicatorData)
return false;
if (source.action & (DragSourceActionLink | DragSourceActionSelection))
return true;
#if ENABLE(ATTACHMENT_ELEMENT)
if (source.action & DragSourceActionAttachment)
return true;
#endif
return false;
}
std::optional<DragSourceState> DragDropInteractionState::activeDragSourceForItem(UIDragItem *item) const
{
if (![item.privateLocalContext isKindOfClass:[NSNumber class]])
return std::nullopt;
auto identifier = [(NSNumber *)item.privateLocalContext integerValue];
for (auto& source : m_activeDragSources) {
if (source.itemIdentifier == identifier)
return source;
}
return std::nullopt;
}
bool DragDropInteractionState::anyActiveDragSourceIs(WebCore::DragSourceAction action) const
{
for (auto& source : m_activeDragSources) {
if (source.action & action)
return true;
}
return false;
}
void DragDropInteractionState::prepareForDragSession(id <UIDragSession> session, dispatch_block_t completionHandler)
{
m_dragSession = session;
m_dragStartCompletionBlock = completionHandler;
}
void DragDropInteractionState::dragSessionWillBegin()
{
m_didBeginDragging = true;
updatePreviewsForActiveDragSources();
}
UITargetedDragPreview *DragDropInteractionState::previewForDragItem(UIDragItem *item, UIView *contentView, UIView *previewContainer) const
{
auto foundSource = activeDragSourceForItem(item);
if (!foundSource)
return nil;
auto& source = foundSource.value();
if (shouldUseDragImageToCreatePreviewForDragSource(source))
return createTargetedDragPreview(source.image.get(), contentView, previewContainer, source.dragPreviewFrameInRootViewCoordinates, { }, nil);
if (shouldUseTextIndicatorToCreatePreviewForDragSource(source)) {
auto indicator = source.indicatorData.value();
auto textIndicatorImage = uiImageForImage(indicator.contentImage.get());
return createTargetedDragPreview(textIndicatorImage.get(), contentView, previewContainer, indicator.textBoundingRectInRootViewCoordinates, indicator.textRectsInBoundingRectCoordinates, [UIColor colorWithCGColor:cachedCGColor(indicator.estimatedBackgroundColor)]);
}
return nil;
}
void DragDropInteractionState::dragSessionWillDelaySetDownAnimation(dispatch_block_t completion)
{
m_dragCancelSetDownBlock = completion;
}
bool DragDropInteractionState::shouldRequestAdditionalItemForDragSession(id <UIDragSession> session) const
{
return m_dragSession == session && !m_addDragItemCompletionBlock && !m_dragStartCompletionBlock;
}
void DragDropInteractionState::dragSessionWillRequestAdditionalItem(void (^completion)(NSArray <UIDragItem *> *))
{
clearStagedDragSource();
m_addDragItemCompletionBlock = completion;
}
void DragDropInteractionState::dropSessionDidEnterOrUpdate(id <UIDropSession> session, const DragData& dragData)
{
m_dropSession = session;
m_lastGlobalPosition = dragData.globalPosition();
}
void DragDropInteractionState::stageDragItem(const DragItem& item, UIImage *dragImage)
{
static NSInteger currentDragSourceItemIdentifier = 0;
m_adjustedPositionForDragEnd = item.eventPositionInContentCoordinates;
m_stagedDragSource = {{
static_cast<DragSourceAction>(item.sourceAction),
item.eventPositionInContentCoordinates,
item.dragPreviewFrameInRootViewCoordinates,
dragImage,
item.image.indicatorData(),
item.title.isEmpty() ? nil : (NSString *)item.title,
item.url.isEmpty() ? nil : (NSURL *)item.url,
true, // We assume here that drag previews need to be updated until proven otherwise in updatePreviewsForActiveDragSources().
++currentDragSourceItemIdentifier
}};
}
bool DragDropInteractionState::hasStagedDragSource() const
{
return m_stagedDragSource && stagedDragSource().action != WebCore::DragSourceActionNone;
}
void DragDropInteractionState::clearStagedDragSource(DidBecomeActive didBecomeActive)
{
if (didBecomeActive == DidBecomeActive::Yes)
m_activeDragSources.append(stagedDragSource());
m_stagedDragSource = std::nullopt;
}
void DragDropInteractionState::dragAndDropSessionsDidEnd()
{
// If any of UIKit's completion blocks are still in-flight when the drag interaction ends, we need to ensure that they are still invoked
// to prevent UIKit from getting into an inconsistent state.
if (auto completionBlock = takeDragCancelSetDownBlock())
completionBlock();
if (auto completionBlock = takeAddDragItemCompletionBlock())
completionBlock(@[ ]);
if (auto completionBlock = takeDragStartCompletionBlock())
completionBlock();
}
void DragDropInteractionState::updatePreviewsForActiveDragSources()
{
for (auto& source : m_activeDragSources) {
if (!source.possiblyNeedsDragPreviewUpdate)
continue;
if (source.action & DragSourceActionImage || !(source.action & DragSourceActionLink)) {
// Currently, non-image links are the only type of source for which we need to update
// drag preview providers after the initial lift. All other dragged content should maintain
// the same targeted drag preview used during the lift animation.
continue;
}
UIDragItem *dragItem = dragItemMatchingIdentifier(m_dragSession.get(), source.itemIdentifier);
if (!dragItem)
continue;
auto linkDraggingCenter = source.adjustedOrigin;
RetainPtr<NSString> title = (NSString *)source.linkTitle;
RetainPtr<NSURL> url = (NSURL *)source.linkURL;
dragItem.previewProvider = [title, url, linkDraggingCenter] () -> UIDragPreview * {
UIURLDragPreviewView *previewView = [UIURLDragPreviewView viewWithTitle:title.get() URL:url.get()];
previewView.center = linkDraggingCenter;
UIDragPreviewParameters *parameters = [[[UIDragPreviewParameters alloc] initWithTextLineRects:@[ [NSValue valueWithCGRect:previewView.bounds] ]] autorelease];
return [[[UIDragPreview alloc] initWithView:previewView parameters:parameters] autorelease];
};
source.possiblyNeedsDragPreviewUpdate = false;
}
}
} // namespace WebKit
#endif // ENABLE(DRAG_SUPPORT) && PLATFORM(IOS)