WAKScrollView.mm   [plain text]

/*
* Copyright (C) 2005-2018 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.
*/

#import "config.h"
#import "WAKScrollView.h"

#if PLATFORM(IOS_FAMILY)

#import "WAKAppKitStubs.h"
#import "WAKClipView.h"
#import "WAKViewInternal.h"
#import "WAKWindow.h"
#import "WebEvent.h"

@interface WAKClipView(PrivateAPI)
- (void)_setDocumentView:(WAKView *)aView;
@end

// FIXME: get rid of this and use polymorphic response to notifications.
@interface WAKScrollView()
- (void)_adjustScrollers;
@end

static void _notificationCallback(WKViewRef v, WKViewNotificationType type, void *userInfo)
{
UNUSED_PARAM(v);
switch (type){
case WKViewNotificationViewFrameSizeChanged: {
WAKScrollView *scrollView = (WAKScrollView *)userInfo;
ASSERT(scrollView);
ASSERT([scrollView isKindOfClass:[WAKScrollView class]]);
[scrollView _adjustScrollers];
break;
}
default: {
break;
}
}
}

@implementation WAKScrollView

@synthesize delegate;

- (id)initWithFrame:(CGRect)rect
{
WKViewRef view = WKViewCreateWithFrame(rect, &viewContext);
viewContext.notificationCallback = _notificationCallback;
viewContext.notificationUserInfo = self;
self = [super _initWithViewRef:(WKViewRef)view];
WKRelease(view);

_contentView = [[WAKClipView alloc] initWithFrame:rect];
[self addSubview:_contentView];

return self;
}

- (void)dealloc
{
[_documentView autorelease];
[_contentView release];
[super dealloc];
}

- (BOOL)_selfHandleEvent:(WebEvent *)event
{
switch (event.type) {
case WebEventScrollWheel:
[self scrollWheel:event];
return YES;
default:
return NO;
}
}

- (void)setHasVerticalScroller:(BOOL)flag
{
UNUSED_PARAM(flag);
}

- (BOOL)hasVerticalScroller
{
return NO;
}

- (void)setHasHorizontalScroller:(BOOL)flag
{
UNUSED_PARAM(flag);
}

- (BOOL)hasHorizontalScroller
{
return NO;
}

- (CGRect)documentVisibleRect
{
return [_contentView documentVisibleRect];
}

- (void)setDocumentView:(WAKView *)view
{
if (view != _documentView) {
[_documentView release];
_documentView = [view retain];
[_contentView _setDocumentView:view];
}
}

- (id)documentView
{
return _documentView;
}

- (WAKClipView *)contentView
{
return _contentView;
}

- (void)setDrawsBackground:(BOOL)flag
{
UNUSED_PARAM(flag);
}

- (BOOL)drawsBackground
{
return NO;
}

- (void)setLineScroll:(float)value
{
UNUSED_PARAM(value);
}

- (float)verticalLineScroll
{
return 0;
}

- (float)horizontalLineScroll
{
return 0;
}

- (void)reflectScrolledClipView:(WAKClipView *)aClipView
{
UNUSED_PARAM(aClipView);
}

- (void)drawRect:(CGRect)rect
{
UNUSED_PARAM(rect);
}

// WebCoreFrameView methods

- (void)setHorizontalScrollingMode:(WebCore::ScrollbarMode)mode
{
UNUSED_PARAM(mode);
}

- (void)setVerticalScrollingMode:(WebCore::ScrollbarMode)mode
{
UNUSED_PARAM(mode);
}

- (void)setScrollingMode:(WebCore::ScrollbarMode)mode
{
UNUSED_PARAM(mode);
}

- (WebCore::ScrollbarMode)horizontalScrollingMode
{
return WebCore::ScrollbarAuto;
}

- (WebCore::ScrollbarMode)verticalScrollingMode
{
return WebCore::ScrollbarAuto;
}

#pragma mark -
#pragma mark WebCoreFrameScrollView protocol

- (void)setScrollingModes:(WebCore::ScrollbarMode)hMode vertical:(WebCore::ScrollbarMode)vMode andLock:(BOOL)lock
{
UNUSED_PARAM(hMode);
UNUSED_PARAM(vMode);
UNUSED_PARAM(lock);
}

- (void)scrollingModes:(WebCore::ScrollbarMode*)hMode vertical:(WebCore::ScrollbarMode*)vMode
{
UNUSED_PARAM(hMode);
UNUSED_PARAM(vMode);
}

- (void)setScrollBarsSuppressed:(BOOL)suppressed repaintOnUnsuppress:(BOOL)repaint
{
UNUSED_PARAM(suppressed);
UNUSED_PARAM(repaint);
}

- (void)setScrollOrigin:(NSPoint)scrollOrigin updatePositionAtAll:(BOOL)updatePositionAtAll immediately:(BOOL)updatePositionImmediately
{
UNUSED_PARAM(updatePositionAtAll);
UNUSED_PARAM(updatePositionImmediately);

// The cross-platform ScrollView call already checked to see if the old/new scroll origins were the same or not
// so we don't have to check for equivalence here.
_scrollOrigin = scrollOrigin;

[_documentView setBoundsOrigin:NSMakePoint(-scrollOrigin.x, -scrollOrigin.y)];
}

- (NSPoint)scrollOrigin
{
return _scrollOrigin;
}

#pragma mark -

static bool shouldScroll(WAKScrollView *scrollView, CGPoint scrollPoint)
{
// We can scroll as long as we are not the last scroll view.
WAKView *view = scrollView;
while ((view = [view superview]))
if ([view isKindOfClass:[WAKScrollView class]])
return YES;

id delegate = [scrollView delegate];
SEL selector = @selector(scrollView:shouldScrollToPoint:);
return delegate == nil || ![delegate respondsToSelector:selector] || [delegate scrollView:scrollView shouldScrollToPoint:scrollPoint];
}

static float viewDocumentScrollableLength(WAKScrollView *scrollView, bool horizontalOrientation)
{
float scrollableAmount = 0, documentLength = 0, clipLength = 0;
WAKView *documentView = [scrollView documentView];
ASSERT(documentView);

CGRect frame = [documentView frame];
if (horizontalOrientation)
documentLength = frame.size.width;
else
documentLength = frame.size.height;

WAKClipView *clipView = [scrollView contentView];
ASSERT_WITH_MESSAGE(clipView, "The WAKClipView is supposed to be created by the WAKScrollView at initialization.");
if (clipView) {
CGRect frame = [clipView frame];
if (horizontalOrientation)
clipLength = frame.size.width;
else
clipLength = frame.size.height;
}

scrollableAmount = documentLength - clipLength;
if (scrollableAmount <= 0)
scrollableAmount = 0;

return scrollableAmount;
}

static float updateScrollerWithDocumentPosition(WAKScrollView *scrollView, bool horizontalOrientation, float documentPosition)
{
float documentOriginPosition = 0.;
if (documentPosition > 0) {
float scrollableLength = viewDocumentScrollableLength(scrollView, horizontalOrientation);
if (scrollableLength > 0) {
float scrolledLength = MIN(documentPosition, scrollableLength);
documentOriginPosition = -scrolledLength;
}
}

return documentOriginPosition;
}

static bool setDocumentViewOrigin(WAKScrollView *scrollView, WAKView *documentView, CGPoint newDocumentOrigin)
{
ASSERT(documentView);
ASSERT(documentView == [scrollView documentView]);

CGPoint oldDocumentOrigin = [documentView frame].origin;
if (!CGPointEqualToPoint(oldDocumentOrigin, newDocumentOrigin)) {
[documentView setFrameOrigin:newDocumentOrigin];
[scrollView setNeedsDisplay:YES];
WKViewRef documentViewRef = [documentView _viewRef];
if (documentViewRef->context && documentViewRef->context->notificationCallback)
documentViewRef->context->notificationCallback(documentViewRef, WKViewNotificationViewDidScroll, documentViewRef->context->notificationUserInfo);
return true;
}
return false;
}

static BOOL scrollViewToPoint(WAKScrollView *scrollView, CGPoint point)
{
WAKView *documentView = [scrollView documentView];
if (!documentView)
return NO;

if (!shouldScroll(scrollView, point))
return NO;

CGPoint newDocumentOrigin;
newDocumentOrigin.x = updateScrollerWithDocumentPosition(scrollView, /* horizontal? = */ true, point.x);
newDocumentOrigin.y = updateScrollerWithDocumentPosition(scrollView, /* horizontal? = */ false, point.y);
return setDocumentViewOrigin(scrollView, documentView, newDocumentOrigin);
}

- (void)scrollPoint:(NSPoint)point
{
scrollViewToPoint(self, point);
}

- (void)scrollWheel:(WebEvent *)anEvent
{
if (!_documentView)
return [[self nextResponder] scrollWheel:anEvent];

CGPoint origin = [_documentView frame].origin;
origin.x = roundf(-origin.x - anEvent.deltaX);
origin.y = roundf(-origin.y - anEvent.deltaY);

if (!scrollViewToPoint(self, origin))
return [[self nextResponder] scrollWheel:anEvent];
}

- (CGRect)unobscuredContentRect
{
// Only called by WebCore::ScrollView::unobscuredContentRect
WAKView* view = self;
while ((view = [view superview])) {
if ([view isKindOfClass:[WAKScrollView class]])
return [self documentVisibleRect];
}

WAKWindow* window = [self window];
// If we don't have a WAKWindow, we must be in a offscreen WebView.
if (!window)
return [self documentVisibleRect];

CGRect windowVisibleRect = CGRectIntegral([window exposedScrollViewRect]);
return [_documentView convertRect:windowVisibleRect fromView:nil];
}

- (CGRect)exposedContentRect
{
// Only called by WebCore::ScrollView::exposedContentRect
WAKView* view = self;
while ((view = [view superview])) {
if ([view isKindOfClass:[WAKScrollView class]])
return [self documentVisibleRect];
}

WAKWindow* window = [self window];
// If we don't have a WAKWindow, we must be in a offscreen WebView.
if (!window)
return [self documentVisibleRect];

CGRect windowVisibleRect = CGRectIntegral([window extendedVisibleRect]);
return [_documentView convertRect:windowVisibleRect fromView:nil];
}

- (void)setActualScrollPosition:(CGPoint)point
{
WAKView* view = self;
while ((view = [view superview])) {
if ([view isKindOfClass:[WAKScrollView class]]) {
// No need for coordinate transformation if what is being scrolled is a subframe
[self scrollPoint:point];
return;
}
}

if (!_documentView)
return;
CGPoint windowPoint = [_documentView convertPoint:point toView:nil];
[self scrollPoint:windowPoint];
}

- (NSString *)description
{
NSMutableString *description = [NSMutableString stringWithFormat:@"<%@: ; ", [super description]];

[description appendFormat:@"documentView: WAK: %p; ", _documentView];

CGRect frame = [self documentVisibleRect];
[description appendFormat:@"documentVisible = (%g %g; %g %g); ", frame.origin.x, frame.origin.y, frame.size.width, frame.size.height];

frame = [self unobscuredContentRect];
[description appendFormat:@"actualDocumentVisible = (%g %g; %g %g)>", frame.origin.x, frame.origin.y, frame.size.width, frame.size.height];

return description;
}

- (BOOL)inProgrammaticScroll
{
return NO;
}

- (void)_adjustScrollers
{
// Set the clip view's size to the scroll view's size so the document view can use the correct clip view size when laying out.
[_contentView setFrameSize:[self bounds].size];

if (_documentView) {
CGPoint newDocumentOrigin = [_documentView frame].origin;
setDocumentViewOrigin(self, _documentView, newDocumentOrigin);
}
}

@end

#endif // PLATFORM(IOS_FAMILY)