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_resources.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")
261 action:@selector(selectPreviousElement)
265 height:CGRectGetHeight(frame)];
267 setAccessibilityLabel:l10n_util::GetNSString(
268 IDS_AUTOFILL_ACCNAME_PREVIOUS_FIELD)];
269 [navView addSubview:previousButton];
270 currentX += kNavigationButtonWidth;
272 // Add internal separator.
273 [[self class] addImageViewWithImageName:@"autofill_middle_sep"
276 width:kNavigationButtonSeparatorWidth
278 currentX += kNavigationButtonSeparatorWidth;
280 UIButton* nextButton = [self
281 keyboardNavButtonWithNormalImage:ButtonImage(@"autofill_next")
282 pressedImage:ButtonImage(@"autofill_next_pressed")
283 disabledImage:ButtonImage(@"autofill_next_inactive")
285 action:@selector(selectNextElement)
289 height:CGRectGetHeight(frame)];
290 [nextButton setAccessibilityLabel:l10n_util::GetNSString(
291 IDS_AUTOFILL_ACCNAME_NEXT_FIELD)];
292 [navView addSubview:nextButton];
293 currentX += kNavigationButtonWidth;
295 [_delegate fetchPreviousAndNextElementsPresenceWithCompletionHandler:
296 ^(BOOL hasPreviousElement, BOOL hasNextElement) {
297 previousButton.enabled = hasPreviousElement;
298 nextButton.enabled = hasNextElement;
301 if (ShouldShowCloseButton()) {
302 // Add internal separator.
303 [[self class] addImageViewWithImageName:@"autofill_middle_sep"
306 width:kNavigationButtonSeparatorWidth
308 currentX += kNavigationButtonSeparatorWidth;
310 UIButton* closeButton = [self
311 keyboardNavButtonWithNormalImage:ButtonImage(@"autofill_close")
312 pressedImage:ButtonImage(@"autofill_close_pressed")
315 action:@selector(closeKeyboard)
319 height:CGRectGetHeight(frame)];
320 [closeButton setAccessibilityLabel:l10n_util::GetNSString(
321 IDS_AUTOFILL_ACCNAME_HIDE_KEYBOARD)];
322 [navView addSubview:closeButton];
323 currentX += kNavigationButtonWidth;
326 // Navigation view is at the left side for RTL. Add a right separator in
329 [[self class] addImageViewWithImageName:@"autofill_right_sep"
332 width:kNavigationAreaSeparatorWidth
339 - (UIButton*)keyboardNavButtonWithNormalImage:(UIImage*)normalImage
340 pressedImage:(UIImage*)pressedImage
341 disabledImage:(UIImage*)disabledImage
344 enabled:(BOOL)enabled
345 originX:(CGFloat)originX
346 originY:(CGFloat)originY
347 height:(CGFloat)height {
348 UIButton* button = [UIButton buttonWithType:UIButtonTypeCustom];
351 CGRectMake(originX, originY, kNavigationButtonWidth, height - originY);
353 [button setBackgroundImage:normalImage forState:UIControlStateNormal];
354 [button setBackgroundImage:pressedImage forState:UIControlStateSelected];
355 [button setBackgroundImage:pressedImage forState:UIControlStateHighlighted];
357 [button setBackgroundImage:disabledImage forState:UIControlStateDisabled];
359 CALayer* layer = [button layer];
360 layer.borderWidth = 0;
361 layer.borderColor = [[UIColor blackColor] CGColor];
362 button.enabled = enabled;
363 [button addTarget:target
365 forControlEvents:UIControlEventTouchUpInside];
369 + (void)addBackgroundImageInView:(UIView*)view
370 withImageName:(NSString*)imageName {
371 UIImage* backgroundImage = StretchableImageNamed(imageName);
373 UIImageView* backgroundImageView =
374 [[[UIImageView alloc] initWithFrame:view.bounds] autorelease];
375 [backgroundImageView setImage:backgroundImage];
376 [backgroundImageView setAlpha:kBackgroundColorAlpha];
377 [view addSubview:backgroundImageView];
378 [view sendSubviewToBack:backgroundImageView];
381 + (void)addImageViewWithImageName:(NSString*)imageName
382 originX:(CGFloat)originX
383 originY:(CGFloat)originY
385 inView:(UIView*)view {
387 StretchableImageFromUIImage([UIImage imageNamed:imageName], 0, 0);
388 base::scoped_nsobject<UIImageView> imageView(
389 [[UIImageView alloc] initWithImage:image]);
390 [imageView setFrame:CGRectMake(originX, originY, width,
391 CGRectGetHeight(view.bounds) - originY)];
392 [view addSubview:imageView];