blob: 31d6879962dc1a632919e2a00589805ee560bcf5 [file] [log] [blame]
// Copyright (c) 2012 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.
#include "chrome/browser/download/download_started_animation.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_view.h"
#include "grit/theme_resources.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/animation/linear_animation.h"
#include "ui/gfx/rect.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/widget/widget.h"
using content::WebContents;
// How long to spend moving downwards and fading out after waiting.
static const int kMoveTimeMs = 600;
// The animation framerate.
static const int kFrameRateHz = 60;
// What fraction of the frame height to move downward from the frame center.
// Note that setting this greater than 0.5 will mean moving past the bottom of
// the frame.
static const double kMoveFraction = 1.0 / 3.0;
namespace {
// DownloadStartAnimation creates an animation (which begins running
// immediately) that animates an image downward from the center of the frame
// provided on the constructor, while simultaneously fading it out. To use,
// simply call "new DownloadStartAnimation"; the class cleans itself up when it
// finishes animating.
class DownloadStartedAnimationWin : public gfx::LinearAnimation,
public content::NotificationObserver,
public views::ImageView {
public:
explicit DownloadStartedAnimationWin(WebContents* web_contents);
private:
// Move the animation to wherever it should currently be.
void Reposition();
// Shut down the animation cleanly.
void Close();
// Animation
virtual void AnimateToState(double state) OVERRIDE;
// content::NotificationObserver
virtual void Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) OVERRIDE;
// We use a HWND for the popup so that it may float above any HWNDs in our UI.
views::Widget* popup_;
// The content area holding us.
WebContents* web_contents_;
// The content area at the start of the animation. We store this so that the
// download shelf's resizing of the content area doesn't cause the animation
// to move around. This means that once started, the animation won't move
// with the parent window, but it's so fast that this shouldn't cause too
// much heartbreak.
gfx::Rect web_contents_bounds_;
// A scoped container for notification registries.
content::NotificationRegistrar registrar_;
DISALLOW_COPY_AND_ASSIGN(DownloadStartedAnimationWin);
};
DownloadStartedAnimationWin::DownloadStartedAnimationWin(
WebContents* web_contents)
: gfx::LinearAnimation(kMoveTimeMs, kFrameRateHz, NULL),
popup_(NULL),
web_contents_(web_contents) {
static gfx::ImageSkia* kDownloadImage = NULL;
if (!kDownloadImage) {
kDownloadImage = ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
IDR_DOWNLOAD_ANIMATION_BEGIN);
}
// If we're too small to show the download image, then don't bother -
// the shelf will be enough.
web_contents_->GetView()->GetContainerBounds(&web_contents_bounds_);
if (web_contents_bounds_.height() < kDownloadImage->height())
return;
registrar_.Add(
this,
content::NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED,
content::Source<WebContents>(web_contents_));
registrar_.Add(
this,
content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
content::Source<WebContents>(web_contents_));
SetImage(kDownloadImage);
popup_ = new views::Widget;
views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
params.accept_events = false;
params.parent = web_contents_->GetView()->GetNativeView();
popup_->Init(params);
popup_->SetOpacity(0x00);
popup_->SetContentsView(this);
Reposition();
popup_->Show();
Start();
}
void DownloadStartedAnimationWin::Reposition() {
if (!web_contents_)
return;
// Align the image with the bottom left of the web contents (so that it
// points to the newly created download).
gfx::Size size = GetPreferredSize();
int x = base::i18n::IsRTL() ?
web_contents_bounds_.right() - size.width() : web_contents_bounds_.x();
popup_->SetBounds(gfx::Rect(
x,
static_cast<int>(web_contents_bounds_.bottom() -
size.height() - size.height() * (1 - GetCurrentValue())),
size.width(),
size.height()));
}
void DownloadStartedAnimationWin::Close() {
if (!web_contents_)
return;
registrar_.Remove(
this,
content::NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED,
content::Source<WebContents>(web_contents_));
registrar_.Remove(
this,
content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
content::Source<WebContents>(web_contents_));
web_contents_ = NULL;
popup_->Close();
}
void DownloadStartedAnimationWin::AnimateToState(double state) {
if (state >= 1.0) {
Close();
} else {
Reposition();
// Start at zero, peak halfway and end at zero.
double opacity = std::min(1.0 - pow(GetCurrentValue() - 0.5, 2) * 4.0,
static_cast<double>(1.0));
popup_->SetOpacity(static_cast<unsigned char>(opacity * 255.0));
}
}
void DownloadStartedAnimationWin::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
if (type == content::NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED) {
bool visible = *content::Details<bool>(details).ptr();
if (visible)
return;
}
Close();
}
} // namespace
// static
void DownloadStartedAnimation::Show(WebContents* web_contents) {
// The animation will delete itself when it's finished or when the tab
// contents is hidden or destroyed.
new DownloadStartedAnimationWin(web_contents);
}