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.
5 #import "chrome/browser/ui/cocoa/autofill/autofill_loading_shield_controller.h"
9 #include "base/strings/sys_string_conversions.h"
10 #include "chrome/browser/ui/autofill/autofill_dialog_view_delegate.h"
11 #include "chrome/browser/ui/autofill/loading_animation.h"
12 #include "ui/base/resource/resource_bundle.h"
13 #include "ui/gfx/animation/animation_delegate.h"
14 #include "ui/gfx/font_list.h"
18 // Horizontal spacing between the animated dots.
19 // Using a negative spacing creates an ellipsis-like effect.
20 // TODO(isherman): Consider using the recipe below instead:
21 // Create NSBezierPath
22 // -[NSBezierPath appendBezierPathWithGlyph:inFont:]
23 // -[NSBezierPath bounds]
24 const CGFloat kDotsHorizontalPadding = -6;
29 // A C++ bridge class for driving the animation.
30 class AutofillLoadingAnimationBridge : public gfx::AnimationDelegate {
32 AutofillLoadingAnimationBridge(AutofillLoadingShieldController* controller,
34 : animation_(this, font_size),
35 controller_(controller) {}
36 ~AutofillLoadingAnimationBridge() override {}
38 // gfx::AnimationDelegate implementation.
39 void AnimationProgressed(const gfx::Animation* animation) override {
40 DCHECK_EQ(animation, &animation_);
41 [controller_ relayoutDotsForSteppedAnimation:animation_];
44 autofill::LoadingAnimation* animation() { return &animation_; }
47 autofill::LoadingAnimation animation_;
48 AutofillLoadingShieldController* const controller_; // weak, owns |this|
52 @implementation AutofillLoadingShieldController
54 - (id)initWithDelegate:(autofill::AutofillDialogViewDelegate*)delegate {
55 if (self = [super initWithNibName:nil bundle:nil]) {
58 const gfx::FontList& font_list =
59 ui::ResourceBundle::GetSharedInstance().GetFontList(
60 ui::ResourceBundle::LargeFont);
61 NSFont* native_font = font_list.GetPrimaryFont().GetNativeFont();
62 animationDriver_.reset(
63 new AutofillLoadingAnimationBridge(self, font_list.GetHeight()));
65 base::scoped_nsobject<NSBox> view([[NSBox alloc] initWithFrame:NSZeroRect]);
66 [view setBoxType:NSBoxCustom];
67 [view setBorderType:NSNoBorder];
68 [view setContentViewMargins:NSZeroSize];
69 [view setTitlePosition:NSNoTitle];
71 message_.reset([[NSTextField alloc] initWithFrame:NSZeroRect]);
72 [message_ setFont:native_font];
73 [message_ setEditable:NO];
74 [message_ setBordered:NO];
75 [message_ setDrawsBackground:NO];
76 [view addSubview:message_];
78 dots_.reset([[NSArray alloc] initWithArray:@[
79 [[NSTextField alloc] initWithFrame:NSZeroRect],
80 [[NSTextField alloc] initWithFrame:NSZeroRect],
81 [[NSTextField alloc] initWithFrame:NSZeroRect] ]]);
83 for (NSTextField* dot in dots_.get()) {
84 [dot setFont:native_font];
87 [dot setDrawsBackground:NO];
88 [dot setStringValue:@"."];
91 [view addSubview:dot];
102 NSString* newMessage = @"";
103 if (delegate_->ShouldShowSpinner())
104 newMessage = base::SysUTF16ToNSString(delegate_->SpinnerText());
106 if ([newMessage isEqualToString:[message_ stringValue]])
109 [message_ setStringValue:newMessage];
110 [message_ sizeToFit];
112 if ([newMessage length] > 0) {
113 [[self view] setHidden:NO];
114 animationDriver_->animation()->Start();
116 [[self view] setHidden:YES];
117 animationDriver_->animation()->Reset();
120 NSWindowController* delegate = [[[self view] window] windowController];
121 if ([delegate respondsToSelector:@selector(requestRelayout)])
122 [delegate performSelector:@selector(requestRelayout)];
125 - (NSSize)preferredSize {
126 NOTREACHED(); // Only implemented as part of AutofillLayout protocol.
130 - (void)performLayout {
131 if ([[self view] isHidden])
134 NSRect bounds = [[self view] bounds];
135 NSRect messageFrame = [message_ frame];
137 NSSize size = messageFrame.size;
138 for (NSView* dot in dots_.get()) {
139 size.width += NSWidth([dot frame]) + kDotsHorizontalPadding;
140 size.height = std::max(size.height, NSHeight([dot frame]));
143 // The message + dots should be centered in the view.
144 messageFrame.origin.x = std::ceil((NSWidth(bounds) - size.width) / 2.0);
145 messageFrame.origin.y = std::ceil((NSHeight(bounds) - size.height) / 2.0);
146 [message_ setFrameOrigin:messageFrame.origin];
148 NSView* previousView = message_;
149 for (NSView* dot in dots_.get()) {
150 NSPoint dotFrameOrigin =
151 NSMakePoint(NSMaxX([previousView frame]) + kDotsHorizontalPadding,
152 messageFrame.origin.y);
153 [dot setFrameOrigin:dotFrameOrigin];
159 - (void)relayoutDotsForSteppedAnimation:
160 (const autofill::LoadingAnimation&)animation {
161 for (NSView* dot in dots_.get()) {
162 NSPoint origin = [dot frame].origin;
163 origin.y = [message_ frame].origin.y -
164 animation.GetCurrentValueForDot([dot tag]);
165 [dot setFrameOrigin:origin];