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