1 // Copyright (c) 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. 4 5 #include "chrome/browser/ui/tabs/tab_audio_indicator.h" 6 7 #include "grit/theme_resources.h" 8 #include "ui/base/animation/animation_container.h" 9 #include "ui/base/animation/linear_animation.h" 10 #include "ui/base/resource/resource_bundle.h" 11 #include "ui/gfx/canvas.h" 12 #include "ui/gfx/rect.h" 13 #include "ui/gfx/skia_util.h" 14 15 namespace { 16 17 // The number of columns to draw for the equalizer graphic. 18 const size_t kEqualizerColumnCount = 3; 19 20 // The equalizer cycles between these frames. An equalizer frame is 2 columns 21 // where each column ranges from 0 to 4. 22 const size_t kEqualizerFrames[][kEqualizerColumnCount] = { 23 { 1, 1, 1 }, 24 { 1, 2, 2 }, 25 { 2, 2, 3 }, 26 { 3, 3, 2 }, 27 { 3, 2, 1 }, 28 { 2, 1, 2 }, 29 { 1, 2, 3 }, 30 { 2, 1, 2 }, 31 { 3, 2, 1 }, 32 { 3, 2, 1 }, 33 { 2, 3, 2 }, 34 { 1, 2, 3 }, 35 { 2, 1, 2 }, 36 }; 37 38 // The space between equalizer levels. 39 const int kEqualizerColumnPadding = 1; 40 41 // The duration of each equalizer frame. 42 const size_t kAnimationCycleDurationMs = 250; 43 44 // The duration of the "ending" animation once audio stops playing. 45 const size_t kAnimationEndingDurationMs = 1000; 46 47 // Target frames per second. In reality fewer frames are drawn because the 48 // equalizer levels change slowly. 49 const int kFPS = 15; 50 51 } // namespace 52 53 TabAudioIndicator::TabAudioIndicator(Delegate* delegate) 54 : delegate_(delegate), 55 frame_index_(0), 56 state_(STATE_NOT_ANIMATING) { 57 } 58 59 TabAudioIndicator::~TabAudioIndicator() { 60 } 61 62 void TabAudioIndicator::SetAnimationContainer( 63 ui::AnimationContainer* animation_container) { 64 animation_container_ = animation_container; 65 } 66 67 void TabAudioIndicator::SetIsPlayingAudio(bool is_playing_audio) { 68 if (is_playing_audio && state_ != STATE_ANIMATING) { 69 state_ = STATE_ANIMATING; 70 animation_.reset( 71 new ui::LinearAnimation(kAnimationCycleDurationMs, kFPS, this)); 72 animation_->SetContainer(animation_container_.get()); 73 animation_->Start(); 74 } else if (!is_playing_audio && state_ == STATE_ANIMATING) { 75 state_ = STATE_ANIMATION_ENDING; 76 animation_.reset( 77 new ui::LinearAnimation(kAnimationEndingDurationMs, kFPS, this)); 78 animation_->SetContainer(animation_container_.get()); 79 animation_->Start(); 80 } 81 } 82 83 bool TabAudioIndicator::IsAnimating() { 84 return state_ != STATE_NOT_ANIMATING; 85 } 86 87 void TabAudioIndicator::Paint(gfx::Canvas* canvas, const gfx::Rect& rect) { 88 canvas->Save(); 89 canvas->ClipRect(rect); 90 91 // Draw 3 equalizer columns. |IDR_AUDIO_EQUALIZER_COLUMN| is a column of the 92 // equalizer with 4 levels. The current level is between 0 and 4 so the 93 // image is shifted down and then drawn. 94 if (state_ != STATE_NOT_ANIMATING) { 95 ui::ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 96 gfx::ImageSkia* image(rb.GetImageSkiaNamed(IDR_AUDIO_EQUALIZER_COLUMN)); 97 int x = rect.right(); 98 std::vector<int> levels = GetCurrentEqualizerLevels(); 99 for (int i = levels.size() - 1; i >= 0; --i) { 100 x -= image->width(); 101 if (levels[i] == 0) 102 continue; 103 104 // Shift the image down by the level. 105 int y = rect.bottom() - levels[i] * 2; 106 canvas->DrawImageInt(*image, x, y); 107 108 // Clip the equalizer column so the favicon doesn't obscure it. 109 gfx::Rect equalizer_rect(x, y, image->width(), image->height()); 110 canvas->sk_canvas()->clipRect( 111 gfx::RectToSkRect(equalizer_rect), SkRegion::kDifference_Op); 112 113 // Padding is baked into both sides of the icons so overlap the images. 114 x += kEqualizerColumnPadding; 115 } 116 117 // Cache the levels that were just drawn. This is used to prevent 118 // unnecessary drawing when animation progress doesn't result in equalizer 119 // levels changing. 120 last_displayed_equalizer_levels_ = levels; 121 } 122 123 if (!favicon_.isNull()) { 124 int dst_x = rect.x() - (favicon_.width() - rect.width()) / 2; 125 int dst_y = rect.y() - (favicon_.height()- rect.height()) / 2; 126 canvas->DrawImageInt(favicon_, dst_x, dst_y); 127 } 128 129 canvas->Restore(); 130 } 131 132 void TabAudioIndicator::AnimationProgressed(const ui::Animation* animation) { 133 std::vector<int> levels = GetCurrentEqualizerLevels(); 134 if (last_displayed_equalizer_levels_ != levels) 135 delegate_->ScheduleAudioIndicatorPaint(); 136 } 137 138 void TabAudioIndicator::AnimationEnded(const ui::Animation* animation) { 139 if (state_ == STATE_ANIMATING) { 140 // The current equalizer frame animation has finished. Start animating the 141 // next frame. 142 frame_index_ = (frame_index_ + 1) % arraysize(kEqualizerFrames); 143 animation_->Start(); 144 } else if (state_ == STATE_ANIMATION_ENDING) { 145 // The "ending" animation has stopped. Update the tab state so that the UI 146 // can update the tab icon. 147 state_ = STATE_NOT_ANIMATING; 148 delegate_->ScheduleAudioIndicatorPaint(); 149 } 150 } 151 152 std::vector<int> TabAudioIndicator::GetCurrentEqualizerLevels() const { 153 int next_frame_index = (frame_index_ + 1) % arraysize(kEqualizerFrames); 154 std::vector<int> levels; 155 // For all 2 columsn of the equalizer, tween between the current equalizer 156 // level and the target equalizer level. 157 for (size_t i = 0; i < kEqualizerColumnCount; ++i) { 158 int start = kEqualizerFrames[frame_index_][i]; 159 int end = state_ == STATE_ANIMATION_ENDING 160 ? 0 161 : kEqualizerFrames[next_frame_index][i]; 162 levels.push_back(animation_->CurrentValueBetween(start, end)); 163 } 164 return levels; 165 } 166