blob: ed253a063dad61bfc6e2f53ec7fd9037fb2e33fc [file] [log] [blame]
// 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_loading_shield_controller.h"
#include <cmath>
#include "base/strings/sys_string_conversions.h"
#include "chrome/browser/ui/autofill/autofill_dialog_view_delegate.h"
#include "chrome/browser/ui/autofill/loading_animation.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/animation/animation_delegate.h"
namespace {
// Horizontal spacing between the animated dots.
// Using a negative spacing creates an ellipsis-like effect.
// TODO(isherman): Consider using the recipe below instead:
// Create NSBezierPath
// -[NSBezierPath appendBezierPathWithGlyph:inFont:]
// -[NSBezierPath bounds]
const CGFloat kDotsHorizontalPadding = -6;
} // namespace
// A C++ bridge class for driving the animation.
class AutofillLoadingAnimationBridge : public gfx::AnimationDelegate {
public:
AutofillLoadingAnimationBridge(AutofillLoadingShieldController* controller,
int font_size)
: animation_(this, font_size),
controller_(controller) {}
virtual ~AutofillLoadingAnimationBridge() {}
// gfx::AnimationDelegate implementation.
virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE {
DCHECK_EQ(animation, &animation_);
[controller_ relayoutDotsForSteppedAnimation:animation_];
}
autofill::LoadingAnimation* animation() { return &animation_; }
private:
autofill::LoadingAnimation animation_;
AutofillLoadingShieldController* const controller_; // weak, owns |this|
};
@implementation AutofillLoadingShieldController
- (id)initWithDelegate:(autofill::AutofillDialogViewDelegate*)delegate {
if (self = [super initWithNibName:nil bundle:nil]) {
delegate_ = delegate;
gfx::Font font = ui::ResourceBundle::GetSharedInstance().
GetFont(ui::ResourceBundle::BaseFont).DeriveFont(8);
NSFont* native_font = font.GetNativeFont();
animationDriver_.reset(
new AutofillLoadingAnimationBridge(self, font.GetHeight()));
base::scoped_nsobject<NSBox> view([[NSBox alloc] initWithFrame:NSZeroRect]);
[view setBoxType:NSBoxCustom];
[view setBorderType:NSNoBorder];
[view setContentViewMargins:NSZeroSize];
[view setTitlePosition:NSNoTitle];
message_.reset([[NSTextField alloc] initWithFrame:NSZeroRect]);
[message_ setFont:native_font];
[message_ setEditable:NO];
[message_ setBordered:NO];
[message_ setDrawsBackground:NO];
[view addSubview:message_];
dots_.reset([[NSArray alloc] initWithArray:@[
[[NSTextField alloc] initWithFrame:NSZeroRect],
[[NSTextField alloc] initWithFrame:NSZeroRect],
[[NSTextField alloc] initWithFrame:NSZeroRect] ]]);
NSInteger tag = 0;
for (NSTextField* dot in dots_.get()) {
[dot setFont:native_font];
[dot setEditable:NO];
[dot setBordered:NO];
[dot setDrawsBackground:NO];
[dot setStringValue:@"."];
[dot setTag:tag];
[dot sizeToFit];
[view addSubview:dot];
++tag;
}
[self setView:view];
}
return self;
}
- (void)update {
NSString* newMessage = @"";
if (delegate_->ShouldShowSpinner())
newMessage = base::SysUTF16ToNSString(delegate_->SpinnerText());
if ([newMessage isEqualToString:[message_ stringValue]])
return;
[message_ setStringValue:newMessage];
[message_ sizeToFit];
if ([newMessage length] > 0) {
[[self view] setHidden:NO];
animationDriver_->animation()->Start();
} else {
[[self view] setHidden:YES];
animationDriver_->animation()->Reset();
}
NSWindowController* delegate = [[[self view] window] windowController];
if ([delegate respondsToSelector:@selector(requestRelayout)])
[delegate performSelector:@selector(requestRelayout)];
}
- (NSSize)preferredSize {
NOTREACHED(); // Only implemented as part of AutofillLayout protocol.
return NSZeroSize;
}
- (void)performLayout {
if ([[self view] isHidden])
return;
NSRect bounds = [[self view] bounds];
NSRect messageFrame = [message_ frame];
NSSize size = messageFrame.size;
for (NSView* dot in dots_.get()) {
size.width += NSWidth([dot frame]) + kDotsHorizontalPadding;
size.height = std::max(size.height, NSHeight([dot frame]));
}
// The message + dots should be centered in the view.
messageFrame.origin.x = std::ceil((NSWidth(bounds) - size.width) / 2.0);
messageFrame.origin.y = std::ceil((NSHeight(bounds) - size.height) / 2.0);
[message_ setFrameOrigin:messageFrame.origin];
NSView* previousView = message_;
for (NSView* dot in dots_.get()) {
NSPoint dotFrameOrigin =
NSMakePoint(NSMaxX([previousView frame]) + kDotsHorizontalPadding,
messageFrame.origin.y);
[dot setFrameOrigin:dotFrameOrigin];
previousView = dot;
}
}
- (void)relayoutDotsForSteppedAnimation:
(const autofill::LoadingAnimation&)animation {
for (NSView* dot in dots_.get()) {
NSPoint origin = [dot frame].origin;
origin.y = [message_ frame].origin.y -
animation.GetCurrentValueForDot([dot tag]);
[dot setFrameOrigin:origin];
}
}
@end