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 |