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