Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(297)

Side by Side Diff: chrome/browser/ui/cocoa/autofill/autofill_overlay_controller.mm

Issue 23674004: [rAC, OSX] Add overlay shield for interstitials/waits. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Review fixes & missing test file. Created 7 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright 2013 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.
4
5 #import "chrome/browser/ui/cocoa/autofill/autofill_overlay_controller.h"
6
7 #include "base/logging.h"
8 #include "base/mac/foundation_util.h"
9 #include "base/strings/sys_string_conversions.h"
10 #include "base/timer/timer.h"
11 #include "chrome/browser/ui/autofill/autofill_dialog_types.h"
12 #include "chrome/browser/ui/autofill/autofill_dialog_view_delegate.h"
13 #include "chrome/browser/ui/cocoa/autofill/autofill_dialog_constants.h"
14 #include "skia/ext/skia_utils_mac.h"
15 #include "ui/base/animation/animation_delegate.h"
16 #include "ui/base/animation/multi_animation.h"
17
18 namespace {
19
20 // Spacing between lines of text in the overlay view.
21 const CGFloat kOverlayTextInterlineSpacing = 10;
22
23 // Spacing below image and above text messages in overlay view.
24 const CGFloat kOverlayImageBottomMargin = 50;
25
26 // TODO(groby): Unify colors with Views.
27 // Slight shading for mouse hover and legal document background.
28 SkColor kShadingColor = 0xfff2f2f2;
29
30 // A border color for the legal document view.
31 SkColor kSubtleBorderColor = 0xffdfdfdf;
32
33 // Shorten a few long types.
34 typedef ui::MultiAnimation::Part Part;
35 typedef ui::MultiAnimation::Parts Parts;
36
37 } // namespace
38
39 // Bridges Objective C and C++ delegate interfaces.
40 class AnimationDelegateBridge : public ui::AnimationDelegate {
41 public:
42 AnimationDelegateBridge(id<AnimationDelegate>);
43
44 protected:
45 // AnimationDelegate implementation.
46 virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE;
47 virtual void AnimationEnded(const ui::Animation* animation) OVERRIDE;
48
49 private:
50 id<AnimationDelegate> delegate_; // Not owned. Owns DelegateBridge.
51 };
52
53 AnimationDelegateBridge::AnimationDelegateBridge(
54 id<AnimationDelegate> delegate) : delegate_(delegate) {}
55
56 void AnimationDelegateBridge::AnimationProgressed(
57 const ui::Animation* animation) {
58 [delegate_ animationProgressed:animation];
59 }
60
61 void AnimationDelegateBridge::AnimationEnded(
62 const ui::Animation* animation) {
63 [delegate_ animationEnded:animation];
64 }
65
66 class OverlayTimerBridge {
67 public:
68 OverlayTimerBridge(AutofillOverlayController* controller);
69 void SetExpiry(const base::TimeDelta& delta);
70
71 private:
72 void UpdateOverlayState();
73
74 base::Timer refresh_timer_; // Controls when it's time to refresh overlay.
75
76 // not owned, |overlay_view_controller_| owns |this|.
77 AutofillOverlayController* overlay_view_controller_;
78 };
79
80 OverlayTimerBridge::OverlayTimerBridge(AutofillOverlayController* controller)
81 : refresh_timer_(false, false),
82 overlay_view_controller_(controller) {
83 }
84
85 void OverlayTimerBridge::SetExpiry(const base::TimeDelta& expiry) {
86 if (expiry != base::TimeDelta()) {
87 refresh_timer_.Start(FROM_HERE,
88 expiry,
89 base::Bind(&OverlayTimerBridge::UpdateOverlayState,
90 base::Unretained(this)));
91 } else {
92 refresh_timer_.Stop();
93 }
94 }
95
96 void OverlayTimerBridge::UpdateOverlayState() {
97 [overlay_view_controller_ updateState];
98 }
99
100 // An NSView encapsulating the message stack and its custom drawn elements.
101 @interface AutofillMessageStackView : NSView<AutofillLayout>
102 - (CGFloat)heightForWidth:(CGFloat)width;
103 - (void)setMessages:
104 (const std::vector<autofill::DialogOverlayString>&)messages;
105 @end
106
107
108 @implementation AutofillMessageStackView
109
110 - (void)drawRect:(NSRect)dirtyRect {
111 NSColor* shadingColor = gfx::SkColorToCalibratedNSColor(kShadingColor);
112 NSColor* borderColor = gfx::SkColorToCalibratedNSColor(kSubtleBorderColor);
113
114 CGFloat arrowHalfWidth = kArrowWidth / 2.0;
115 NSRect bounds = [self bounds];
116 CGFloat y = NSMaxY(bounds) - kArrowHeight;
117
118 NSBezierPath* arrow = [NSBezierPath bezierPath];
119 // Note that we purposely draw slightly outside of |bounds| so that the
120 // stroke is hidden on the sides.
121 [arrow moveToPoint:NSMakePoint(NSMinX(bounds) - 1.0, y)];
122 [arrow relativeLineToPoint:NSMakePoint(NSMidX(bounds) - arrowHalfWidth, 0)];
123 [arrow relativeLineToPoint:NSMakePoint(arrowHalfWidth, kArrowHeight)];
124 [arrow relativeLineToPoint:NSMakePoint(arrowHalfWidth, -kArrowHeight)];
125 [arrow lineToPoint:NSMakePoint(NSMaxX(bounds) + 1.0, y)];
126 [arrow lineToPoint:NSMakePoint(NSMaxX(bounds) + 1.0, NSMinY(bounds) - 1.0)];
127 [arrow lineToPoint:NSMakePoint(NSMinX(bounds) - 1.0, NSMinY(bounds) - 1.0)];
128 [arrow closePath];
129
130 [shadingColor setFill];
131 [arrow fill];
132 [borderColor setStroke];
133 [arrow stroke];
134
135 [super drawRect:dirtyRect];
136 }
137
138 - (CGFloat)heightForWidth:(CGFloat)width {
139 CGFloat height = kOverlayTextInterlineSpacing;
140 for (NSTextView* label in [self subviews]) {
141 height += NSHeight([label frame]);
142 height += kOverlayTextInterlineSpacing;
143 }
144 return height + kArrowHeight;
145 }
146
147 - (void)setMessages:
148 (const std::vector<autofill::DialogOverlayString>&) messages {
149 // We probably want to look at other multi-line messages somewhere.
150 base::scoped_nsobject<NSMutableArray> labels(
151 [[NSMutableArray alloc] initWithCapacity:messages.size()]);
152 for (size_t i = 0; i < messages.size(); ++i) {
153 base::scoped_nsobject<NSTextField> label(
154 [[NSTextField alloc] initWithFrame:NSZeroRect]);
155
156 NSFont* labelFont = messages[i].font.GetNativeFont();
157 [label setEditable:NO];
158 [label setBordered:NO];
159 [label setDrawsBackground:NO];
160 [label setFont:labelFont];
161 [label setStringValue:base::SysUTF16ToNSString(messages[i].text)];
162 [label setTextColor:gfx::SkColorToDeviceNSColor(messages[i].text_color)];
163 DCHECK(messages[i].alignment == gfx::ALIGN_CENTER);
164 [label setAlignment:NSCenterTextAlignment];
165 [label sizeToFit];
166
167 [labels addObject:label];
168 }
169 [self setSubviews:labels];
170 [self setHidden:([labels count] == 0)];
171 }
172
173 - (void)performLayout {
174 CGFloat y =
175 NSMaxY([self bounds]) - kArrowHeight - kOverlayTextInterlineSpacing;
176 for (NSTextView* label in [self subviews]) {
177 CGFloat labelHeight = NSHeight([label frame]);
178 [label setFrame:NSMakeRect(0, y - labelHeight,
179 NSWidth([self bounds]), labelHeight)];
180 y = NSMinY([label frame]) - kOverlayTextInterlineSpacing;
181 }
182 DCHECK_GT(0.0, y);
183 }
184
185 - (NSSize)preferredSize {
186 NOTREACHED();
187 return NSZeroSize;
188 }
189
190 @end
191
192
193 @implementation AutofillOverlayController
194
195 - (id)initWithDelegate:(autofill::AutofillDialogViewDelegate*)delegate {
196 if (self = [super initWithNibName:nil bundle:nil]) {
197 delegate_ = delegate;
198 refreshTimer_.reset(new OverlayTimerBridge(self));
199
200 base::scoped_nsobject<NSBox> view(
201 [[NSBox alloc] initWithFrame:NSZeroRect]);
202 [view setBoxType:NSBoxCustom];
203 [view setBorderType:NSNoBorder];
204 [view setContentViewMargins:NSZeroSize];
205 [view setTitlePosition:NSNoTitle];
206
207 childView_.reset([[NSView alloc] initWithFrame:NSZeroRect]);
208 messageStackView_.reset(
209 [[AutofillMessageStackView alloc] initWithFrame:NSZeroRect]);
210 imageView_.reset([[NSImageView alloc] initWithFrame:NSZeroRect]);
211 [imageView_ setImageAlignment:NSImageAlignCenter];
212
213 [childView_ setSubviews:@[messageStackView_, imageView_]];
214 [view addSubview:childView_];
215 [self setView:view];
216 }
217 return self;
218 }
219
220 - (void)updateState {
221 const autofill::DialogOverlayState& state = delegate_->GetDialogOverlay();
222
223 // Don't update anything if we're still fading out the old state.
224 if (fadeOutAnimation_)
225 return;
226
227 if (state.image.IsEmpty()) {
228 [[self view] setHidden:YES];
229 return;
230 }
231
232 NSBox* view = base::mac::ObjCCastStrict<NSBox>([self view]);
233 [view setFillColor:[[view window] backgroundColor]];
234 [view setAlphaValue:1];
235 [childView_ setAlphaValue:1];
236 [imageView_ setImage:state.image.ToNSImage()];
237 [messageStackView_ setMessages:state.strings];
238 [childView_ setHidden:NO];
239 [view setHidden:NO];
240
241 id delegate = [[[self view] window] windowController];
242 if ([delegate respondsToSelector:@selector(requestRelayout)])
243 [delegate performSelector:@selector(requestRelayout)];
244
245 refreshTimer_->SetExpiry(state.expiry);
246 }
247
248 - (void)beginFadeOut {
249 // Remove first responders, since focus rings show on top of overlay view.
250 // TODO(groby): Figure out to do that less hacky. Ideally, the focus ring
251 // should be part of the controls fading in, not appear at the end.
252 [[self view] setNextResponder:[[[self view] window] firstResponder]];
253 [[[self view] window] makeFirstResponder:[self view]];
254
255 Parts parts;
256 // For this part of the animation, simply show the splash image.
257 parts.push_back(Part(autofill::kSplashDisplayDurationMs, ui::Tween::ZERO));
258 // For this part of the animation, fade out the splash image.
259 parts.push_back(
260 Part(autofill::kSplashFadeOutDurationMs, ui::Tween::EASE_IN));
261 // For this part of the animation, fade out |this| (fade in the dialog).
262 parts.push_back(
263 Part(autofill::kSplashFadeInDialogDurationMs, ui::Tween::EASE_OUT));
264 fadeOutAnimation_.reset(
265 new ui::MultiAnimation(parts,
266 ui::MultiAnimation::GetDefaultTimerInterval()));
267 animationDelegate_.reset(new AnimationDelegateBridge(self));
268 fadeOutAnimation_->set_delegate(animationDelegate_.get());
269 fadeOutAnimation_->set_continuous(false);
270 fadeOutAnimation_->Start();
271 }
272
273 - (int)heightForWidth:(int) width {
274 // 0 means "no preference". Image-only overlays fit the container.
275 if ([messageStackView_ isHidden])
276 return 0;
277
278 // Overlays with text messages express a size preference.
279 return kOverlayImageBottomMargin +
280 [messageStackView_ heightForWidth:width] +
281 NSHeight([imageView_ frame]);
282
283 return 0;
284 }
285
286 - (NSSize)preferredSize {
287 NOTREACHED(); // Only implemented as part of AutofillLayout protocol.
288 return NSZeroSize;
289 }
290
291 - (void)performLayout {
292 NSRect bounds = [[self view] bounds];
293 [childView_ setFrame:bounds];
294 if ([messageStackView_ isHidden]) {
295 [imageView_ setFrame:bounds];
296 return;
297 }
298
299 int messageHeight = [messageStackView_ heightForWidth:NSWidth(bounds)];
300 [messageStackView_ setFrame:
301 NSMakeRect(0, 0, NSWidth(bounds), messageHeight)];
302 [messageStackView_ performLayout];
303
304 NSSize imageSize = [[imageView_ image] size];
305 [imageView_ setFrame:NSMakeRect(
306 0, NSMaxY([messageStackView_ frame]) + kOverlayImageBottomMargin,
307 NSWidth(bounds), imageSize.height)];
308 }
309
310 - (void)animationProgressed:(const ui::Animation*) animation {
311 DCHECK_EQ(animation, fadeOutAnimation_.get());
312
313 // Fade out children in stage 1.
314 if (fadeOutAnimation_->current_part_index() == 1) {
315 [childView_ setAlphaValue:(1 - fadeOutAnimation_->GetCurrentValue())];
316 }
317
318 // Fade out background in stage 2(i.e. fade in what's behind |this|).
319 if (fadeOutAnimation_->current_part_index() == 2) {
320 [[self view] setAlphaValue: (1 - fadeOutAnimation_->GetCurrentValue())];
321 [childView_ setHidden:YES];
322 }
323
324 // If any fading was done, refresh display.
325 if (fadeOutAnimation_->current_part_index() != 0) {
326 [[self view] setNeedsDisplay:YES];
327 }
328 }
329
330 - (void)animationEnded:(const ui::Animation*)animation {
331 DCHECK_EQ(animation, fadeOutAnimation_.get());
332 [[self view] setHidden:YES];
333 fadeOutAnimation_.reset();
334 }
335
336 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698