Chromium Code Reviews| Index: chrome/browser/ui/cocoa/autofill/autofill_overlay_controller.mm |
| diff --git a/chrome/browser/ui/cocoa/autofill/autofill_overlay_controller.mm b/chrome/browser/ui/cocoa/autofill/autofill_overlay_controller.mm |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..64b794e6f9e57e62f50ab9b0e7733d71daf1bae7 |
| --- /dev/null |
| +++ b/chrome/browser/ui/cocoa/autofill/autofill_overlay_controller.mm |
| @@ -0,0 +1,290 @@ |
| +// Copyright 2013 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#import "chrome/browser/ui/cocoa/autofill/autofill_overlay_controller.h" |
| + |
| +#include "base/logging.h" |
| +#include "base/strings/sys_string_conversions.h" |
| +#include "chrome/browser/ui/autofill/autofill_dialog_types.h" |
| +#include "chrome/browser/ui/cocoa/autofill/autofill_dialog_constants.h" |
| +#include "skia/ext/skia_utils_mac.h" |
| +#include "ui/base/animation/animation_delegate.h" |
| +#include "ui/base/animation/multi_animation.h" |
| + |
| +namespace { |
| + |
| +// Spacing between lines of text in the overlay view. |
| +const int kOverlayTextInterlineSpacing = 10; |
| + |
| +// Spacing below image and above text messages in overlay view. |
| +const int kOverlayImageBottomMargin = 50; |
| + |
| +// TODO(groby): Unify colors with Views. |
| +// Slight shading for mouse hover and legal document background. |
| +SkColor kShadingColor = 0xfff2f2f2; |
| + |
| +// A border color for the legal document view. |
| +SkColor kSubtleBorderColor = 0xffdfdfdf; |
| + |
| +// Shorten a few long types. |
| +typedef ui::MultiAnimation::Part Part; |
| +typedef ui::MultiAnimation::Parts Parts; |
| + |
| +} // namespace |
| + |
| +// Bridges Objective C and C++ delegate interfaces. |
| +class AnimationDelegateBridge : public ui::AnimationDelegate { |
| + public: |
| + AnimationDelegateBridge(id<ChromiumAnimationDelegate>); |
| + |
| + protected: |
| + // AnimationDelegate implementation. |
| + virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE; |
| + virtual void AnimationEnded(const ui::Animation* animation) OVERRIDE; |
| + |
| + private: |
| + id<ChromiumAnimationDelegate> delegate_; // Not owned. Owns DelegateBridge. |
| +}; |
| + |
| +AnimationDelegateBridge::AnimationDelegateBridge( |
| + id<ChromiumAnimationDelegate> delegate) : delegate_(delegate) {} |
| + |
| +void AnimationDelegateBridge::AnimationProgressed( |
| + const ui::Animation* animation) { |
| + [delegate_ animationProgressed:animation]; |
| +} |
| + |
| +void AnimationDelegateBridge::AnimationEnded( |
| + const ui::Animation* animation) { |
| + [delegate_ animationEnded:animation]; |
| +} |
| + |
| + |
| +// An NSView encapsulating the message stack and its custom drawn elements. |
| +@interface AutofillMessageStackView : NSView<AutofillLayout> |
| +- (CGFloat)getHeightForWidth:(CGFloat)width; |
| +- (void)setMessages: |
| + (const std::vector<autofill::DialogOverlayString>&)messages; |
| +@end |
| + |
| + |
| +@implementation AutofillMessageStackView |
| + |
| +- (void)drawRect:(NSRect)dirtyRect { |
| + NSColor* shadingColor = gfx::SkColorToCalibratedNSColor(kShadingColor); |
| + NSColor* borderColor = gfx::SkColorToCalibratedNSColor(kSubtleBorderColor); |
| + |
| + CGFloat arrowHalfWidth = kArrowWidth / 2.0; |
| + NSRect bounds = [self bounds]; |
| + CGFloat y = NSMaxY(bounds) - kArrowHeight; |
| + |
| + NSBezierPath* arrow = [NSBezierPath bezierPath]; |
| + // Note that we purposely draw slightly outside of |bounds| so that the |
| + // stroke is hidden on the sides. |
| + [arrow moveToPoint:NSMakePoint(NSMinX(bounds) - 1.0, y)]; |
| + [arrow relativeLineToPoint:NSMakePoint(NSMidX(bounds) - arrowHalfWidth, 0)]; |
| + [arrow relativeLineToPoint:NSMakePoint(arrowHalfWidth, kArrowHeight)]; |
| + [arrow relativeLineToPoint:NSMakePoint(arrowHalfWidth, -kArrowHeight)]; |
| + [arrow lineToPoint:NSMakePoint(NSMaxX(bounds) + 1.0, y)]; |
| + [arrow lineToPoint:NSMakePoint(NSMaxX(bounds) + 1.0, NSMinY(bounds) - 1.0)]; |
| + [arrow lineToPoint:NSMakePoint(NSMinX(bounds) - 1.0, NSMinY(bounds) - 1.0)]; |
| + [arrow closePath]; |
| + |
| + [shadingColor setFill]; |
| + [arrow fill]; |
| + [borderColor setStroke]; |
| + [arrow stroke]; |
| + |
| + [super drawRect:dirtyRect]; |
| +} |
| + |
| +- (CGFloat)getHeightForWidth:(CGFloat)width { |
| + CGFloat height = kOverlayTextInterlineSpacing; |
| + for (NSTextView* label in [self subviews]) { |
| + height += NSHeight([label frame]); |
| + height += kOverlayTextInterlineSpacing; |
| + } |
| + return height + kArrowHeight; |
| +} |
| + |
| +- (void)setMessages: |
| + (const std::vector<autofill::DialogOverlayString>&) messages { |
| + // We probably want to look at other multi-line messages somewhere. |
| + base::scoped_nsobject<NSMutableArray> labels( |
| + [[NSMutableArray alloc] initWithCapacity:messages.size()]); |
| + for (size_t i = 0; i < messages.size(); ++i) { |
| + base::scoped_nsobject<NSTextField> label( |
| + [[NSTextField alloc] initWithFrame:NSZeroRect]); |
| + |
| + NSFont* labelFont = messages[i].font.GetNativeFont(); |
| + [label setEditable:NO]; |
| + [label setBordered:NO]; |
| + [label setDrawsBackground:NO]; |
| + [label setFont:labelFont]; |
| + [label setStringValue:base::SysUTF16ToNSString(messages[i].text)]; |
| + [label setTextColor:gfx::SkColorToDeviceNSColor(messages[i].text_color)]; |
| + DCHECK(messages[i].alignment == gfx::ALIGN_CENTER); |
| + [label setAlignment:NSCenterTextAlignment]; |
| + [label sizeToFit]; |
| + |
| + [labels addObject:label]; |
| + } |
| + [self setSubviews:labels]; |
| + [self setHidden:([labels count] == 0)]; |
| +} |
| + |
| +- (void)performLayout { |
| + CGFloat y = |
| + NSMaxY([self bounds]) - kArrowHeight - kOverlayTextInterlineSpacing; |
| + for (NSTextView* label in [self subviews]) { |
| + CGFloat labelHeight = NSHeight([label frame]); |
| + [label setFrame:NSMakeRect(0, y - labelHeight, |
| + NSWidth([self bounds]), labelHeight)]; |
| + y = NSMinY([label frame]) - kOverlayTextInterlineSpacing; |
| + } |
| + DCHECK_GT(0.0, y); |
| +} |
| + |
| +- (NSSize)preferredSize { |
| + NOTREACHED(); |
| + return NSZeroSize; |
| +} |
| + |
| +@end |
| + |
| + |
| +@implementation AutofillOverlayController |
| + |
| +- (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.
|
| + if (self = [super init]) { |
| + view_.reset([[NSBox alloc] initWithFrame:NSZeroRect]); |
| + [view_ setBoxType:NSBoxCustom]; |
| + [view_ setBorderType:NSNoBorder]; |
| + [view_ setContentViewMargins:NSZeroSize]; |
| + [view_ setTitlePosition:NSNoTitle]; |
| + childView_.reset([[NSView alloc] initWithFrame:NSZeroRect]); |
| + messageStackView_.reset( |
| + [[AutofillMessageStackView alloc] initWithFrame:NSZeroRect]); |
| + imageView_.reset([[NSImageView alloc] initWithFrame:NSZeroRect]); |
| + [imageView_ setImageAlignment:NSImageAlignCenter]; |
| + [childView_ setSubviews:@[messageStackView_, imageView_]]; |
| + [view_ addSubview:childView_]; |
| + |
| + [self setView:view_]; |
| + } |
| + return self; |
| +} |
| + |
| +- (void)setState:(const autofill::DialogOverlayState&)state { |
| + // Don't update anything if we're still fading out the old state. |
| + if (fadeOutAnimation_) |
| + return; |
| + |
| + if (state.image.IsEmpty()) { |
| + [view_ setHidden:YES]; |
| + return; |
| + } |
| + |
| + [view_ setFillColor:[[view_ window] backgroundColor]]; |
| + [view_ setAlphaValue:1]; |
| + [childView_ setAlphaValue:1]; |
| + [imageView_ setImage:state.image.ToNSImage()]; |
| + [messageStackView_ setMessages:state.strings]; |
| + [childView_ setHidden:NO]; |
| + [view_ setHidden:NO]; |
| + |
| + id delegate = [[[self view] window] windowController]; |
| + if ([delegate respondsToSelector:@selector(requestRelayout)]) |
| + [delegate performSelector:@selector(requestRelayout)]; |
| +} |
| + |
| +- (void)beginFadeOut { |
| + // Remove first responders, since focus rings show on top of overlay view. |
| + // TODO(groby): Figure out to do that less hacky. Ideally, the focus ring |
| + // should be part of the controls fading in, not appear at the end. |
| + [[self view] setNextResponder:[[[self view] window] firstResponder]]; |
| + [[[self view] window] makeFirstResponder:[self view]]; |
| + |
| + Parts parts; |
| + // For this part of the animation, simply show the splash image. |
| + parts.push_back(Part(autofill::kSplashDisplayDurationMs, ui::Tween::ZERO)); |
| + // For this part of the animation, fade out the splash image. |
| + parts.push_back( |
| + Part(autofill::kSplashFadeOutDurationMs, ui::Tween::EASE_IN)); |
| + // For this part of the animation, fade out |this| (fade in the dialog). |
| + parts.push_back( |
| + Part(autofill::kSplashFadeInDialogDurationMs, ui::Tween::EASE_OUT)); |
| + fadeOutAnimation_.reset( |
| + new ui::MultiAnimation(parts, |
| + ui::MultiAnimation::GetDefaultTimerInterval())); |
| + animationDelegate_.reset(new AnimationDelegateBridge(self)); |
| + fadeOutAnimation_->set_delegate(animationDelegate_.get()); |
| + fadeOutAnimation_->set_continuous(false); |
| + fadeOutAnimation_->Start(); |
| +} |
| + |
| +- (int)getHeightForWidth:(int) width { |
| + // 0 means "no preference". Image-only overlays fit the container. |
| + if ([messageStackView_ isHidden]) |
| + return 0; |
| + |
| + // Overlays with text messages express a size preference. |
| + return kOverlayImageBottomMargin + |
| + [messageStackView_ getHeightForWidth:width] + |
| + NSHeight([imageView_ frame]); |
| + |
| + return 0; |
| +} |
| + |
| +- (NSSize)preferredSize { |
| + NOTREACHED(); // Only implemented as part of AutofillLayout protocol. |
| + return NSZeroSize; |
| +} |
| + |
| +- (void)performLayout { |
| + NSRect bounds = [view_ bounds]; |
| + [childView_ setFrame:bounds]; |
| + if ([messageStackView_ isHidden]) { |
| + [imageView_ setFrame:bounds]; |
| + return; |
| + } |
| + |
| + int messageHeight = [messageStackView_ getHeightForWidth:NSWidth(bounds)]; |
| + [messageStackView_ setFrame: |
| + NSMakeRect(0, 0, NSWidth(bounds), messageHeight)]; |
| + [messageStackView_ performLayout]; |
| + |
| + NSSize imageSize = [[imageView_ image] size]; |
| + [imageView_ setFrame:NSMakeRect( |
| + 0, NSMaxY([messageStackView_ frame]) + kOverlayImageBottomMargin, |
| + NSWidth(bounds), imageSize.height)]; |
| +} |
| + |
| +- (void)animationProgressed:(const ui::Animation*) animation { |
| + DCHECK_EQ(animation, fadeOutAnimation_.get()); |
| + |
| + // Fade out children in stage 1. |
| + if (fadeOutAnimation_->current_part_index() == 1) { |
| + [childView_ setAlphaValue:(1 - fadeOutAnimation_->GetCurrentValue())]; |
| + } |
| + |
| + // Fade out background in stage 2(i.e. fade in what's behind |this|). |
| + if (fadeOutAnimation_->current_part_index() == 2) { |
| + [view_ setAlphaValue: (1 - fadeOutAnimation_->GetCurrentValue())]; |
| + [childView_ setHidden:YES]; |
| + } |
| + |
| + // If any fading was done, refresh display. |
| + if (fadeOutAnimation_->current_part_index() != 0) { |
| + [view_ setNeedsDisplay:YES]; |
| + } |
| +} |
| + |
| +- (void)animationEnded:(const ui::Animation*)animation { |
| + DCHECK_EQ(animation, fadeOutAnimation_.get()); |
| + [[self view] setHidden:YES]; |
| + fadeOutAnimation_.reset(); |
| +} |
| + |
| +@end |