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