Chromium Code Reviews| Index: ios/chrome/browser/ui/uikit_ui_util.mm |
| diff --git a/ios/chrome/browser/ui/uikit_ui_util.mm b/ios/chrome/browser/ui/uikit_ui_util.mm |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..fa96ffde79bde7f15a43e62967ca3e48986567da |
| --- /dev/null |
| +++ b/ios/chrome/browser/ui/uikit_ui_util.mm |
| @@ -0,0 +1,460 @@ |
| +// Copyright 2012 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#import "ios/chrome/browser/ui/uikit_ui_util.h" |
| + |
| +#import <Accelerate/Accelerate.h> |
| +#import <Foundation/Foundation.h> |
| +#import <QuartzCore/QuartzCore.h> |
| +#import <UIKit/UIKit.h> |
| +#include <cmath> |
| + |
| +#include "base/logging.h" |
| +#include "base/ios/ios_util.h" |
| +#include "base/mac/foundation_util.h" |
| +#include "ios/chrome/browser/ui/ui_util.h" |
| +#include "ios/chrome/browser/ui/ui_util.h" |
| +#include "ui/base/l10n/l10n_util.h" |
| +#include "ui/base/l10n/l10n_util_mac.h" |
| +#include "ui/gfx/scoped_cg_context_save_gstate_mac.h" |
| +#include "ui/ios/uikit_util.h" |
| + |
| +namespace { |
| + |
| +// Linearly interpolate between |a| and |b| by fraction |f|. Satisfies |
| +// |lerp(a,b,0) == a| and |lerp(a,b,1) == b|. |
|
sdefresne
2015/01/13 15:19:25
nit: s/lerp/Lerp/g
|
| +CGFloat Lerp(CGFloat a, CGFloat b, CGFloat fraction) { |
| + return a * (1.0f - fraction) + b * fraction; |
| +} |
| + |
| +// Gets the RGBA components from a UIColor in RBG or monochrome color space. |
| +void GetRGBA(UIColor* color, CGFloat* r, CGFloat* g, CGFloat* b, CGFloat* a) { |
| + switch (CGColorSpaceGetModel(CGColorGetColorSpace(color.CGColor))) { |
| + case kCGColorSpaceModelRGB: { |
| + BOOL success = [color getRed:r green:g blue:b alpha:a]; |
| + DCHECK(success); |
| + return; |
| + } |
| + case kCGColorSpaceModelMonochrome: { |
| + const size_t componentsCount = |
| + CGColorGetNumberOfComponents(color.CGColor); |
| + DCHECK(componentsCount == 1 || componentsCount == 2); |
| + const CGFloat* components = CGColorGetComponents(color.CGColor); |
| + *r = components[0]; |
| + *g = components[0]; |
| + *b = components[0]; |
| + *a = componentsCount == 1 ? 1 : components[1]; |
| + return; |
| + } |
| + default: |
| + NOTREACHED() << "Unsupported color space."; |
| + return; |
| + } |
| +} |
| + |
| +} // namespace |
| + |
| +void SetA11yLabelAndUiAutomationName(UIView* element, |
| + int idsAccessibilityLabel, |
| + NSString* englishUiAutomationName) { |
| + [element setAccessibilityLabel:l10n_util::GetNSString(idsAccessibilityLabel)]; |
| + [element setAccessibilityIdentifier:englishUiAutomationName]; |
| +} |
| + |
| +void GetSizeButtonWidthToFit(UIButton* button) { |
| + // Resize the button's width to fit the new text, but keep the original |
| + // height. sizeToFit appears to ignore the image size, so re-add the size of |
| + // the button's image to the frame width. |
| + CGFloat buttonHeight = [button frame].size.height; |
| + CGFloat imageWidth = [[button imageView] frame].size.width; |
| + [button sizeToFit]; |
| + CGRect newFrame = [button frame]; |
| + newFrame.size.height = buttonHeight; |
| + newFrame.size.width += imageWidth; |
| + [button setFrame:newFrame]; |
| +} |
| + |
| +void TranslateFrame(UIView* view, UIOffset offset) { |
| + if (!view) |
| + return; |
| + |
| + CGRect frame = [view frame]; |
| + frame.origin.x = frame.origin.x + offset.horizontal; |
| + frame.origin.y = frame.origin.y + offset.vertical; |
| + [view setFrame:frame]; |
| +} |
| + |
| +UIFont* GetUIFont(int fontFace, bool isBold, CGFloat fontSize) { |
| + NSString* fontFaceName; |
| + switch (fontFace) { |
| + case FONT_HELVETICA: |
| + fontFaceName = isBold ? @"Helvetica-Bold" : @"Helvetica"; |
| + break; |
| + case FONT_HELVETICA_NEUE: |
| + fontFaceName = isBold ? @"HelveticaNeue-Bold" : @"HelveticaNeue"; |
| + break; |
| + case FONT_HELVETICA_NEUE_LIGHT: |
| + // FONT_HELVETICA_NEUE_LIGHT does not support Bold. |
| + DCHECK(!isBold); |
| + fontFaceName = @"HelveticaNeue-Light"; |
| + break; |
| + default: |
| + NOTREACHED(); |
| + fontFaceName = @"Helvetica"; |
| + break; |
| + } |
| + return [UIFont fontWithName:fontFaceName size:fontSize]; |
| +} |
| + |
| +void AddBorderShadow(UIView* view, CGFloat offset, UIColor* color) { |
| + CGRect rect = CGRectInset(view.bounds, -offset, -offset); |
| + CGPoint waypoints[] = { |
| + CGPointMake(rect.origin.x, rect.origin.y), |
| + CGPointMake(rect.origin.x, rect.origin.y + rect.size.height), |
| + CGPointMake(rect.origin.x + rect.size.width, |
| + rect.origin.y + rect.size.height), |
| + CGPointMake(rect.origin.x + rect.size.width, rect.origin.y), |
| + CGPointMake(rect.origin.x, rect.origin.y)}; |
| + int numberOfWaypoints = sizeof(waypoints) / sizeof(waypoints[0]); |
| + CGMutablePathRef outline = CGPathCreateMutable(); |
| + CGPathAddLines(outline, NULL, waypoints, numberOfWaypoints); |
| + view.layer.shadowColor = [color CGColor]; |
| + view.layer.shadowOpacity = 1.0; |
| + view.layer.shadowOffset = CGSizeZero; |
| + view.layer.shadowPath = outline; |
| + CGPathRelease(outline); |
| +} |
| + |
| +// TODO(pkl): The implementation of this has some duplicated code with |
| +// AddBorderShadow and ToolsPopupView newPathForRect:withRadius:withArrow:. |
| +// There is an opportunity to refactor them into a common shadow library. |
| +void AddRoundedBorderShadow(UIView* view, CGFloat radius, UIColor* color) { |
| + CGRect rect = view.bounds; |
| + CGMutablePathRef path = CGPathCreateMutable(); |
| + CGFloat minX = CGRectGetMinX(rect); |
| + CGFloat midX = CGRectGetMidX(rect); |
| + CGFloat maxX = CGRectGetMaxX(rect); |
| + CGFloat minY = CGRectGetMinY(rect); |
| + CGFloat midY = CGRectGetMidY(rect); |
| + CGFloat maxY = CGRectGetMaxY(rect); |
| + CGPathMoveToPoint(path, NULL, minX, midY); |
| + CGPathAddArcToPoint(path, NULL, minX, minY, midX, minY, radius); |
| + CGPathAddArcToPoint(path, NULL, maxX, minY, maxX, midY, radius); |
| + CGPathAddArcToPoint(path, NULL, maxX, maxY, midX, maxY, radius); |
| + CGPathAddArcToPoint(path, NULL, minX, maxY, minX, midY, radius); |
| + CGPathCloseSubpath(path); |
| + view.layer.shadowColor = [color CGColor]; |
| + view.layer.shadowOpacity = 1.0; |
| + view.layer.shadowRadius = radius; |
| + view.layer.shadowOffset = CGSizeZero; |
| + view.layer.shadowPath = path; |
| + view.layer.borderWidth = 0.0; |
| + CGPathRelease(path); |
| +} |
| + |
| +UIImage* CaptureView(UIView* view, CGFloat scale) { |
| + UIGraphicsBeginImageContextWithOptions(view.bounds.size, YES /* opaque */, |
| + scale); |
| + CGContext* context = UIGraphicsGetCurrentContext(); |
| + [view.layer renderInContext:context]; |
| + UIImage* image = UIGraphicsGetImageFromCurrentImageContext(); |
| + UIGraphicsEndImageContext(); |
| + return image; |
| +} |
| + |
| +UIImage* GreyImage(UIImage* image) { |
| + DCHECK(image); |
| + // Grey images are always non-retina to improve memory performance. |
| + UIGraphicsBeginImageContextWithOptions(image.size, YES, 1.0); |
| + CGRect greyImageRect = CGRectMake(0, 0, image.size.width, image.size.height); |
| + [image drawInRect:greyImageRect blendMode:kCGBlendModeLuminosity alpha:1.0]; |
| + UIImage* greyImage = UIGraphicsGetImageFromCurrentImageContext(); |
| + UIGraphicsEndImageContext(); |
| + return greyImage; |
| +} |
| + |
| +UIColor* GetPrimaryActionButtonColor() { |
| + return UIColorFromRGB(0x2d6ada, 1.0); |
| +} |
| + |
| +UIColor* GetSettingsBackgroundColor() { |
| + CGFloat rgb = 237.0 / 255.0; |
| + return [UIColor colorWithWhite:rgb alpha:1]; |
| +} |
| + |
| +UIImage* ResizeImage(UIImage* image, |
| + CGSize targetSize, |
| + BOOL preserveAspectRatio, |
| + BOOL trimToFit) { |
| + CGSize revisedTargetSize; |
| + CGRect projectTo; |
| + |
| + CalculateProjection([image size], targetSize, preserveAspectRatio, trimToFit, |
| + revisedTargetSize, projectTo); |
| + |
| + if (CGRectEqualToRect(projectTo, CGRectZero)) |
| + return nil; |
| + |
| + // Resize photo. Use UIImage drawing methods because they respect |
| + // UIImageOrientation as opposed to CGContextDrawImage(). |
| + UIGraphicsBeginImageContextWithOptions(revisedTargetSize, NO, image.scale); |
| + [image drawInRect:projectTo]; |
| + UIImage* resizedPhoto = UIGraphicsGetImageFromCurrentImageContext(); |
| + UIGraphicsEndImageContext(); |
| + return resizedPhoto; |
| +} |
| + |
| +UIImage* DarkenImage(UIImage* image) { |
| + UIColor* tintColor = [UIColor colorWithWhite:0.22 alpha:0.6]; |
| + return BlurImage(image, |
| + 3.0, // blurRadius, |
| + tintColor, |
| + 1.8, // saturationDeltaFactor |
| + nil); |
| +} |
| + |
| +UIImage* BlurImage(UIImage* image, |
| + CGFloat blurRadius, |
| + UIColor* tintColor, |
| + CGFloat saturationDeltaFactor, |
| + UIImage* maskImage) { |
| + // This code is heavily inspired by the UIImageEffect sample project, |
| + // presented at WWDC and available from Apple. |
| + DCHECK(image.size.width >= 1 && image.size.height >= 1); |
| + DCHECK(image.CGImage); |
| + DCHECK(!maskImage || maskImage.CGImage); |
| + |
| + CGRect imageRect = {CGPointZero, image.size}; |
| + UIImage* effectImage = nil; |
| + |
| + BOOL hasBlur = blurRadius > __FLT_EPSILON__; |
| + BOOL hasSaturationChange = fabs(saturationDeltaFactor - 1.) > __FLT_EPSILON__; |
| + if (hasBlur || hasSaturationChange) { |
| + UIGraphicsBeginImageContextWithOptions(image.size, |
| + NO, // opaque. |
| + [[UIScreen mainScreen] scale]); |
| + CGContextRef effectInContext = UIGraphicsGetCurrentContext(); |
| + CGContextScaleCTM(effectInContext, 1.0, -1.0); |
| + CGContextTranslateCTM(effectInContext, 0, -image.size.height); |
| + CGContextDrawImage(effectInContext, imageRect, image.CGImage); |
| + |
| + vImage_Buffer effectInBuffer; |
| + effectInBuffer.data = CGBitmapContextGetData(effectInContext); |
| + effectInBuffer.width = CGBitmapContextGetWidth(effectInContext); |
| + effectInBuffer.height = CGBitmapContextGetHeight(effectInContext); |
| + effectInBuffer.rowBytes = CGBitmapContextGetBytesPerRow(effectInContext); |
| + |
| + UIGraphicsBeginImageContextWithOptions(image.size, |
| + NO, // opaque. |
| + [[UIScreen mainScreen] scale]); |
| + CGContextRef effectOutContext = UIGraphicsGetCurrentContext(); |
| + vImage_Buffer effectOutBuffer; |
| + effectOutBuffer.data = CGBitmapContextGetData(effectOutContext); |
| + effectOutBuffer.width = CGBitmapContextGetWidth(effectOutContext); |
| + effectOutBuffer.height = CGBitmapContextGetHeight(effectOutContext); |
| + effectOutBuffer.rowBytes = CGBitmapContextGetBytesPerRow(effectOutContext); |
| + |
| + // Those are swapped as effects are applied. |
| + vImage_Buffer* inBuffer = &effectInBuffer; |
| + vImage_Buffer* outBuffer = &effectOutBuffer; |
| + |
| + if (hasBlur) { |
| + // A description of how to compute the box kernel width from the Gaussian |
| + // radius (aka standard deviation) appears in the SVG spec: |
| + // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement |
| + // |
| + // For larger values of 's' (s >= 2.0), an approximation can be used: |
| + // Three successive box-blurs build a piece-wise quadratic convolution |
| + // kernel, which approximates the Gaussian kernel to within roughly 3%. |
| + // |
| + // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5) |
| + // |
| + // ... if d is odd, use three box-blurs of size 'd', centered on the |
| + // output pixel. |
| + // |
| + CGFloat inputRadius = blurRadius * [[UIScreen mainScreen] scale]; |
| + NSUInteger radius = floor(inputRadius * 3. * sqrt(2 * M_PI) / 4 + 0.5); |
| + if (radius % 2 != 1) { |
| + // force radius to be odd so that the three box-blur methodology works. |
| + radius += 1; |
| + } |
| + for (int i = 0; i < 3; ++i) { |
| + vImageBoxConvolve_ARGB8888(inBuffer, // src. |
| + outBuffer, // dst. |
| + NULL, // tempBuffer. |
| + 0, // srcOffsetToROI_X. |
| + 0, // srcOffsetToROI_Y |
| + radius, // kernel_height |
| + radius, // kernel_width |
| + 0, // backgroundColor |
| + kvImageEdgeExtend); // flags |
| + vImage_Buffer* temp = inBuffer; |
| + inBuffer = outBuffer; |
| + outBuffer = temp; |
| + } |
| + } |
| + if (hasSaturationChange) { |
| + CGFloat s = saturationDeltaFactor; |
| + CGFloat floatingPointSaturationMatrix[] = { |
| + 0.0722 + 0.9278 * s, 0.0722 - 0.0722 * s, 0.0722 - 0.0722 * s, 0, |
| + 0.7152 - 0.7152 * s, 0.7152 + 0.2848 * s, 0.7152 - 0.7152 * s, 0, |
| + 0.2126 - 0.2126 * s, 0.2126 - 0.2126 * s, 0.2126 + 0.7873 * s, 0, |
| + 0, 0, 0, 1 }; |
| + const int32_t divisor = 256; |
| + NSUInteger matrixSize = sizeof(floatingPointSaturationMatrix) / |
| + sizeof(floatingPointSaturationMatrix[0]); |
| + int16_t saturationMatrix[matrixSize]; |
| + for (NSUInteger i = 0; i < matrixSize; ++i) { |
| + saturationMatrix[i] = |
| + (int16_t)roundf(floatingPointSaturationMatrix[i] * divisor); |
| + } |
| + vImageMatrixMultiply_ARGB8888(inBuffer, outBuffer, saturationMatrix, |
| + divisor, NULL, NULL, kvImageNoFlags); |
| + } |
| + if (outBuffer == &effectOutBuffer) |
| + effectImage = UIGraphicsGetImageFromCurrentImageContext(); |
| + UIGraphicsEndImageContext(); |
| + |
| + if (!effectImage) |
| + effectImage = UIGraphicsGetImageFromCurrentImageContext(); |
| + UIGraphicsEndImageContext(); |
| + } |
| + |
| + // Set up output context. |
| + UIGraphicsBeginImageContextWithOptions(image.size, |
| + NO, // opaque |
| + [[UIScreen mainScreen] scale]); |
| + CGContextRef outputContext = UIGraphicsGetCurrentContext(); |
| + CGContextScaleCTM(outputContext, 1.0, -1.0); |
| + CGContextTranslateCTM(outputContext, 0, -image.size.height); |
| + |
| + // Draw base image. |
| + CGContextDrawImage(outputContext, imageRect, image.CGImage); |
| + |
| + // Draw effect image. |
| + if (effectImage) { |
| + gfx::ScopedCGContextSaveGState context(outputContext); |
| + if (maskImage) |
| + CGContextClipToMask(outputContext, imageRect, maskImage.CGImage); |
| + CGContextDrawImage(outputContext, imageRect, effectImage.CGImage); |
| + } |
| + |
| + // Add in color tint. |
| + if (tintColor) { |
| + gfx::ScopedCGContextSaveGState context(outputContext); |
| + CGContextSetFillColorWithColor(outputContext, tintColor.CGColor); |
| + CGContextFillRect(outputContext, imageRect); |
| + } |
| + |
| + // Output image is ready. |
| + UIImage* outputImage = UIGraphicsGetImageFromCurrentImageContext(); |
| + UIGraphicsEndImageContext(); |
| + |
| + return outputImage; |
| +} |
| + |
| +UIInterfaceOrientation GetInterfaceOrientation() { |
| + return [[UIApplication sharedApplication] statusBarOrientation]; |
| +} |
| + |
| +CGFloat CurrentKeyboardHeight(NSValue* keyboardFrameValue) { |
| + CGSize keyboardSize = [keyboardFrameValue CGRectValue].size; |
| + if (base::ios::IsRunningOnIOS8OrLater()) { |
| + return keyboardSize.height; |
| + } else { |
| + return IsPortrait() ? keyboardSize.height : keyboardSize.width; |
| + } |
| +} |
| + |
| +UIImage* ImageWithColor(UIColor* color) { |
| + CGRect rect = CGRectMake(0, 0, 1, 1); |
| + UIGraphicsBeginImageContext(rect.size); |
| + CGContextRef context = UIGraphicsGetCurrentContext(); |
| + CGContextSetFillColorWithColor(context, [color CGColor]); |
| + CGContextFillRect(context, rect); |
| + UIImage* image = UIGraphicsGetImageFromCurrentImageContext(); |
| + UIGraphicsEndImageContext(); |
| + return image; |
| +} |
| + |
| +UIImage* CircularImageFromImage(UIImage* image, CGFloat width) { |
| + CGRect frame = |
| + CGRectMakeAlignedAndCenteredAt(width / 2.0, width / 2.0, width); |
| + |
| + UIGraphicsBeginImageContextWithOptions(frame.size, NO, 0.0); |
| + CGContextRef context = UIGraphicsGetCurrentContext(); |
| + |
| + CGContextBeginPath(context); |
| + CGContextAddEllipseInRect(context, frame); |
| + CGContextClosePath(context); |
| + CGContextClip(context); |
| + |
| + CGFloat scaleX = frame.size.width / image.size.width; |
| + CGFloat scaleY = frame.size.height / image.size.height; |
| + CGFloat scale = std::max(scaleX, scaleY); |
| + CGContextScaleCTM(context, scale, scale); |
| + |
| + [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)]; |
| + |
| + image = UIGraphicsGetImageFromCurrentImageContext(); |
| + UIGraphicsEndImageContext(); |
| + |
| + return image; |
| +} |
| + |
| +UIColor* InterpolateFromColorToColor(UIColor* firstColor, |
| + UIColor* secondColor, |
| + CGFloat fraction) { |
| + DCHECK_LE(0.0, fraction); |
| + DCHECK_LE(fraction, 1.0); |
| + CGFloat r1, r2, g1, g2, b1, b2, a1, a2; |
| + GetRGBA(firstColor, &r1, &g1, &b1, &a1); |
| + GetRGBA(secondColor, &r2, &g2, &b2, &a2); |
| + return [UIColor colorWithRed:Lerp(r1, r2, fraction) |
| + green:Lerp(g1, g2, fraction) |
| + blue:Lerp(b1, b2, fraction) |
| + alpha:Lerp(a1, a2, fraction)]; |
| +} |
| + |
| +void ApplyVisualConstraints(NSArray* constraints, |
| + NSDictionary* subviewsDictionary, |
| + UIView* parentView) { |
| + for (NSString* constraint in constraints) { |
| + DCHECK([constraint isKindOfClass:[NSString class]]); |
| + [parentView |
| + addConstraints:[NSLayoutConstraint |
| + constraintsWithVisualFormat:constraint |
| + options:0 |
| + metrics:nil |
| + views:subviewsDictionary]]; |
| + } |
| +} |
| + |
| +void AddSameCenterXConstraint(UIView* parentView, UIView* subview) { |
| + DCHECK_EQ(parentView, [subview superview]); |
| + [parentView addConstraint:[NSLayoutConstraint |
| + constraintWithItem:subview |
| + attribute:NSLayoutAttributeCenterX |
| + relatedBy:NSLayoutRelationEqual |
| + toItem:parentView |
| + attribute:NSLayoutAttributeCenterX |
| + multiplier:1 |
| + constant:0]]; |
| +} |
| + |
| +void AddSameCenterYConstraint(UIView* parentView, |
| + UIView* subview1, |
| + UIView* subview2) { |
| + DCHECK_EQ(parentView, [subview1 superview]); |
| + DCHECK_EQ(parentView, [subview2 superview]); |
| + DCHECK_NE(subview1, subview2); |
| + [parentView addConstraint:[NSLayoutConstraint |
| + constraintWithItem:subview1 |
| + attribute:NSLayoutAttributeCenterY |
| + relatedBy:NSLayoutRelationEqual |
| + toItem:subview2 |
| + attribute:NSLayoutAttributeCenterY |
| + multiplier:1 |
| + constant:0]]; |
| +} |