[go: nahoru, domu]

blob: d992f51a60b6106b50c3e855a23da080217861d5 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
#import <Accelerate/Accelerate.h>
#import <Foundation/Foundation.h>
#import <QuartzCore/QuartzCore.h>
#import <UIKit/UIKit.h>
#import <stddef.h>
#import <stdint.h>
#import <cmath>
#import "base/apple/foundation_util.h"
#import "base/check.h"
#import "base/check_op.h"
#import "base/ios/ios_util.h"
#import "base/notreached.h"
#import "base/numerics/math_constants.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/public/features/system_flags.h"
#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
#import "ios/chrome/browser/shared/ui/util/dynamic_type_util.h"
#import "ios/chrome/browser/shared/ui/util/rtl_geometry.h"
#import "ios/chrome/common/ui/util/ui_util.h"
#import "ui/base/l10n/l10n_util.h"
#import "ui/base/l10n/l10n_util_mac.h"
#import "ui/base/resource/resource_bundle.h"
#import "ui/gfx/ios/uikit_util.h"
#import "ui/gfx/scoped_cg_context_save_gstate_mac.h"
void SetA11yLabelAndUiAutomationName(
NSObject<UIAccessibilityIdentification>* element,
int idsAccessibilityLabel,
NSString* englishUiAutomationName) {
[element setAccessibilityLabel:l10n_util::GetNSString(idsAccessibilityLabel)];
[element setAccessibilityIdentifier:englishUiAutomationName];
}
void SetUILabelScaledFont(UILabel* label, UIFont* font) {
label.font = [[UIFontMetrics defaultMetrics] scaledFontForFont:font];
label.adjustsFontForContentSizeCategory = YES;
}
void MaybeSetUILabelScaledFont(BOOL maybe, UILabel* label, UIFont* font) {
if (maybe) {
SetUILabelScaledFont(label, font);
} else {
label.font = font;
}
}
void SetUITextFieldScaledFont(UITextField* textField, UIFont* font) {
textField.font = [[UIFontMetrics defaultMetrics] scaledFontForFont:font];
textField.adjustsFontForContentSizeCategory = YES;
}
void MaybeSetUITextFieldScaledFont(BOOL maybe,
UITextField* textField,
UIFont* font) {
if (maybe) {
SetUITextFieldScaledFont(textField, font);
} else {
textField.font = font;
}
}
UIFont* CreateDynamicFont(UIFontTextStyle style, UIFontWeight weight) {
UIFontDescriptor* fontDescriptor =
[UIFontDescriptor preferredFontDescriptorWithTextStyle:style];
return [UIFont systemFontOfSize:fontDescriptor.pointSize weight:weight];
}
UIImage* CaptureViewWithOption(UIView* view,
CGFloat scale,
CaptureViewOption option) {
UIGraphicsImageRendererFormat* format =
[UIGraphicsImageRendererFormat preferredFormat];
format.scale = scale;
format.opaque = NO;
UIGraphicsImageRenderer* renderer =
[[UIGraphicsImageRenderer alloc] initWithSize:view.bounds.size
format:format];
return [renderer imageWithActions:^(UIGraphicsImageRendererContext* context) {
[view drawViewHierarchyInRect:view.bounds
afterScreenUpdates:option == kClientSideRendering];
}];
}
UIImage* CaptureView(UIView* view, CGFloat scale) {
return CaptureViewWithOption(view, scale, kNoCaptureOption);
}
UIImage* GreyImage(UIImage* image) {
DCHECK(image);
UIGraphicsImageRendererFormat* format =
[UIGraphicsImageRendererFormat preferredFormat];
// Grey images are always non-retina to improve memory performance.
format.scale = 1;
format.opaque = YES;
CGRect greyImageRect = CGRectMake(0, 0, image.size.width, image.size.height);
UIGraphicsImageRenderer* renderer =
[[UIGraphicsImageRenderer alloc] initWithSize:greyImageRect.size
format:format];
return [renderer imageWithActions:^(UIGraphicsImageRendererContext* context) {
UIBezierPath* background = [UIBezierPath bezierPathWithRect:greyImageRect];
[UIColor.blackColor set];
[background fill];
[image drawInRect:greyImageRect blendMode:kCGBlendModeLuminosity alpha:1.0];
}];
}
UIImage* NativeReversibleImage(int imageID, BOOL reversible) {
DCHECK(imageID);
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
UIImage* image = rb.GetNativeImageNamed(imageID).ToUIImage();
return (reversible && UseRTLLayout())
? [image imageFlippedForRightToLeftLayoutDirection]
: image;
}
UIImage* NativeImage(int imageID) {
return NativeReversibleImage(imageID, NO);
}
UIInterfaceOrientation GetInterfaceOrientation(UIWindow* window) {
return window.windowScene.interfaceOrientation;
}
UIActivityIndicatorView* GetMediumUIActivityIndicatorView() {
return [[UIActivityIndicatorView alloc]
initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium];
}
UIActivityIndicatorView* GetLargeUIActivityIndicatorView() {
return [[UIActivityIndicatorView alloc]
initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleLarge];
}
CGFloat CurrentKeyboardHeight(NSValue* keyboardFrameValue) {
return [keyboardFrameValue CGRectValue].size.height;
}
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);
UIGraphicsImageRendererFormat* format =
[UIGraphicsImageRendererFormat preferredFormat];
format.opaque = NO;
UIGraphicsImageRenderer* renderer =
[[UIGraphicsImageRenderer alloc] initWithSize:frame.size format:format];
return
[renderer imageWithActions:^(UIGraphicsImageRendererContext* UIContext) {
CGContextRef context = UIContext.CGContext;
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)];
}];
}
bool IsPortrait(UIWindow* window) {
UIInterfaceOrientation orient = GetInterfaceOrientation(window);
return UIInterfaceOrientationIsPortrait(orient) ||
orient == UIInterfaceOrientationUnknown;
}
bool IsLandscape(UIWindow* window) {
return UIInterfaceOrientationIsLandscape(GetInterfaceOrientation(window));
}
bool IsCompactWidth(id<UITraitEnvironment> environment) {
return IsCompactWidth(environment.traitCollection);
}
bool IsCompactWidth(UITraitCollection* traitCollection) {
return traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact;
}
bool IsCompactHeight(id<UITraitEnvironment> environment) {
return IsCompactHeight(environment.traitCollection);
}
bool IsCompactHeight(UITraitCollection* traitCollection) {
return traitCollection.verticalSizeClass == UIUserInterfaceSizeClassCompact;
}
bool IsRegularXRegularSizeClass(id<UITraitEnvironment> environment) {
return IsRegularXRegularSizeClass(environment.traitCollection);
}
bool IsRegularXRegularSizeClass(UITraitCollection* traitCollection) {
return traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular &&
traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular;
}
bool ShouldShowCompactToolbar(id<UITraitEnvironment> environment) {
return ShouldShowCompactToolbar(environment.traitCollection);
}
bool ShouldShowCompactToolbar(UITraitCollection* traitCollection) {
return !IsRegularXRegularSizeClass(traitCollection);
}
bool IsSplitToolbarMode(id<UITraitEnvironment> environment) {
return IsSplitToolbarMode(environment.traitCollection);
}
bool IsSplitToolbarMode(UITraitCollection* traitCollection) {
return IsCompactWidth(traitCollection) && !IsCompactHeight(traitCollection);
}
UIView* GetFirstResponderSubview(UIView* view) {
if ([view isFirstResponder]) {
return view;
}
for (UIView* subview in [view subviews]) {
UIView* firstResponder = GetFirstResponderSubview(subview);
if (firstResponder) {
return firstResponder;
}
}
return nil;
}
UIResponder* GetFirstResponder() {
DCHECK(NSThread.isMainThread);
return GetFirstResponderSubview(GetAnyKeyWindow());
}
UIResponder* GetFirstResponderInWindowScene(UIWindowScene* windowScene) {
DCHECK(NSThread.isMainThread);
if (!windowScene) {
return nil;
}
// First checking the key window for this window scene.
UIResponder* responder = GetFirstResponderSubview(windowScene.keyWindow);
if (responder) {
return responder;
}
for (UIWindow* window in windowScene.windows) {
if (window.isKeyWindow) {
continue;
}
responder = GetFirstResponderSubview(window);
if (responder) {
return responder;
}
}
return nil;
}
// Trigger a haptic vibration for the user selecting an action. This is a no-op
// for devices that do not support it.
void TriggerHapticFeedbackForImpact(UIImpactFeedbackStyle impactStyle) {
// Although Apple documentation claims that UIFeedbackGenerator and its
// concrete subclasses are available on iOS 10+, they are not really
// available on an app whose deployment target is iOS 10.0 (iOS 10.1+ are
// okay) and Chrome will fail at dynamic link time and instantly crash.
// NSClassFromString() checks if Objective-C run-time has the class before
// using it.
Class generatorClass = NSClassFromString(@"UIImpactFeedbackGenerator");
if (generatorClass) {
UIImpactFeedbackGenerator* generator =
[[generatorClass alloc] initWithStyle:impactStyle];
[generator impactOccurred];
}
}
// Trigger a haptic vibration for the change in selection. This is a no-op for
// devices that do not support it.
void TriggerHapticFeedbackForSelectionChange() {
// Although Apple documentation claims that UIFeedbackGenerator and its
// concrete subclasses are available on iOS 10+, they are not really
// available on an app whose deployment target is iOS 10.0 (iOS 10.1+ are
// okay) and Chrome will fail at dynamic link time and instantly crash.
// NSClassFromString() checks if Objective-C run-time has the class before
// using it.
Class generatorClass = NSClassFromString(@"UISelectionFeedbackGenerator");
if (generatorClass) {
UISelectionFeedbackGenerator* generator = [[generatorClass alloc] init];
[generator selectionChanged];
}
}
// Trigger a haptic vibration for a notification. This is a no-op for devices
// that do not support it.
void TriggerHapticFeedbackForNotification(UINotificationFeedbackType type) {
// Although Apple documentation claims that UIFeedbackGenerator and its
// concrete subclasses are available on iOS 10+, they are not really
// available on an app whose deployment target is iOS 10.0 (iOS 10.1+ are
// okay) and Chrome will fail at dynamic link time and instantly crash.
// NSClassFromString() checks if Objective-C run-time has the class before
// using it.
Class generatorClass = NSClassFromString(@"UINotificationFeedbackGenerator");
if (generatorClass) {
UINotificationFeedbackGenerator* generator = [[generatorClass alloc] init];
[generator notificationOccurred:type];
}
}
NSAttributedString* TextForTabCount(int count, CGFloat font_size) {
NSString* string;
if (count <= 0) {
string = @"";
} else if (count > 99) {
string = @":)";
} else {
string = [NSString stringWithFormat:@"%d", count];
}
UIFontWeight weight =
UIAccessibilityIsBoldTextEnabled() ? UIFontWeightHeavy : UIFontWeightBold;
UIFont* font = [UIFont systemFontOfSize:font_size weight:weight];
UIFontDescriptor* descriptor = [font.fontDescriptor
fontDescriptorWithDesign:UIFontDescriptorSystemDesignRounded];
font = [UIFont fontWithDescriptor:descriptor size:font_size];
return [[NSAttributedString alloc] initWithString:string
attributes:@{
NSFontAttributeName : font,
NSKernAttributeName : @(-0.8),
}];
}
void RegisterEditMenuItem(UIMenuItem* item) {
UIMenuController* menu = [UIMenuController sharedMenuController];
NSArray<UIMenuItem*>* items = [menu menuItems];
for (UIMenuItem* existingItem in items) {
if ([existingItem action] == [item action]) {
return;
}
}
items = items ? [items arrayByAddingObject:item] : @[ item ];
[menu setMenuItems:items];
}
UIView* ViewHierarchyRootForView(UIView* view) {
if (view.window) {
return view.window;
}
if (!view.superview) {
return view;
}
return ViewHierarchyRootForView(view.superview);
}