1 // Copyright 2014 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/autofill/form_input_accessory_view.h"
7 #import <QuartzCore/QuartzCore.h>
9 #include "base/i18n/rtl.h"
10 #include "base/ios/weak_nsobject.h"
11 #include "base/mac/scoped_nsobject.h"
12 #import "ios/chrome/browser/autofill/form_input_accessory_view_delegate.h"
13 #import "ios/chrome/browser/ui/image_util.h"
14 #include "ios/chrome/browser/ui/ui_util.h"
15 #include "ios/chrome/grit/ios_strings.h"
16 #include "ui/base/l10n/l10n_util.h"
20 // The alpha value of the background color.
21 const CGFloat kBackgroundColorAlpha = 1.0;
23 // Horizontal margin around the custom view.
24 const CGFloat kCustomViewHorizontalMargin = 2;
26 // The width of the previous and next buttons.
27 const CGFloat kNavigationButtonWidth = 44;
29 // The width of the separators of the previous and next buttons.
30 const CGFloat kNavigationButtonSeparatorWidth = 1;
32 // The width of the shadow part of the navigation area separator.
33 const CGFloat kNavigationAreaSeparatorShadowWidth = 2;
35 // The width of the navigation area / custom view separator asset.
36 const CGFloat kNavigationAreaSeparatorWidth = 1;
38 // Returns YES if the keyboard close button should be shown on the accessory.
39 BOOL ShouldShowCloseButton() {
40 return !IsIPadIdiom();
43 // Returns the width of navigation view.
44 CGFloat GetNavigationViewWidth() {
45 // The number of naviation buttons (includes close button if shown).
46 NSUInteger numberNavigationButtons = 2;
47 if (ShouldShowCloseButton())
48 numberNavigationButtons++;
49 return numberNavigationButtons * kNavigationButtonWidth +
50 (numberNavigationButtons - 1) * kNavigationButtonSeparatorWidth +
51 kNavigationAreaSeparatorWidth;
56 @interface FormInputAccessoryView ()
58 // Initializes the view with the given |customView|.
59 // If the size of |rightFrame| is non-zero, the view will be split into two
60 // parts with |leftFrame| and |rightFrame|. Otherwise the Autofill view will
61 // be shown in |leftFrame|.
62 - (void)initializeViewWithCustomView:(UIView*)customView
63 leftFrame:(CGRect)leftFrame
64 rightFrame:(CGRect)rightFrame;
66 // Returns a view that shows navigation buttons in the |frame|.
67 - (UIView*)viewForNavigationButtonsInFrame:(CGRect)frame;
69 // Returns a navigation button for Autofill that has |normalImage| for state
70 // UIControlStateNormal, a |pressedImage| for states UIControlStateSelected and
71 // UIControlStateHighlighted, and an optional |disabledImage| for
72 // UIControlStateDisabled.
73 - (UIButton*)keyboardNavButtonWithNormalImage:(UIImage*)normalImage
74 pressedImage:(UIImage*)pressedImage
75 disabledImage:(UIImage*)disabledImage
79 originX:(CGFloat)originX
80 originY:(CGFloat)originY
81 height:(CGFloat)height;
83 // Adds a background image to |view|. The supplied image is stretched to fit the
84 // space by stretching the content its horizontal and vertical centers.
85 + (void)addBackgroundImageInView:(UIView*)view
86 withImageName:(NSString*)imageName;
88 // Adds an image view in |view| with an image named |imageName| at
89 // (|originX|, 0). The width is |width| and the height is the height of |view|.
90 + (void)addImageViewWithImageName:(NSString*)imageName
91 originX:(CGFloat)originX
92 originY:(CGFloat)originY
98 @implementation FormInputAccessoryView {
99 // The custom view that is displayed in the input accessory view.
100 base::scoped_nsobject<UIView> _customView;
102 // Delegate of this view.
103 base::WeakNSProtocol<id<FormInputAccessoryViewDelegate>> _delegate;
106 - (instancetype)initWithFrame:(CGRect)frame
107 delegate:(id<FormInputAccessoryViewDelegate>)delegate
108 customView:(UIView*)customView
109 leftFrame:(CGRect)leftFrame
110 rightFrame:(CGRect)rightFrame {
112 self = [super initWithFrame:frame];
114 _delegate.reset(delegate);
115 _customView.reset([customView retain]);
116 [self initializeViewWithCustomView:_customView
118 rightFrame:rightFrame];
123 - (instancetype)initWithFrame:(CGRect)frame customView:(UIView*)customView {
124 self = [super initWithFrame:frame];
126 _customView.reset([customView retain]);
128 CGRectMake(0, 0, CGRectGetWidth(frame), CGRectGetHeight(frame));
129 [self addSubview:customView];
131 [[self class] addBackgroundImageInView:self
132 withImageName:@"autofill_keyboard_background"];
138 #pragma mark UIInputViewAudioFeedback
140 - (BOOL)enableInputClicksWhenVisible {
145 #pragma mark Private Methods
147 - (void)initializeViewWithCustomView:(UIView*)customView
148 leftFrame:(CGRect)leftFrame
149 rightFrame:(CGRect)rightFrame {
150 UIView* customViewContainer = [[[UIView alloc] init] autorelease];
151 [self addSubview:customViewContainer];
152 UIView* navView = [[[UIView alloc] init] autorelease];
153 [self addSubview:navView];
155 bool splitKeyboard = CGRectGetWidth(rightFrame) != 0;
156 BOOL isRTL = base::i18n::IsRTL();
158 // The computed frame for |customView|.
159 CGRect customViewFrame;
160 // Frame of a subview of |navView| in which navigation buttons will be shown.
161 CGRect navFrame = CGRectZero;
163 NSString* navViewBackgroundImageName = nil;
164 NSString* customViewContainerBackgroundImageName = nil;
165 NSUInteger navFrameOriginX = 0;
167 navView.frame = leftFrame;
168 navViewBackgroundImageName = @"autofill_keyboard_background_left";
169 customViewContainer.frame = rightFrame;
170 customViewContainerBackgroundImageName =
171 @"autofill_keyboard_background_right";
172 // Navigation buttons will be shown on the left side.
175 customViewContainer.frame = leftFrame;
176 customViewContainerBackgroundImageName =
177 @"autofill_keyboard_background_left";
178 navView.frame = rightFrame;
179 navViewBackgroundImageName = @"autofill_keyboard_background_right";
180 // Navigation buttons will be shown on the right side.
182 CGRectGetWidth(navView.frame) - GetNavigationViewWidth();
186 addBackgroundImageInView:customViewContainer
187 withImageName:customViewContainerBackgroundImageName];
188 [[self class] addBackgroundImageInView:navView
189 withImageName:navViewBackgroundImageName];
191 // For RTL, the custom view is the right view; the padding should be at the
192 // left side of this view. Otherwise, the custom view is the left view
193 // and the space is at the right side.
194 customViewFrame = CGRectMake(isRTL ? kCustomViewHorizontalMargin : 0, 0,
195 CGRectGetWidth(customViewContainer.bounds) -
196 kCustomViewHorizontalMargin,
197 CGRectGetHeight(customViewContainer.bounds));
198 navFrame = CGRectMake(navFrameOriginX, 0, GetNavigationViewWidth(),
199 CGRectGetHeight(navView.frame));
201 NSUInteger navViewFrameOriginX = 0;
202 NSUInteger customViewContainerFrameOrginX = 0;
204 navViewFrameOriginX = kNavigationAreaSeparatorShadowWidth;
205 customViewContainerFrameOrginX = GetNavigationViewWidth();
207 navViewFrameOriginX =
208 CGRectGetWidth(leftFrame) - GetNavigationViewWidth();
211 customViewContainer.frame =
212 CGRectMake(customViewContainerFrameOrginX, 0,
213 CGRectGetWidth(leftFrame) - GetNavigationViewWidth() +
214 kNavigationAreaSeparatorShadowWidth,
215 CGRectGetHeight(leftFrame));
216 navView.frame = CGRectMake(navViewFrameOriginX, 0, GetNavigationViewWidth(),
217 CGRectGetHeight(leftFrame));
219 customViewFrame = customViewContainer.bounds;
220 navFrame = navView.bounds;
221 [[self class] addBackgroundImageInView:self
222 withImageName:@"autofill_keyboard_background"];
225 [customView setFrame:customViewFrame];
226 [customViewContainer addSubview:customView];
227 [navView addSubview:[self viewForNavigationButtonsInFrame:navFrame]];
230 UIImage* ButtonImage(NSString* name) {
231 UIImage* rawImage = [UIImage imageNamed:name];
232 return StretchableImageFromUIImage(rawImage, 1, 0);
235 - (UIView*)viewForNavigationButtonsInFrame:(CGRect)frame {
236 UIView* navView = [[[UIView alloc] initWithFrame:frame] autorelease];
238 BOOL isRTL = base::i18n::IsRTL();
240 // Vertical space is left for a dividing line.
241 CGFloat firstRow = 1;
243 CGFloat currentX = 0;
245 // Navigation view is at the right side if not RTL. Add a left separator in
248 [[self class] addImageViewWithImageName:@"autofill_left_sep"
251 width:kNavigationAreaSeparatorWidth
253 currentX = kNavigationAreaSeparatorWidth;
256 UIButton* previousButton = [self
257 keyboardNavButtonWithNormalImage:ButtonImage(@"autofill_prev")
258 pressedImage:ButtonImage(@"autofill_prev_pressed")
259 disabledImage:ButtonImage(@"autofill_prev_inactive")
262 selectPreviousElementWithButtonPress)
266 height:CGRectGetHeight(frame)];
268 setAccessibilityLabel:l10n_util::GetNSString(
269 IDS_IOS_AUTOFILL_ACCNAME_PREVIOUS_FIELD)];
270 [navView addSubview:previousButton];
271 currentX += kNavigationButtonWidth;
273 // Add internal separator.
274 [[self class] addImageViewWithImageName:@"autofill_middle_sep"
277 width:kNavigationButtonSeparatorWidth
279 currentX += kNavigationButtonSeparatorWidth;
281 UIButton* nextButton = [self
282 keyboardNavButtonWithNormalImage:ButtonImage(@"autofill_next")
283 pressedImage:ButtonImage(@"autofill_next_pressed")
284 disabledImage:ButtonImage(@"autofill_next_inactive")
287 selectNextElementWithButtonPress)
291 height:CGRectGetHeight(frame)];
292 [nextButton setAccessibilityLabel:l10n_util::GetNSString(
293 IDS_IOS_AUTOFILL_ACCNAME_NEXT_FIELD)];
294 [navView addSubview:nextButton];
295 currentX += kNavigationButtonWidth;
297 [_delegate fetchPreviousAndNextElementsPresenceWithCompletionHandler:
298 ^(BOOL hasPreviousElement, BOOL hasNextElement) {
299 previousButton.enabled = hasPreviousElement;
300 nextButton.enabled = hasNextElement;
303 if (ShouldShowCloseButton()) {
304 // Add internal separator.
305 [[self class] addImageViewWithImageName:@"autofill_middle_sep"
308 width:kNavigationButtonSeparatorWidth
310 currentX += kNavigationButtonSeparatorWidth;
312 UIButton* closeButton = [self
313 keyboardNavButtonWithNormalImage:ButtonImage(@"autofill_close")
314 pressedImage:ButtonImage(@"autofill_close_pressed")
317 action:@selector(closeKeyboardWithButtonPress)
321 height:CGRectGetHeight(frame)];
323 setAccessibilityLabel:l10n_util::GetNSString(
324 IDS_IOS_AUTOFILL_ACCNAME_HIDE_KEYBOARD)];
325 [navView addSubview:closeButton];
326 currentX += kNavigationButtonWidth;
329 // Navigation view is at the left side for RTL. Add a right separator in
332 [[self class] addImageViewWithImageName:@"autofill_right_sep"
335 width:kNavigationAreaSeparatorWidth
342 - (UIButton*)keyboardNavButtonWithNormalImage:(UIImage*)normalImage
343 pressedImage:(UIImage*)pressedImage
344 disabledImage:(UIImage*)disabledImage
347 enabled:(BOOL)enabled
348 originX:(CGFloat)originX
349 originY:(CGFloat)originY
350 height:(CGFloat)height {
351 UIButton* button = [UIButton buttonWithType:UIButtonTypeCustom];
354 CGRectMake(originX, originY, kNavigationButtonWidth, height - originY);
356 [button setBackgroundImage:normalImage forState:UIControlStateNormal];
357 [button setBackgroundImage:pressedImage forState:UIControlStateSelected];
358 [button setBackgroundImage:pressedImage forState:UIControlStateHighlighted];
360 [button setBackgroundImage:disabledImage forState:UIControlStateDisabled];
362 CALayer* layer = [button layer];
363 layer.borderWidth = 0;
364 layer.borderColor = [[UIColor blackColor] CGColor];
365 button.enabled = enabled;
366 [button addTarget:target
368 forControlEvents:UIControlEventTouchUpInside];
372 + (void)addBackgroundImageInView:(UIView*)view
373 withImageName:(NSString*)imageName {
374 UIImage* backgroundImage = StretchableImageNamed(imageName);
376 UIImageView* backgroundImageView =
377 [[[UIImageView alloc] initWithFrame:view.bounds] autorelease];
378 [backgroundImageView setImage:backgroundImage];
379 [backgroundImageView setAlpha:kBackgroundColorAlpha];
380 [view addSubview:backgroundImageView];
381 [view sendSubviewToBack:backgroundImageView];
384 + (void)addImageViewWithImageName:(NSString*)imageName
385 originX:(CGFloat)originX
386 originY:(CGFloat)originY
388 inView:(UIView*)view {
390 StretchableImageFromUIImage([UIImage imageNamed:imageName], 0, 0);
391 base::scoped_nsobject<UIImageView> imageView(
392 [[UIImageView alloc] initWithImage:image]);
393 [imageView setFrame:CGRectMake(originX, originY, width,
394 CGRectGetHeight(view.bounds) - originY)];
395 [view addSubview:imageView];