1 // Copyright (c) 2012 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. 4 5 #include "chrome/browser/download/download_started_animation.h" 6 7 #include <math.h> 8 9 #include <gtk/gtk.h> 10 11 #include "base/message_loop/message_loop.h" 12 #include "content/public/browser/notification_details.h" 13 #include "content/public/browser/notification_observer.h" 14 #include "content/public/browser/notification_registrar.h" 15 #include "content/public/browser/notification_source.h" 16 #include "content/public/browser/notification_types.h" 17 #include "content/public/browser/web_contents.h" 18 #include "content/public/browser/web_contents_view.h" 19 #include "grit/theme_resources.h" 20 #include "ui/base/animation/linear_animation.h" 21 #include "ui/base/resource/resource_bundle.h" 22 #include "ui/gfx/image/image.h" 23 #include "ui/gfx/rect.h" 24 25 using content::WebContents; 26 27 namespace { 28 29 // How long to spend moving downwards and fading out after waiting. 30 const int kMoveTimeMs = 600; 31 32 // The animation framerate. 33 const int kFrameRateHz = 60; 34 35 // What fraction of the frame height to move downward from the frame center. 36 // Note that setting this greater than 0.5 will mean moving past the bottom of 37 // the frame. 38 const double kMoveFraction = 1.0 / 3.0; 39 40 class DownloadStartedAnimationGtk : public ui::LinearAnimation, 41 public content::NotificationObserver { 42 public: 43 explicit DownloadStartedAnimationGtk(WebContents* web_contents); 44 45 // DownloadStartedAnimation will delete itself, but this is public so 46 // that we can use DeleteSoon(). 47 virtual ~DownloadStartedAnimationGtk(); 48 49 private: 50 // Move the arrow to wherever it should currently be. 51 void Reposition(); 52 53 // Shut down cleanly. 54 void Close(); 55 56 // Animation implementation. 57 virtual void AnimateToState(double state) OVERRIDE; 58 59 // NotificationObserver 60 virtual void Observe(int type, 61 const content::NotificationSource& source, 62 const content::NotificationDetails& details) OVERRIDE; 63 64 // The top level window that floats over the browser and displays the 65 // image. 66 GtkWidget* popup_; 67 68 // Dimensions of the image. 69 int width_; 70 int height_; 71 72 // The content area holding us. 73 WebContents* web_contents_; 74 75 // The content area at the start of the animation. We store this so that the 76 // download shelf's resizing of the content area doesn't cause the animation 77 // to move around. This means that once started, the animation won't move 78 // with the parent window, but it's so fast that this shouldn't cause too 79 // much heartbreak. 80 gfx::Rect web_contents_bounds_; 81 82 // A scoped container for notification registries. 83 content::NotificationRegistrar registrar_; 84 85 DISALLOW_COPY_AND_ASSIGN(DownloadStartedAnimationGtk); 86 }; 87 88 DownloadStartedAnimationGtk::DownloadStartedAnimationGtk( 89 WebContents* web_contents) 90 : ui::LinearAnimation(kMoveTimeMs, kFrameRateHz, NULL), 91 popup_(NULL), 92 web_contents_(web_contents) { 93 static GdkPixbuf* kDownloadImage = NULL; 94 if (!kDownloadImage) { 95 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 96 kDownloadImage = rb.GetNativeImageNamed( 97 IDR_DOWNLOAD_ANIMATION_BEGIN).ToGdkPixbuf(); 98 } 99 100 width_ = gdk_pixbuf_get_width(kDownloadImage); 101 height_ = gdk_pixbuf_get_height(kDownloadImage); 102 103 // If we're too small to show the download image, then don't bother - 104 // the shelf will be enough. 105 web_contents_->GetView()->GetContainerBounds(&web_contents_bounds_); 106 if (web_contents_bounds_.height() < height_) 107 return; 108 109 registrar_.Add( 110 this, 111 content::NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED, 112 content::Source<WebContents>(web_contents_)); 113 registrar_.Add( 114 this, 115 content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 116 content::Source<WebContents>(web_contents_)); 117 118 // TODO(estade): don't show up on the wrong virtual desktop. 119 120 popup_ = gtk_window_new(GTK_WINDOW_POPUP); 121 GtkWidget* image = gtk_image_new_from_pixbuf(kDownloadImage); 122 gtk_container_add(GTK_CONTAINER(popup_), image); 123 124 // Set the shape of the window to that of the arrow. Areas with 125 // opacity less than 0xff (i.e. <100% opacity) will be transparent. 126 GdkBitmap* mask = gdk_pixmap_new(NULL, width_, height_, 1); 127 gdk_pixbuf_render_threshold_alpha(kDownloadImage, mask, 128 0, 0, 129 0, 0, -1, -1, 130 0xff); 131 gtk_widget_shape_combine_mask(popup_, mask, 0, 0); 132 g_object_unref(mask); 133 134 Reposition(); 135 gtk_widget_show_all(popup_); 136 // Make sure our window has focus, is brought to the top, etc. 137 gtk_window_present(GTK_WINDOW(popup_)); 138 139 Start(); 140 } 141 142 DownloadStartedAnimationGtk::~DownloadStartedAnimationGtk() { 143 } 144 145 void DownloadStartedAnimationGtk::Reposition() { 146 if (!web_contents_) 147 return; 148 149 DCHECK(popup_); 150 151 // Align the image with the bottom left of the web contents (so that it 152 // points to the newly created download). 153 gtk_window_move(GTK_WINDOW(popup_), 154 web_contents_bounds_.x(), 155 static_cast<int>(web_contents_bounds_.bottom() - 156 height_ - height_ * (1 - GetCurrentValue()))); 157 } 158 159 void DownloadStartedAnimationGtk::Close() { 160 if (!web_contents_) 161 return; 162 163 DCHECK(popup_); 164 165 registrar_.Remove( 166 this, 167 content::NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED, 168 content::Source<WebContents>(web_contents_)); 169 registrar_.Remove( 170 this, 171 content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 172 content::Source<WebContents>(web_contents_)); 173 174 web_contents_ = NULL; 175 gtk_widget_destroy(popup_); 176 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); 177 } 178 179 void DownloadStartedAnimationGtk::AnimateToState(double state) { 180 if (!web_contents_) 181 return; 182 183 if (state >= 1.0) { 184 Close(); 185 } else { 186 Reposition(); 187 188 // Start at zero, peak halfway and end at zero. 189 double opacity = std::min(1.0 - pow(GetCurrentValue() - 0.5, 2) * 4.0, 190 static_cast<double>(1.0)); 191 192 // This only works when there's a compositing manager running. Oh well. 193 gtk_window_set_opacity(GTK_WINDOW(popup_), opacity); 194 } 195 } 196 197 void DownloadStartedAnimationGtk::Observe( 198 int type, 199 const content::NotificationSource& source, 200 const content::NotificationDetails& details) { 201 if (type == content::NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED) { 202 bool visible = *content::Details<bool>(details).ptr(); 203 if (visible) 204 return; 205 } 206 Close(); 207 } 208 209 } // namespace 210 211 // static 212 void DownloadStartedAnimation::Show(WebContents* web_contents) { 213 // The animation will delete itself. 214 new DownloadStartedAnimationGtk(web_contents); 215 } 216