Home | History | Annotate | Download | only in tabs
      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