WKPDFPageNumberIndicator.mm   [plain text]


/*
 * Copyright (C) 2014 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 "WKPDFPageNumberIndicator.h"

#if PLATFORM(IOS)

#import "UIKitSPI.h"
#import <WebCore/LocalizedStrings.h>
#import <WebCore/QuartzCoreSPI.h>
#import <wtf/RetainPtr.h>
#import <wtf/text/WTFString.h>

const CGFloat indicatorMargin = 20;
const CGFloat indicatorVerticalPadding = 6;
const CGFloat indicatorHorizontalPadding = 10;
const CGFloat indicatorCornerRadius = 7;
const CGFloat indicatorLabelOpacity = 0.4;
const CGFloat indicatorFontSize = 16;

const NSTimeInterval indicatorTimeout = 2;
const NSTimeInterval indicatorFadeDuration = 0.5;
const NSTimeInterval indicatorMoveDuration = 0.3;

@implementation WKPDFPageNumberIndicator {
    RetainPtr<UILabel> _label;
    RetainPtr<_UIBackdropView> _backdropView;
    RetainPtr<NSTimer> _timer;

    bool _hasValidPageCountAndCurrentPage;
}

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (!self)
        return nil;

    self.alpha = 0;
    self.layer.allowsGroupOpacity = NO;
    self.layer.allowsGroupBlending = NO;

    _backdropView = adoptNS([[_UIBackdropView alloc] initWithPrivateStyle:_UIBackdropViewStyle_Light]);
    [_backdropView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
    [self addSubview:_backdropView.get()];
    [self _makeRoundedCorners];

    _label = adoptNS([[UILabel alloc] initWithFrame:CGRectZero]);

    [_label setOpaque:NO];
    [_label setBackgroundColor:nil];
    [_label setTextAlignment:NSTextAlignmentCenter];
    [_label setFont:[UIFont boldSystemFontOfSize:indicatorFontSize]];
    [_label setTextColor:[UIColor blackColor]];
    [_label setAlpha:indicatorLabelOpacity];
    [_label setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
    [[_label layer] setCompositingFilter:[CAFilter filterWithType:kCAFilterPlusD]];

    [self addSubview:_label.get()];
    
    return self;
}

- (void)dealloc
{
    [_timer invalidate];
    [super dealloc];
}

- (void)setCurrentPageNumber:(unsigned)currentPageNumber
{
    if (_currentPageNumber == currentPageNumber)
        return;

    _currentPageNumber = currentPageNumber;
    [self _updateLabel];
}

- (void)setPageCount:(unsigned)pageCount
{
    if (_pageCount == pageCount)
        return;

    _pageCount = pageCount;
    [self _updateLabel];
}

- (void)show
{
    self.alpha = 1;

    if (_timer)
        [_timer setFireDate:[NSDate dateWithTimeIntervalSinceNow:indicatorTimeout]];
    else
        _timer = [NSTimer scheduledTimerWithTimeInterval:indicatorTimeout target:self selector:@selector(hide:) userInfo:nil repeats:NO];
}

- (void)hide
{
    [self hide:nil];
}

- (void)hide:(NSTimer *)timer
{
    [UIView animateWithDuration:indicatorFadeDuration animations:^{
        self.alpha = 0;
    }];

    [_timer invalidate];
    _timer = nullptr;
}

- (void)moveToPoint:(CGPoint)point animated:(BOOL)animated
{
    point.x += indicatorMargin;
    point.y += indicatorMargin;

    void (^animations)() = ^{
        self.frameOrigin = point;
    };
    if (animated)
        [UIView animateWithDuration:indicatorMoveDuration animations:animations];
    else
        animations();
}

- (CGSize)sizeThatFits:(CGSize)size
{
    CGSize labelSize = [_label sizeThatFits:[_label size]];
    labelSize.width += 2 * indicatorHorizontalPadding;
    labelSize.height += 2 * indicatorVerticalPadding;
    return labelSize;
}

- (void)_updateLabel
{
    [_label setText:[NSString localizedStringWithFormat:WEB_UI_STRING("%d of %d", "Label for PDF page number indicator."), _currentPageNumber, _pageCount]];
    [self sizeToFit];

    if (!_pageCount || !_currentPageNumber)
        return;

    // We don't want to show the indicator when the PDF loads, but rather on the first subsequent change.
    if (_hasValidPageCountAndCurrentPage)
        [self show];
    _hasValidPageCountAndCurrentPage = true;
}

- (void)_makeRoundedCorners
{
    CGSize cornerImageSize = CGSizeMake(2 * indicatorCornerRadius + 2, 2 * indicatorCornerRadius + 2);
    CGRect cornerImageBounds = CGRectMake(0, 0, cornerImageSize.width, cornerImageSize.height);

    UIGraphicsBeginImageContextWithOptions(cornerImageSize, NO, [UIScreen mainScreen].scale);

    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSaveGState(context);
    CGContextAddRect(context, cornerImageBounds);

    UIBezierPath *cornerPath = [UIBezierPath bezierPathWithRoundedRect:cornerImageBounds byRoundingCorners:(UIRectCorner)UIAllCorners cornerRadii:CGSizeMake(indicatorCornerRadius, indicatorCornerRadius)];
    CGContextAddPath(context, [cornerPath CGPath]);
    CGContextEOClip(context);
    CGContextFillRect(context, cornerImageBounds);
    CGContextRestoreGState(context);

    UIImage *cornerImage = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    UIView *contentView = [_backdropView contentView];

    UIEdgeInsets capInsets = UIEdgeInsetsMake(indicatorCornerRadius, indicatorCornerRadius, indicatorCornerRadius, indicatorCornerRadius);
    cornerImage = [cornerImage resizableImageWithCapInsets:capInsets];

    RetainPtr<UIImageView> cornerMaskView = adoptNS([[UIImageView alloc] initWithImage:cornerImage]);
    [cornerMaskView setAlpha:0];
    [cornerMaskView _setBackdropMaskViewFlags:_UIBackdropMaskViewAll];
    [cornerMaskView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
    [cornerMaskView setFrame:contentView.bounds];

    [contentView addSubview:cornerMaskView.get()];
}

@end

#endif /* PLATFORM(IOS) */