Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / ios / chrome / browser / ui / uikit_ui_util.mm
blobf5b315c898617af9d368cf58e42cda11083b4328
1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "ios/chrome/browser/ui/uikit_ui_util.h"
7 #import <Accelerate/Accelerate.h>
8 #import <Foundation/Foundation.h>
9 #import <QuartzCore/QuartzCore.h>
10 #import <UIKit/UIKit.h>
11 #include <cmath>
13 #include "base/ios/ios_util.h"
14 #include "base/logging.h"
15 #include "base/mac/foundation_util.h"
16 #include "ios/chrome/browser/ui/ui_util.h"
17 #include "ui/base/l10n/l10n_util.h"
18 #include "ui/base/l10n/l10n_util_mac.h"
19 #include "ui/gfx/ios/uikit_util.h"
20 #include "ui/gfx/scoped_cg_context_save_gstate_mac.h"
22 namespace {
24 // Linearly interpolate between |a| and |b| by fraction |f|. Satisfies
25 // |Lerp(a,b,0) == a| and |Lerp(a,b,1) == b|.
26 CGFloat Lerp(CGFloat a, CGFloat b, CGFloat fraction) {
27   return a * (1.0f - fraction) + b * fraction;
30 // Gets the RGBA components from a UIColor in RBG or monochrome color space.
31 void GetRGBA(UIColor* color, CGFloat* r, CGFloat* g, CGFloat* b, CGFloat* a) {
32   switch (CGColorSpaceGetModel(CGColorGetColorSpace(color.CGColor))) {
33     case kCGColorSpaceModelRGB: {
34       BOOL success = [color getRed:r green:g blue:b alpha:a];
35       DCHECK(success);
36       return;
37     }
38     case kCGColorSpaceModelMonochrome: {
39       const size_t componentsCount =
40           CGColorGetNumberOfComponents(color.CGColor);
41       DCHECK(componentsCount == 1 || componentsCount == 2);
42       const CGFloat* components = CGColorGetComponents(color.CGColor);
43       *r = components[0];
44       *g = components[0];
45       *b = components[0];
46       *a = componentsCount == 1 ? 1 : components[1];
47       return;
48     }
49     default:
50       NOTREACHED() << "Unsupported color space.";
51       return;
52   }
55 }  // namespace
57 void SetA11yLabelAndUiAutomationName(UIView* element,
58                                      int idsAccessibilityLabel,
59                                      NSString* englishUiAutomationName) {
60   [element setAccessibilityLabel:l10n_util::GetNSString(idsAccessibilityLabel)];
61   [element setAccessibilityIdentifier:englishUiAutomationName];
64 void GetSizeButtonWidthToFit(UIButton* button) {
65   // Resize the button's width to fit the new text, but keep the original
66   // height. sizeToFit appears to ignore the image size, so re-add the size of
67   // the button's image to the frame width.
68   CGFloat buttonHeight = [button frame].size.height;
69   CGFloat imageWidth = [[button imageView] frame].size.width;
70   [button sizeToFit];
71   CGRect newFrame = [button frame];
72   newFrame.size.height = buttonHeight;
73   newFrame.size.width += imageWidth;
74   [button setFrame:newFrame];
77 void TranslateFrame(UIView* view, UIOffset offset) {
78   if (!view)
79     return;
81   CGRect frame = [view frame];
82   frame.origin.x = frame.origin.x + offset.horizontal;
83   frame.origin.y = frame.origin.y + offset.vertical;
84   [view setFrame:frame];
87 UIFont* GetUIFont(int fontFace, bool isBold, CGFloat fontSize) {
88   NSString* fontFaceName;
89   switch (fontFace) {
90     case FONT_HELVETICA:
91       fontFaceName = isBold ? @"Helvetica-Bold" : @"Helvetica";
92       break;
93     case FONT_HELVETICA_NEUE:
94       fontFaceName = isBold ? @"HelveticaNeue-Bold" : @"HelveticaNeue";
95       break;
96     case FONT_HELVETICA_NEUE_LIGHT:
97       // FONT_HELVETICA_NEUE_LIGHT does not support Bold.
98       DCHECK(!isBold);
99       fontFaceName = @"HelveticaNeue-Light";
100       break;
101     default:
102       NOTREACHED();
103       fontFaceName = @"Helvetica";
104       break;
105   }
106   return [UIFont fontWithName:fontFaceName size:fontSize];
109 void AddBorderShadow(UIView* view, CGFloat offset, UIColor* color) {
110   CGRect rect = CGRectInset(view.bounds, -offset, -offset);
111   CGPoint waypoints[] = {
112       CGPointMake(rect.origin.x, rect.origin.y),
113       CGPointMake(rect.origin.x, rect.origin.y + rect.size.height),
114       CGPointMake(rect.origin.x + rect.size.width,
115                   rect.origin.y + rect.size.height),
116       CGPointMake(rect.origin.x + rect.size.width, rect.origin.y),
117       CGPointMake(rect.origin.x, rect.origin.y)};
118   int numberOfWaypoints = sizeof(waypoints) / sizeof(waypoints[0]);
119   CGMutablePathRef outline = CGPathCreateMutable();
120   CGPathAddLines(outline, nullptr, waypoints, numberOfWaypoints);
121   view.layer.shadowColor = [color CGColor];
122   view.layer.shadowOpacity = 1.0;
123   view.layer.shadowOffset = CGSizeZero;
124   view.layer.shadowPath = outline;
125   CGPathRelease(outline);
128 // TODO(pkl): The implementation of this has some duplicated code with
129 // AddBorderShadow and ToolsPopupView newPathForRect:withRadius:withArrow:.
130 // There is an opportunity to refactor them into a common shadow library.
131 void AddRoundedBorderShadow(UIView* view, CGFloat radius, UIColor* color) {
132   CGRect rect = view.bounds;
133   CGMutablePathRef path = CGPathCreateMutable();
134   CGFloat minX = CGRectGetMinX(rect);
135   CGFloat midX = CGRectGetMidX(rect);
136   CGFloat maxX = CGRectGetMaxX(rect);
137   CGFloat minY = CGRectGetMinY(rect);
138   CGFloat midY = CGRectGetMidY(rect);
139   CGFloat maxY = CGRectGetMaxY(rect);
140   CGPathMoveToPoint(path, nullptr, minX, midY);
141   CGPathAddArcToPoint(path, nullptr, minX, minY, midX, minY, radius);
142   CGPathAddArcToPoint(path, nullptr, maxX, minY, maxX, midY, radius);
143   CGPathAddArcToPoint(path, nullptr, maxX, maxY, midX, maxY, radius);
144   CGPathAddArcToPoint(path, nullptr, minX, maxY, minX, midY, radius);
145   CGPathCloseSubpath(path);
146   view.layer.shadowColor = [color CGColor];
147   view.layer.shadowOpacity = 1.0;
148   view.layer.shadowRadius = radius;
149   view.layer.shadowOffset = CGSizeZero;
150   view.layer.shadowPath = path;
151   view.layer.borderWidth = 0.0;
152   CGPathRelease(path);
155 UIImage* CaptureView(UIView* view, CGFloat scale) {
156   UIGraphicsBeginImageContextWithOptions(view.bounds.size, YES /* opaque */,
157                                          scale);
158   CGContext* context = UIGraphicsGetCurrentContext();
159   [view.layer renderInContext:context];
160   UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
161   UIGraphicsEndImageContext();
162   return image;
165 UIImage* GreyImage(UIImage* image) {
166   DCHECK(image);
167   // Grey images are always non-retina to improve memory performance.
168   UIGraphicsBeginImageContextWithOptions(image.size, YES, 1.0);
169   CGRect greyImageRect = CGRectMake(0, 0, image.size.width, image.size.height);
170   [image drawInRect:greyImageRect blendMode:kCGBlendModeLuminosity alpha:1.0];
171   UIImage* greyImage = UIGraphicsGetImageFromCurrentImageContext();
172   UIGraphicsEndImageContext();
173   return greyImage;
176 UIColor* GetPrimaryActionButtonColor() {
177   return UIColorFromRGB(0x2d6ada, 1.0);
180 UIColor* GetSettingsBackgroundColor() {
181   CGFloat rgb = 237.0 / 255.0;
182   return [UIColor colorWithWhite:rgb alpha:1];
185 UIImage* ResizeImage(UIImage* image,
186                      CGSize targetSize,
187                      ProjectionMode projectionMode) {
188   CGSize revisedTargetSize;
189   CGRect projectTo;
191   CalculateProjection([image size], targetSize, projectionMode,
192                       revisedTargetSize, projectTo);
194   if (CGRectEqualToRect(projectTo, CGRectZero))
195     return nil;
197   // Resize photo. Use UIImage drawing methods because they respect
198   // UIImageOrientation as opposed to CGContextDrawImage().
199   UIGraphicsBeginImageContextWithOptions(revisedTargetSize, NO, image.scale);
200   [image drawInRect:projectTo];
201   UIImage* resizedPhoto = UIGraphicsGetImageFromCurrentImageContext();
202   UIGraphicsEndImageContext();
203   return resizedPhoto;
206 UIImage* DarkenImage(UIImage* image) {
207   UIColor* tintColor = [UIColor colorWithWhite:0.22 alpha:0.6];
208   return BlurImage(image,
209                    3.0,  // blurRadius,
210                    tintColor,
211                    1.8,  // saturationDeltaFactor
212                    nil);
215 UIImage* BlurImage(UIImage* image,
216                    CGFloat blurRadius,
217                    UIColor* tintColor,
218                    CGFloat saturationDeltaFactor,
219                    UIImage* maskImage) {
220   // This code is heavily inspired by the UIImageEffect sample project,
221   // presented at WWDC and available from Apple.
222   DCHECK(image.size.width >= 1 && image.size.height >= 1);
223   DCHECK(image.CGImage);
224   DCHECK(!maskImage || maskImage.CGImage);
226   CGRect imageRect = {CGPointZero, image.size};
227   UIImage* effectImage = nil;
229   BOOL hasBlur = blurRadius > __FLT_EPSILON__;
230   BOOL hasSaturationChange = fabs(saturationDeltaFactor - 1.) > __FLT_EPSILON__;
231   if (hasBlur || hasSaturationChange) {
232     UIGraphicsBeginImageContextWithOptions(image.size,
233                                            NO,  // opaque.
234                                            [[UIScreen mainScreen] scale]);
235     CGContextRef effectInContext = UIGraphicsGetCurrentContext();
236     CGContextScaleCTM(effectInContext, 1.0, -1.0);
237     CGContextTranslateCTM(effectInContext, 0, -image.size.height);
238     CGContextDrawImage(effectInContext, imageRect, image.CGImage);
240     vImage_Buffer effectInBuffer;
241     effectInBuffer.data = CGBitmapContextGetData(effectInContext);
242     effectInBuffer.width = CGBitmapContextGetWidth(effectInContext);
243     effectInBuffer.height = CGBitmapContextGetHeight(effectInContext);
244     effectInBuffer.rowBytes = CGBitmapContextGetBytesPerRow(effectInContext);
246     UIGraphicsBeginImageContextWithOptions(image.size,
247                                            NO,  // opaque.
248                                            [[UIScreen mainScreen] scale]);
249     CGContextRef effectOutContext = UIGraphicsGetCurrentContext();
250     vImage_Buffer effectOutBuffer;
251     effectOutBuffer.data = CGBitmapContextGetData(effectOutContext);
252     effectOutBuffer.width = CGBitmapContextGetWidth(effectOutContext);
253     effectOutBuffer.height = CGBitmapContextGetHeight(effectOutContext);
254     effectOutBuffer.rowBytes = CGBitmapContextGetBytesPerRow(effectOutContext);
256     // Those are swapped as effects are applied.
257     vImage_Buffer* inBuffer = &effectInBuffer;
258     vImage_Buffer* outBuffer = &effectOutBuffer;
260     if (hasBlur) {
261       // A description of how to compute the box kernel width from the Gaussian
262       // radius (aka standard deviation) appears in the SVG spec:
263       // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement
264       //
265       // For larger values of 's' (s >= 2.0), an approximation can be used:
266       // Three successive box-blurs build a piece-wise quadratic convolution
267       // kernel, which approximates the Gaussian kernel to within roughly 3%.
268       //
269       // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5)
270       //
271       // ... if d is odd, use three box-blurs of size 'd', centered on the
272       // output pixel.
273       //
274       CGFloat inputRadius = blurRadius * [[UIScreen mainScreen] scale];
275       NSUInteger radius = floor(inputRadius * 3. * sqrt(2 * M_PI) / 4 + 0.5);
276       if (radius % 2 != 1) {
277         // force radius to be odd so that the three box-blur methodology works.
278         radius += 1;
279       }
280       for (int i = 0; i < 3; ++i) {
281         vImageBoxConvolve_ARGB8888(inBuffer,            // src.
282                                    outBuffer,           // dst.
283                                    nullptr,             // tempBuffer.
284                                    0,                   // srcOffsetToROI_X.
285                                    0,                   // srcOffsetToROI_Y
286                                    radius,              // kernel_height
287                                    radius,              // kernel_width
288                                    0,                   // backgroundColor
289                                    kvImageEdgeExtend);  // flags
290         vImage_Buffer* temp = inBuffer;
291         inBuffer = outBuffer;
292         outBuffer = temp;
293       }
294     }
295     if (hasSaturationChange) {
296       CGFloat s = saturationDeltaFactor;
297       CGFloat floatingPointSaturationMatrix[] = {
298           0.0722 + 0.9278 * s,  0.0722 - 0.0722 * s,  0.0722 - 0.0722 * s,  0,
299           0.7152 - 0.7152 * s,  0.7152 + 0.2848 * s,  0.7152 - 0.7152 * s,  0,
300           0.2126 - 0.2126 * s,  0.2126 - 0.2126 * s,  0.2126 + 0.7873 * s,  0,
301           0,                    0,                    0,  1 };
302       const int32_t divisor = 256;
303       NSUInteger matrixSize = sizeof(floatingPointSaturationMatrix) /
304                               sizeof(floatingPointSaturationMatrix[0]);
305       int16_t saturationMatrix[matrixSize];
306       for (NSUInteger i = 0; i < matrixSize; ++i) {
307         saturationMatrix[i] =
308             (int16_t)roundf(floatingPointSaturationMatrix[i] * divisor);
309       }
310       vImageMatrixMultiply_ARGB8888(inBuffer, outBuffer, saturationMatrix,
311                                     divisor, nullptr, nullptr, kvImageNoFlags);
312     }
313     if (outBuffer == &effectOutBuffer)
314       effectImage = UIGraphicsGetImageFromCurrentImageContext();
315     UIGraphicsEndImageContext();
317     if (!effectImage)
318       effectImage = UIGraphicsGetImageFromCurrentImageContext();
319     UIGraphicsEndImageContext();
320   }
322   // Set up output context.
323   UIGraphicsBeginImageContextWithOptions(image.size,
324                                          NO,  // opaque
325                                          [[UIScreen mainScreen] scale]);
326   CGContextRef outputContext = UIGraphicsGetCurrentContext();
327   CGContextScaleCTM(outputContext, 1.0, -1.0);
328   CGContextTranslateCTM(outputContext, 0, -image.size.height);
330   // Draw base image.
331   CGContextDrawImage(outputContext, imageRect, image.CGImage);
333   // Draw effect image.
334   if (effectImage) {
335     gfx::ScopedCGContextSaveGState context(outputContext);
336     if (maskImage)
337       CGContextClipToMask(outputContext, imageRect, maskImage.CGImage);
338     CGContextDrawImage(outputContext, imageRect, effectImage.CGImage);
339   }
341   // Add in color tint.
342   if (tintColor) {
343     gfx::ScopedCGContextSaveGState context(outputContext);
344     CGContextSetFillColorWithColor(outputContext, tintColor.CGColor);
345     CGContextFillRect(outputContext, imageRect);
346   }
348   // Output image is ready.
349   UIImage* outputImage = UIGraphicsGetImageFromCurrentImageContext();
350   UIGraphicsEndImageContext();
352   return outputImage;
355 UIImage* CropImage(UIImage* image, const CGRect& cropRect) {
356   CGImageRef cgImage = CGImageCreateWithImageInRect([image CGImage], cropRect);
357   UIImage* result = [UIImage imageWithCGImage:cgImage];
358   CGImageRelease(cgImage);
359   return result;
362 UIInterfaceOrientation GetInterfaceOrientation() {
363   return [[UIApplication sharedApplication] statusBarOrientation];
366 CGFloat CurrentKeyboardHeight(NSValue* keyboardFrameValue) {
367   CGSize keyboardSize = [keyboardFrameValue CGRectValue].size;
368   if (base::ios::IsRunningOnIOS8OrLater()) {
369     return keyboardSize.height;
370   } else {
371     return IsPortrait() ? keyboardSize.height : keyboardSize.width;
372   }
375 UIImage* ImageWithColor(UIColor* color) {
376   CGRect rect = CGRectMake(0, 0, 1, 1);
377   UIGraphicsBeginImageContext(rect.size);
378   CGContextRef context = UIGraphicsGetCurrentContext();
379   CGContextSetFillColorWithColor(context, [color CGColor]);
380   CGContextFillRect(context, rect);
381   UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
382   UIGraphicsEndImageContext();
383   return image;
386 UIImage* CircularImageFromImage(UIImage* image, CGFloat width) {
387   CGRect frame =
388       CGRectMakeAlignedAndCenteredAt(width / 2.0, width / 2.0, width);
390   UIGraphicsBeginImageContextWithOptions(frame.size, NO, 0.0);
391   CGContextRef context = UIGraphicsGetCurrentContext();
393   CGContextBeginPath(context);
394   CGContextAddEllipseInRect(context, frame);
395   CGContextClosePath(context);
396   CGContextClip(context);
398   CGFloat scaleX = frame.size.width / image.size.width;
399   CGFloat scaleY = frame.size.height / image.size.height;
400   CGFloat scale = std::max(scaleX, scaleY);
401   CGContextScaleCTM(context, scale, scale);
403   [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
405   image = UIGraphicsGetImageFromCurrentImageContext();
406   UIGraphicsEndImageContext();
408   return image;
411 UIColor* InterpolateFromColorToColor(UIColor* firstColor,
412                                      UIColor* secondColor,
413                                      CGFloat fraction) {
414   DCHECK_LE(0.0, fraction);
415   DCHECK_LE(fraction, 1.0);
416   CGFloat r1, r2, g1, g2, b1, b2, a1, a2;
417   GetRGBA(firstColor, &r1, &g1, &b1, &a1);
418   GetRGBA(secondColor, &r2, &g2, &b2, &a2);
419   return [UIColor colorWithRed:Lerp(r1, r2, fraction)
420                          green:Lerp(g1, g2, fraction)
421                           blue:Lerp(b1, b2, fraction)
422                          alpha:Lerp(a1, a2, fraction)];
425 void ApplyVisualConstraints(NSArray* constraints,
426                             NSDictionary* subviewsDictionary,
427                             UIView* parentView) {
428   ApplyVisualConstraintsWithMetricsAndOptions(constraints, subviewsDictionary,
429                                               nil, 0, parentView);
432 void ApplyVisualConstraintsWithOptions(NSArray* constraints,
433                                        NSDictionary* subviewsDictionary,
434                                        NSLayoutFormatOptions options,
435                                        UIView* parentView) {
436   ApplyVisualConstraintsWithMetricsAndOptions(constraints, subviewsDictionary,
437                                               nil, options, parentView);
440 void ApplyVisualConstraintsWithMetrics(NSArray* constraints,
441                                        NSDictionary* subviewsDictionary,
442                                        NSDictionary* metrics,
443                                        UIView* parentView) {
444   ApplyVisualConstraintsWithMetricsAndOptions(constraints, subviewsDictionary,
445                                               metrics, 0, parentView);
448 void ApplyVisualConstraintsWithMetricsAndOptions(
449     NSArray* constraints,
450     NSDictionary* subviewsDictionary,
451     NSDictionary* metrics,
452     NSLayoutFormatOptions options,
453     UIView* parentView) {
454   for (NSString* constraint in constraints) {
455     DCHECK([constraint isKindOfClass:[NSString class]]);
456     [parentView
457         addConstraints:[NSLayoutConstraint
458                            constraintsWithVisualFormat:constraint
459                                                options:options
460                                                metrics:metrics
461                                                  views:subviewsDictionary]];
462   }
465 void AddSameCenterXConstraint(UIView* parentView, UIView* subview) {
466   DCHECK_EQ(parentView, [subview superview]);
467   [parentView addConstraint:[NSLayoutConstraint
468                                 constraintWithItem:subview
469                                          attribute:NSLayoutAttributeCenterX
470                                          relatedBy:NSLayoutRelationEqual
471                                             toItem:parentView
472                                          attribute:NSLayoutAttributeCenterX
473                                         multiplier:1
474                                           constant:0]];
477 void AddSameCenterXConstraint(UIView *parentView, UIView *subview1,
478                               UIView *subview2) {
479   DCHECK_EQ(parentView, [subview1 superview]);
480   DCHECK_EQ(parentView, [subview2 superview]);
481   DCHECK_NE(subview1, subview2);
482   [parentView addConstraint:[NSLayoutConstraint
483                                 constraintWithItem:subview1
484                                          attribute:NSLayoutAttributeCenterX
485                                          relatedBy:NSLayoutRelationEqual
486                                             toItem:subview2
487                                          attribute:NSLayoutAttributeCenterX
488                                         multiplier:1
489                                           constant:0]];
492 void AddSameCenterYConstraint(UIView* parentView, UIView* subview) {
493   DCHECK_EQ(parentView, [subview superview]);
494   [parentView addConstraint:[NSLayoutConstraint
495                                 constraintWithItem:subview
496                                          attribute:NSLayoutAttributeCenterY
497                                          relatedBy:NSLayoutRelationEqual
498                                             toItem:parentView
499                                          attribute:NSLayoutAttributeCenterY
500                                         multiplier:1
501                                           constant:0]];
504 void AddSameCenterYConstraint(UIView* parentView,
505                               UIView* subview1,
506                               UIView* subview2) {
507   DCHECK_EQ(parentView, [subview1 superview]);
508   DCHECK_EQ(parentView, [subview2 superview]);
509   DCHECK_NE(subview1, subview2);
510   [parentView addConstraint:[NSLayoutConstraint
511                                 constraintWithItem:subview1
512                                          attribute:NSLayoutAttributeCenterY
513                                          relatedBy:NSLayoutRelationEqual
514                                             toItem:subview2
515                                          attribute:NSLayoutAttributeCenterY
516                                         multiplier:1
517                                           constant:0]];
520 bool IsCompact(id<UITraitEnvironment> environment) {
521   if (base::ios::IsRunningOnIOS8OrLater()) {
522     return environment.traitCollection.horizontalSizeClass ==
523            UIUserInterfaceSizeClassCompact;
524   } else {
525     // Prior to iOS 8, iPad is always regular, iPhone is always compact.
526     return !IsIPadIdiom();
527   }
530 bool IsCompact() {
531   UIWindow* keyWindow = [UIApplication sharedApplication].keyWindow;
532   return IsCompact(keyWindow);
535 bool IsCompactTablet(id<UITraitEnvironment> environment) {
536   return IsIPadIdiom() && IsCompact(environment);
539 bool IsCompactTablet() {
540   return IsIPadIdiom() && IsCompact();