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 "chrome/browser/ui/autofill/autofill_dialog_types.h" | |
10 #include "chrome/browser/ui/cocoa/autofill/autofill_dialog_constants.h" | |
11 #include "skia/ext/skia_utils_mac.h" | |
12 #include "ui/base/animation/animation_delegate.h" | |
13 #include "ui/base/animation/multi_animation.h" | |
14 | |
15 namespace { | |
16 | |
17 // Spacing between lines of text in the overlay view. | |
18 const int kOverlayTextInterlineSpacing = 10; | |
19 | |
20 // Spacing below image and above text messages in overlay view. | |
21 const int kOverlayImageBottomMargin = 50; | |
22 | |
23 // TODO(groby): Unify colors with Views. | |
24 // Slight shading for mouse hover and legal document background. | |
25 SkColor kShadingColor = 0xfff2f2f2; | |
26 | |
27 // A border color for the legal document view. | |
28 SkColor kSubtleBorderColor = 0xffdfdfdf; | |
29 | |
30 // Shorten a few long types. | |
31 typedef ui::MultiAnimation::Part Part; | |
32 typedef ui::MultiAnimation::Parts Parts; | |
33 | |
34 } // namespace | |
35 | |
36 // Bridges Objective C and C++ delegate interfaces. | |
37 class AnimationDelegateBridge : public ui::AnimationDelegate { | |
38 public: | |
39 AnimationDelegateBridge(id<ChromiumAnimationDelegate>); | |
40 | |
41 protected: | |
42 // AnimationDelegate implementation. | |
43 virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE; | |
44 virtual void AnimationEnded(const ui::Animation* animation) OVERRIDE; | |
45 | |
46 private: | |
47 id<ChromiumAnimationDelegate> delegate_; // Not owned. Owns DelegateBridge. | |
48 }; | |
49 | |
50 AnimationDelegateBridge::AnimationDelegateBridge( | |
51 id<ChromiumAnimationDelegate> delegate) : delegate_(delegate) {} | |
52 | |
53 void AnimationDelegateBridge::AnimationProgressed( | |
54 const ui::Animation* animation) { | |
55 [delegate_ animationProgressed:animation]; | |
56 } | |
57 | |
58 void AnimationDelegateBridge::AnimationEnded( | |
59 const ui::Animation* animation) { | |
60 [delegate_ animationEnded:animation]; | |
61 } | |
62 | |
63 | |
64 // An NSView encapsulating the message stack and its custom drawn elements. | |
65 @interface AutofillMessageStackView : NSView<AutofillLayout> | |
66 - (CGFloat)getHeightForWidth:(CGFloat)width; | |
67 - (void)setMessages: | |
68 (const std::vector<autofill::DialogOverlayString>&)messages; | |
69 @end | |
70 | |
71 | |
72 @implementation AutofillMessageStackView | |
73 | |
74 - (void)drawRect:(NSRect)dirtyRect { | |
75 NSColor* shadingColor = gfx::SkColorToCalibratedNSColor(kShadingColor); | |
76 NSColor* borderColor = gfx::SkColorToCalibratedNSColor(kSubtleBorderColor); | |
77 | |
78 CGFloat arrowHalfWidth = kArrowWidth / 2.0; | |
79 NSRect bounds = [self bounds]; | |
80 CGFloat y = NSMaxY(bounds) - kArrowHeight; | |
81 | |
82 NSBezierPath* arrow = [NSBezierPath bezierPath]; | |
83 // Note that we purposely draw slightly outside of |bounds| so that the | |
84 // stroke is hidden on the sides. | |
85 [arrow moveToPoint:NSMakePoint(NSMinX(bounds) - 1.0, y)]; | |
86 [arrow relativeLineToPoint:NSMakePoint(NSMidX(bounds) - arrowHalfWidth, 0)]; | |
87 [arrow relativeLineToPoint:NSMakePoint(arrowHalfWidth, kArrowHeight)]; | |
88 [arrow relativeLineToPoint:NSMakePoint(arrowHalfWidth, -kArrowHeight)]; | |
89 [arrow lineToPoint:NSMakePoint(NSMaxX(bounds) + 1.0, y)]; | |
90 [arrow lineToPoint:NSMakePoint(NSMaxX(bounds) + 1.0, NSMinY(bounds) - 1.0)]; | |
91 [arrow lineToPoint:NSMakePoint(NSMinX(bounds) - 1.0, NSMinY(bounds) - 1.0)]; | |
92 [arrow closePath]; | |
93 | |
94 [shadingColor setFill]; | |
95 [arrow fill]; | |
96 [borderColor setStroke]; | |
97 [arrow stroke]; | |
98 | |
99 [super drawRect:dirtyRect]; | |
100 } | |
101 | |
102 - (CGFloat)getHeightForWidth:(CGFloat)width { | |
103 CGFloat height = kOverlayTextInterlineSpacing; | |
104 for (NSTextView* label in [self subviews]) { | |
105 height += NSHeight([label frame]); | |
106 height += kOverlayTextInterlineSpacing; | |
107 } | |
108 return height + kArrowHeight; | |
109 } | |
110 | |
111 - (void)setMessages: | |
112 (const std::vector<autofill::DialogOverlayString>&) messages { | |
113 // We probably want to look at other multi-line messages somewhere. | |
114 base::scoped_nsobject<NSMutableArray> labels( | |
115 [[NSMutableArray alloc] initWithCapacity:messages.size()]); | |
116 for (size_t i = 0; i < messages.size(); ++i) { | |
117 base::scoped_nsobject<NSTextField> label( | |
118 [[NSTextField alloc] initWithFrame:NSZeroRect]); | |
119 | |
120 NSFont* labelFont = messages[i].font.GetNativeFont(); | |
121 [label setEditable:NO]; | |
122 [label setBordered:NO]; | |
123 [label setDrawsBackground:NO]; | |
124 [label setFont:labelFont]; | |
125 [label setStringValue:base::SysUTF16ToNSString(messages[i].text)]; | |
126 [label setTextColor:gfx::SkColorToDeviceNSColor(messages[i].text_color)]; | |
127 DCHECK(messages[i].alignment == gfx::ALIGN_CENTER); | |
128 [label setAlignment:NSCenterTextAlignment]; | |
129 [label sizeToFit]; | |
130 | |
131 [labels addObject:label]; | |
132 } | |
133 [self setSubviews:labels]; | |
134 [self setHidden:([labels count] == 0)]; | |
135 } | |
136 | |
137 - (void)performLayout { | |
138 CGFloat y = | |
139 NSMaxY([self bounds]) - kArrowHeight - kOverlayTextInterlineSpacing; | |
140 for (NSTextView* label in [self subviews]) { | |
141 CGFloat labelHeight = NSHeight([label frame]); | |
142 [label setFrame:NSMakeRect(0, y - labelHeight, | |
143 NSWidth([self bounds]), labelHeight)]; | |
144 y = NSMinY([label frame]) - kOverlayTextInterlineSpacing; | |
145 } | |
146 DCHECK_GT(0.0, y); | |
147 } | |
148 | |
149 - (NSSize)preferredSize { | |
150 NOTREACHED(); | |
151 return NSZeroSize; | |
152 } | |
153 | |
154 @end | |
155 | |
156 | |
157 @implementation AutofillOverlayController | |
158 | |
159 - (id)init { | |
sail
2013/09/04 20:04:06
rsesek pointed out that NSViewController has an de
groby-ooo-7-16
2013/09/04 20:41:45
So, all invocation sites use initWithNibName:nil b
sail
2013/09/04 21:18:51
Yea that makes sense.
groby-ooo-7-16
2013/09/04 22:19:01
Done.
| |
160 if (self = [super init]) { | |
161 view_.reset([[NSBox alloc] initWithFrame:NSZeroRect]); | |
162 [view_ setBoxType:NSBoxCustom]; | |
163 [view_ setBorderType:NSNoBorder]; | |
164 [view_ setContentViewMargins:NSZeroSize]; | |
165 [view_ setTitlePosition:NSNoTitle]; | |
166 childView_.reset([[NSView alloc] initWithFrame:NSZeroRect]); | |
167 messageStackView_.reset( | |
168 [[AutofillMessageStackView alloc] initWithFrame:NSZeroRect]); | |
169 imageView_.reset([[NSImageView alloc] initWithFrame:NSZeroRect]); | |
170 [imageView_ setImageAlignment:NSImageAlignCenter]; | |
171 [childView_ setSubviews:@[messageStackView_, imageView_]]; | |
172 [view_ addSubview:childView_]; | |
173 | |
174 [self setView:view_]; | |
175 } | |
176 return self; | |
177 } | |
178 | |
179 - (void)setState:(const autofill::DialogOverlayState&)state { | |
180 // Don't update anything if we're still fading out the old state. | |
181 if (fadeOutAnimation_) | |
182 return; | |
183 | |
184 if (state.image.IsEmpty()) { | |
185 [view_ setHidden:YES]; | |
186 return; | |
187 } | |
188 | |
189 [view_ setFillColor:[[view_ window] backgroundColor]]; | |
190 [view_ setAlphaValue:1]; | |
191 [childView_ setAlphaValue:1]; | |
192 [imageView_ setImage:state.image.ToNSImage()]; | |
193 [messageStackView_ setMessages:state.strings]; | |
194 [childView_ setHidden:NO]; | |
195 [view_ setHidden:NO]; | |
196 | |
197 id delegate = [[[self view] window] windowController]; | |
198 if ([delegate respondsToSelector:@selector(requestRelayout)]) | |
199 [delegate performSelector:@selector(requestRelayout)]; | |
200 } | |
201 | |
202 - (void)beginFadeOut { | |
203 // Remove first responders, since focus rings show on top of overlay view. | |
204 // TODO(groby): Figure out to do that less hacky. Ideally, the focus ring | |
205 // should be part of the controls fading in, not appear at the end. | |
206 [[self view] setNextResponder:[[[self view] window] firstResponder]]; | |
207 [[[self view] window] makeFirstResponder:[self view]]; | |
208 | |
209 Parts parts; | |
210 // For this part of the animation, simply show the splash image. | |
211 parts.push_back(Part(autofill::kSplashDisplayDurationMs, ui::Tween::ZERO)); | |
212 // For this part of the animation, fade out the splash image. | |
213 parts.push_back( | |
214 Part(autofill::kSplashFadeOutDurationMs, ui::Tween::EASE_IN)); | |
215 // For this part of the animation, fade out |this| (fade in the dialog). | |
216 parts.push_back( | |
217 Part(autofill::kSplashFadeInDialogDurationMs, ui::Tween::EASE_OUT)); | |
218 fadeOutAnimation_.reset( | |
219 new ui::MultiAnimation(parts, | |
220 ui::MultiAnimation::GetDefaultTimerInterval())); | |
221 animationDelegate_.reset(new AnimationDelegateBridge(self)); | |
222 fadeOutAnimation_->set_delegate(animationDelegate_.get()); | |
223 fadeOutAnimation_->set_continuous(false); | |
224 fadeOutAnimation_->Start(); | |
225 } | |
226 | |
227 - (int)getHeightForWidth:(int) width { | |
228 // 0 means "no preference". Image-only overlays fit the container. | |
229 if ([messageStackView_ isHidden]) | |
230 return 0; | |
231 | |
232 // Overlays with text messages express a size preference. | |
233 return kOverlayImageBottomMargin + | |
234 [messageStackView_ getHeightForWidth:width] + | |
235 NSHeight([imageView_ frame]); | |
236 | |
237 return 0; | |
238 } | |
239 | |
240 - (NSSize)preferredSize { | |
241 NOTREACHED(); // Only implemented as part of AutofillLayout protocol. | |
242 return NSZeroSize; | |
243 } | |
244 | |
245 - (void)performLayout { | |
246 NSRect bounds = [view_ bounds]; | |
247 [childView_ setFrame:bounds]; | |
248 if ([messageStackView_ isHidden]) { | |
249 [imageView_ setFrame:bounds]; | |
250 return; | |
251 } | |
252 | |
253 int messageHeight = [messageStackView_ getHeightForWidth:NSWidth(bounds)]; | |
254 [messageStackView_ setFrame: | |
255 NSMakeRect(0, 0, NSWidth(bounds), messageHeight)]; | |
256 [messageStackView_ performLayout]; | |
257 | |
258 NSSize imageSize = [[imageView_ image] size]; | |
259 [imageView_ setFrame:NSMakeRect( | |
260 0, NSMaxY([messageStackView_ frame]) + kOverlayImageBottomMargin, | |
261 NSWidth(bounds), imageSize.height)]; | |
262 } | |
263 | |
264 - (void)animationProgressed:(const ui::Animation*) animation { | |
265 DCHECK_EQ(animation, fadeOutAnimation_.get()); | |
266 | |
267 // Fade out children in stage 1. | |
268 if (fadeOutAnimation_->current_part_index() == 1) { | |
269 [childView_ setAlphaValue:(1 - fadeOutAnimation_->GetCurrentValue())]; | |
270 } | |
271 | |
272 // Fade out background in stage 2(i.e. fade in what's behind |this|). | |
273 if (fadeOutAnimation_->current_part_index() == 2) { | |
274 [view_ setAlphaValue: (1 - fadeOutAnimation_->GetCurrentValue())]; | |
275 [childView_ setHidden:YES]; | |
276 } | |
277 | |
278 // If any fading was done, refresh display. | |
279 if (fadeOutAnimation_->current_part_index() != 0) { | |
280 [view_ setNeedsDisplay:YES]; | |
281 } | |
282 } | |
283 | |
284 - (void)animationEnded:(const ui::Animation*)animation { | |
285 DCHECK_EQ(animation, fadeOutAnimation_.get()); | |
286 [[self view] setHidden:YES]; | |
287 fadeOutAnimation_.reset(); | |
288 } | |
289 | |
290 @end | |
OLD | NEW |