| /* |
| * 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) |