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