blob: d1424304430fb137416d1bda47cc831fa22f17a1 [file] [log] [blame]
/*
* Copyright (C) 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 "WKFormColorPicker.h"
#if ENABLE(INPUT_TYPE_COLOR) && PLATFORM(IOS_FAMILY)
#import "FocusedElementInformation.h"
#import "UserInterfaceIdiom.h"
#import "WKContentViewInteraction.h"
#import "WKFormPopover.h"
#import "WebPageProxy.h"
#import <WebCore/Color.h>
#import <WebCore/ColorSerialization.h>
#import <wtf/SoftLinking.h>
SOFT_LINK_PRIVATE_FRAMEWORK(PencilKit)
SOFT_LINK_CLASS(PencilKit, PKColorMatrixView)
static const CGFloat additionalKeyboardAffordance = 80;
static const CGFloat colorSelectionIndicatorBorderWidth = 4;
static const CGFloat colorSelectionIndicatorCornerRadius = 9;
static const CGFloat pickerWidthForPopover = 280;
static const CGFloat topColorMatrixPadding = 5;
#if ENABLE(DATALIST_ELEMENT)
static const size_t maxColorSuggestions = 12;
#endif
using namespace WebKit;
#pragma mark - PKColorMatrixView
@interface PKColorMatrixView
+ (NSArray<NSArray<UIColor *> *> *)defaultColorMatrix;
@end
#pragma mark - WKColorButton
@interface WKColorButton : UIButton
@property (nonatomic, strong) UIColor *color;
+ (instancetype)colorButtonWithColor:(UIColor *)color;
@end
@implementation WKColorButton
+ (instancetype)colorButtonWithColor:(UIColor *)color
{
WKColorButton *colorButton = [WKColorButton buttonWithType:UIButtonTypeCustom];
colorButton.color = color;
colorButton.backgroundColor = color;
return colorButton;
}
- (void)dealloc
{
[_color release];
_color = nil;
[super dealloc];
}
@end
#pragma mark - WKColorMatrixView
@interface WKColorMatrixView : UIView {
RetainPtr<NSArray<NSArray<UIColor *> *>> _colorMatrix;
RetainPtr<NSArray<NSArray<WKColorButton *> *>> _colorButtons;
}
@property (nonatomic, weak) id<WKColorMatrixViewDelegate> delegate;
- (instancetype)initWithFrame:(CGRect)frame colorMatrix:(NSArray<NSArray<UIColor *> *> *)matrix;
@end
@implementation WKColorMatrixView
- (instancetype)initWithFrame:(CGRect)frame
{
return [self initWithFrame:frame colorMatrix:[getPKColorMatrixViewClass() defaultColorMatrix]];
}
- (instancetype)initWithFrame:(CGRect)frame colorMatrix:(NSArray<NSArray<UIColor *> *> *)matrix
{
if (!(self = [super initWithFrame:frame]))
return nil;
_colorMatrix = matrix;
NSMutableArray *colorButtons = [NSMutableArray array];
for (NSUInteger row = 0; row < [_colorMatrix count]; row++) {
NSMutableArray *buttons = [NSMutableArray array];
for (NSUInteger col = 0; col < [_colorMatrix.get()[0] count]; col++) {
WKColorButton *button = [WKColorButton colorButtonWithColor:_colorMatrix.get()[row][col]];
[button addTarget:self action:@selector(colorButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
[buttons addObject:button];
[self addSubview:button];
}
[colorButtons addObject:buttons];
}
_colorButtons = colorButtons;
return self;
}
- (void)layoutSubviews
{
[super layoutSubviews];
CGSize matrixSize = self.bounds.size;
CGFloat numRows = [_colorMatrix count];
CGFloat numCols = [_colorMatrix.get()[0] count];
CGFloat buttonHeight = matrixSize.height / numRows;
CGFloat buttonWidth = matrixSize.width / numCols;
for (NSUInteger row = 0; row < numRows; row++) {
for (NSUInteger col = 0; col < numCols; col++) {
WKColorButton *button = _colorButtons.get()[row][col];
button.frame = CGRectMake(col * buttonWidth, row * buttonHeight, buttonWidth, buttonHeight);
}
}
[self.delegate colorMatrixViewDidLayoutSubviews:self];
}
- (void)colorButtonTapped:(WKColorButton *)colorButton
{
[self.delegate colorMatrixView:self didTapColorButton:colorButton];
}
@end
#pragma mark - WKFormColorPicker
@implementation WKColorPicker {
WKContentView *_view;
__weak WKColorPopover *_popover;
RetainPtr<UIView> _colorPicker;
RetainPtr<UIView> _colorSelectionIndicator;
RetainPtr<CAShapeLayer> _colorSelectionIndicatorBorder;
RetainPtr<WKColorMatrixView> _topColorMatrix;
RetainPtr<WKColorMatrixView> _mainColorMatrix;
WeakObjCPtr<WKColorButton> _selectedColorButton;
RetainPtr<UIPanGestureRecognizer> _colorPanGR;
}
+ (NSArray<NSArray<UIColor *> *> *)defaultTopColorMatrix
{
return @[ @[ UIColor.redColor, UIColor.orangeColor, UIColor.yellowColor, UIColor.greenColor, UIColor.cyanColor, UIColor.blueColor, UIColor.magentaColor, UIColor.purpleColor, UIColor.brownColor, UIColor.whiteColor, UIColor.grayColor, UIColor.blackColor ] ];
}
- (instancetype)initWithView:(WKContentView *)view
{
return [self initWithView:view inPopover:nil];
}
- (instancetype)initWithView:(WKContentView *)view inPopover:(WKColorPopover *)popover
{
if (!(self = [super init]))
return nil;
_view = view;
_popover = popover;
CGSize colorPickerSize;
if (currentUserInterfaceIdiomIsPad())
colorPickerSize = CGSizeMake(pickerWidthForPopover, pickerWidthForPopover);
else {
auto keyboardSize = [UIKeyboard defaultSizeForInterfaceOrientation:view.interfaceOrientation];
colorPickerSize = CGSizeMake(keyboardSize.width, keyboardSize.height + additionalKeyboardAffordance);
}
_colorPicker = adoptNS([[UIView alloc] initWithFrame:CGRectMake(0, 0, colorPickerSize.width, colorPickerSize.height)]);
CGFloat totalRows = [[getPKColorMatrixViewClass() defaultColorMatrix] count] + 1;
CGFloat swatchHeight = (colorPickerSize.height - topColorMatrixPadding) / totalRows;
_mainColorMatrix = adoptNS([[WKColorMatrixView alloc] initWithFrame:CGRectMake(0, swatchHeight + topColorMatrixPadding, colorPickerSize.width, swatchHeight * (totalRows - 1))]);
[_mainColorMatrix setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
[_mainColorMatrix setDelegate:self];
[_colorPicker addSubview:_mainColorMatrix.get()];
NSArray<NSArray<UIColor *> *> *topColorMatrix = [[self class] defaultTopColorMatrix];
#if ENABLE(DATALIST_ELEMENT)
size_t numColorSuggestions = view.focusedElementInformation.suggestedColors.size();
if (numColorSuggestions) {
NSMutableArray<UIColor *> *colors = [NSMutableArray array];
for (size_t i = 0; i < std::min(numColorSuggestions, maxColorSuggestions); i++) {
WebCore::Color color = view.focusedElementInformation.suggestedColors[i];
[colors addObject:[UIColor colorWithCGColor:cachedCGColor(color)]];
}
topColorMatrix = @[ colors ];
}
#endif
_topColorMatrix = adoptNS([[WKColorMatrixView alloc] initWithFrame:CGRectMake(0, 0, colorPickerSize.width, swatchHeight) colorMatrix:topColorMatrix]);
[_topColorMatrix setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
[_topColorMatrix setDelegate:self];
[_colorPicker addSubview:_topColorMatrix.get()];
_colorSelectionIndicator = adoptNS([[UIView alloc] initWithFrame:CGRectZero]);
[_colorSelectionIndicator setClipsToBounds:YES];
[_colorPicker addSubview:_colorSelectionIndicator.get()];
_colorSelectionIndicatorBorder = adoptNS([[CAShapeLayer alloc] init]);
[_colorSelectionIndicatorBorder setLineWidth:colorSelectionIndicatorBorderWidth];
[_colorSelectionIndicatorBorder setStrokeColor:[UIColor whiteColor].CGColor];
[_colorSelectionIndicatorBorder setFillColor:[UIColor clearColor].CGColor];
[[_colorSelectionIndicator layer] addSublayer:_colorSelectionIndicatorBorder.get()];
_colorPanGR = adoptNS([[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(didPanColors:)]);
[_colorPicker addGestureRecognizer:_colorPanGR.get()];
return self;
}
- (void)drawSelectionIndicatorForColorButton:(WKColorButton *)colorButton
{
_selectedColorButton = colorButton;
CGRect frame = [_colorPicker convertRect:colorButton.bounds fromView:colorButton];
[_colorSelectionIndicator setFrame:frame];
UIRectCorner roundCorner = 0;
if (currentUserInterfaceIdiomIsPad()) {
CGRect colorPickerBounds = [_colorPicker bounds];
bool minXEqual = std::abs(CGRectGetMinX(frame) - CGRectGetMinX(colorPickerBounds)) < FLT_EPSILON;
bool minYEqual = std::abs(CGRectGetMinY(frame) - CGRectGetMinY(colorPickerBounds)) < FLT_EPSILON;
bool maxXEqual = std::abs(CGRectGetMaxX(frame) - CGRectGetMaxX(colorPickerBounds)) < FLT_EPSILON;
bool maxYEqual = std::abs(CGRectGetMaxY(frame) - CGRectGetMaxY(colorPickerBounds)) < FLT_EPSILON;
// On iPad, round the corners of the indicator that border the corners of the picker, to match the popover.
if (minXEqual && minYEqual)
roundCorner |= UIRectCornerTopLeft;
if (maxXEqual && minYEqual)
roundCorner |= UIRectCornerTopRight;
if (minXEqual && maxYEqual)
roundCorner |= UIRectCornerBottomLeft;
if (maxXEqual && maxYEqual)
roundCorner |= UIRectCornerBottomRight;
}
UIBezierPath *cornerMaskPath = [UIBezierPath bezierPathWithRoundedRect:colorButton.bounds byRoundingCorners:roundCorner cornerRadii:CGSizeMake(colorSelectionIndicatorCornerRadius, colorSelectionIndicatorCornerRadius)];
[_colorSelectionIndicatorBorder setFrame:colorButton.bounds];
[_colorSelectionIndicatorBorder setPath:cornerMaskPath.CGPath];
}
- (void)setControlValueFromUIColor:(UIColor *)uiColor
{
WebCore::Color color(uiColor.CGColor);
[_view page]->setFocusedElementValue(WebCore::serializationForHTML(color));
}
#pragma mark WKFormControl
- (UIView *)controlView
{
return _colorPicker.get();
}
- (void)controlBeginEditing
{
}
- (void)controlEndEditing
{
}
#pragma mark WKColorMatrixViewDelegate
- (void)colorMatrixViewDidLayoutSubviews:(WKColorMatrixView *)matrixView
{
if ([_selectedColorButton superview] == matrixView)
[self drawSelectionIndicatorForColorButton:_selectedColorButton.get().get()];
}
- (void)colorMatrixView:(WKColorMatrixView *)matrixView didTapColorButton:(WKColorButton *)colorButton
{
if (_selectedColorButton.get().get() == colorButton)
return;
[self drawSelectionIndicatorForColorButton:colorButton];
[self setControlValueFromUIColor:colorButton.color];
#if PLATFORM(MACCATALYST)
[_popover dismissPopoverAnimated:NO];
[_view accessoryDone];
#endif
}
#pragma mark UIPanGestureRecognizer
- (void)didPanColors:(UIGestureRecognizer *)gestureRecognizer
{
CGPoint location = [gestureRecognizer locationInView:gestureRecognizer.view];
UIView *view = [gestureRecognizer.view hitTest:location withEvent:nil];
if ([view isKindOfClass:[WKColorButton class]]) {
WKColorButton *colorButton = (WKColorButton *)view;
if (_selectedColorButton.get().get() == colorButton)
return;
[self drawSelectionIndicatorForColorButton:colorButton];
[self setControlValueFromUIColor:colorButton.color];
}
}
@end
#endif // ENABLE(INPUT_TYPE_COLOR) && PLATFORM(IOS_FAMILY)