Chromium Code Reviews| 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 |