Home | History | Annotate | Download | only in views
      1 // Copyright 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 "ui/app_list/views/speech_view.h"
      6 
      7 #include "base/strings/utf_string_conversions.h"
      8 #include "grit/ui_resources.h"
      9 #include "grit/ui_strings.h"
     10 #include "ui/app_list/app_list_model.h"
     11 #include "ui/app_list/app_list_view_delegate.h"
     12 #include "ui/app_list/speech_ui_model.h"
     13 #include "ui/base/l10n/l10n_util.h"
     14 #include "ui/base/resource/resource_bundle.h"
     15 #include "ui/gfx/canvas.h"
     16 #include "ui/views/animation/bounds_animator.h"
     17 #include "ui/views/background.h"
     18 #include "ui/views/controls/button/image_button.h"
     19 #include "ui/views/controls/label.h"
     20 #include "ui/views/layout/fill_layout.h"
     21 #include "ui/views/shadow_border.h"
     22 
     23 namespace app_list {
     24 
     25 namespace {
     26 
     27 const int kShadowOffset = 1;
     28 const int kShadowBlur = 4;
     29 const int kSpeechViewMaxHeight = 300;
     30 const int kTextSize = 20;
     31 const int kMicButtonMargin = 12;
     32 const int kTextMargin = 32;
     33 const int kIndicatorRadiusMax = 100;
     34 const int kIndicatorAnimationDuration = 100;
     35 const SkColor kShadowColor = SkColorSetARGB(0.3 * 255, 0, 0, 0);
     36 const SkColor kHintTextColor = SkColorSetRGB(119, 119, 119);
     37 const SkColor kResultTextColor = SkColorSetRGB(178, 178, 178);
     38 const SkColor kSoundLevelIndicatorColor = SkColorSetRGB(219, 219, 219);
     39 
     40 // TODO(mukai): check with multiple devices to make sure these limits.
     41 const int16 kSoundLevelMin = 50;
     42 const int16 kSoundLevelMax = 210;
     43 
     44 class SoundLevelIndicator : public views::View {
     45  public:
     46   SoundLevelIndicator();
     47   virtual ~SoundLevelIndicator();
     48 
     49  private:
     50   // Overridden from views::View:
     51   virtual void Paint(gfx::Canvas* canvas) OVERRIDE;
     52 
     53   DISALLOW_COPY_AND_ASSIGN(SoundLevelIndicator);
     54 };
     55 
     56 SoundLevelIndicator::SoundLevelIndicator() {}
     57 
     58 SoundLevelIndicator::~SoundLevelIndicator() {}
     59 
     60 void SoundLevelIndicator::Paint(gfx::Canvas* canvas) {
     61   SkPaint paint;
     62   paint.setStyle(SkPaint::kFill_Style);
     63   paint.setColor(kSoundLevelIndicatorColor);
     64   canvas->DrawCircle(bounds().CenterPoint(), width() / 2, paint);
     65 }
     66 
     67 // MicButton is an image button with circular hit area.
     68 class MicButton : public views::ImageButton {
     69  public:
     70   explicit MicButton(views::ButtonListener* listener);
     71   virtual ~MicButton();
     72 
     73  private:
     74   // Overridden from views::View:
     75   virtual bool HitTestRect(const gfx::Rect& rect) const OVERRIDE;
     76 
     77   DISALLOW_COPY_AND_ASSIGN(MicButton);
     78 };
     79 
     80 MicButton::MicButton(views::ButtonListener* listener)
     81     : views::ImageButton(listener) {}
     82 
     83 MicButton::~MicButton() {}
     84 
     85 bool MicButton::HitTestRect(const gfx::Rect& rect) const {
     86   if (!views::ImageButton::HitTestRect(rect))
     87     return false;
     88 
     89   gfx::Rect local_bounds = GetLocalBounds();
     90   int radius = local_bounds.width() / 2;
     91   return (rect.origin() - local_bounds.CenterPoint()).LengthSquared() <
     92       radius * radius;
     93 }
     94 
     95 }  // namespace
     96 
     97 // static
     98 
     99 SpeechView::SpeechView(AppListViewDelegate* delegate)
    100     : delegate_(delegate) {
    101   set_border(new views::ShadowBorder(
    102       kShadowBlur,
    103       kShadowColor,
    104       kShadowOffset,  // Vertical offset.
    105       0));
    106 
    107   // To keep the painting order of the border and the background, this class
    108   // actually has a single child of 'container' which has white background and
    109   // contains all components.
    110   views::View* container = new views::View();
    111   container->set_background(
    112       views::Background::CreateSolidBackground(SK_ColorWHITE));
    113 
    114   // TODO(mukai): add Google logo.
    115   indicator_ = new SoundLevelIndicator();
    116   indicator_->SetVisible(false);
    117   container->AddChildView(indicator_);
    118 
    119   mic_button_ = new MicButton(this);
    120   container->AddChildView(mic_button_);
    121 
    122   // TODO(mukai): use BoundedLabel to cap 2 lines.
    123   speech_result_ = new views::Label();
    124   speech_result_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    125   const gfx::FontList& font_list = speech_result_->font_list();
    126   speech_result_->SetFontList(font_list.DeriveFontListWithSize(kTextSize));
    127   speech_result_->SetMultiLine(true);
    128   container->AddChildView(speech_result_);
    129 
    130   AddChildView(container);
    131 
    132   delegate_->GetSpeechUI()->AddObserver(this);
    133   indicator_animator_.reset(new views::BoundsAnimator(container));
    134   indicator_animator_->SetAnimationDuration(kIndicatorAnimationDuration);
    135   indicator_animator_->set_tween_type(gfx::Tween::LINEAR);
    136 
    137   Reset();
    138 }
    139 
    140 SpeechView::~SpeechView() {
    141   delegate_->GetSpeechUI()->RemoveObserver(this);
    142 }
    143 
    144 void SpeechView::Reset() {
    145   ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
    146   speech_result_->SetText(l10n_util::GetStringUTF16(
    147       IDS_APP_LIST_SPEECH_HINT_TEXT));
    148   speech_result_->SetEnabledColor(kHintTextColor);
    149   mic_button_->SetImage(views::Button::STATE_NORMAL, bundle.GetImageSkiaNamed(
    150       IDR_APP_LIST_SPEECH_MIC_ON));
    151 }
    152 
    153 int SpeechView::GetIndicatorRadius(int16 level) {
    154   level = std::min(std::max(level, kSoundLevelMin), kSoundLevelMax);
    155   int radius_min = mic_button_->width() / 2;
    156   return (level - kSoundLevelMin) * (kIndicatorRadiusMax - radius_min) /
    157       (kSoundLevelMax - kSoundLevelMin) + radius_min;
    158 }
    159 
    160 void SpeechView::Layout() {
    161   views::View* container = child_at(0);
    162   container->SetBoundsRect(GetContentsBounds());
    163 
    164   // Because container is a pure View, this class should layout its children.
    165   // TODO(mukai): arrange Google logo.
    166   const gfx::Rect contents_bounds = container->GetContentsBounds();
    167   gfx::Size mic_size = mic_button_->GetPreferredSize();
    168   gfx::Point mic_origin(
    169       contents_bounds.right() - kMicButtonMargin - mic_size.width(),
    170       contents_bounds.y() + kMicButtonMargin);
    171   mic_button_->SetBoundsRect(gfx::Rect(mic_origin, mic_size));
    172 
    173   int speech_width = contents_bounds.width() - kTextMargin * 2;
    174   speech_result_->SizeToFit(speech_width);
    175   int speech_height = speech_result_->GetHeightForWidth(speech_width);
    176   speech_result_->SetBounds(
    177       contents_bounds.x() + kTextMargin,
    178       contents_bounds.bottom() - kTextMargin - speech_height,
    179       speech_width,
    180       speech_height);
    181 }
    182 
    183 gfx::Size SpeechView::GetPreferredSize() {
    184   return gfx::Size(0, kSpeechViewMaxHeight);
    185 }
    186 
    187 void SpeechView::ButtonPressed(views::Button* sender, const ui::Event& event) {
    188   delegate_->ToggleSpeechRecognition();
    189 }
    190 
    191 void SpeechView::OnSpeechSoundLevelChanged(int16 level) {
    192   if (!visible())
    193     return;
    194 
    195   gfx::Point origin = mic_button_->bounds().CenterPoint();
    196   int radius = GetIndicatorRadius(level);
    197   origin.Offset(-radius, -radius);
    198   gfx::Rect indicator_bounds =
    199       gfx::Rect(origin, gfx::Size(radius * 2, radius * 2));
    200   if (indicator_->visible()) {
    201     indicator_animator_->AnimateViewTo(indicator_, indicator_bounds);
    202   } else {
    203     indicator_->SetVisible(true);
    204     indicator_->SetBoundsRect(indicator_bounds);
    205   }
    206 }
    207 
    208 void SpeechView::OnSpeechResult(const base::string16& result,
    209                                 bool is_final) {
    210   speech_result_->SetText(result);
    211   speech_result_->SetEnabledColor(kResultTextColor);
    212 }
    213 
    214 void SpeechView::OnSpeechRecognitionStateChanged(
    215     SpeechRecognitionState new_state) {
    216   int resource_id = IDR_APP_LIST_SPEECH_MIC_OFF;
    217   if (new_state == SPEECH_RECOGNITION_ON)
    218     resource_id = IDR_APP_LIST_SPEECH_MIC_ON;
    219   else if (new_state == SPEECH_RECOGNITION_IN_SPEECH)
    220     resource_id = IDR_APP_LIST_SPEECH_MIC_RECORDING;
    221 
    222   ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
    223   mic_button_->SetImage(views::Button::STATE_NORMAL, bundle.GetImageSkiaNamed(
    224       resource_id));
    225 }
    226 
    227 }  // namespace app_list
    228