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 "third_party/skia/include/core/SkPath.h" 9 #include "ui/app_list/app_list_model.h" 10 #include "ui/app_list/app_list_view_delegate.h" 11 #include "ui/app_list/speech_ui_model.h" 12 #include "ui/base/l10n/l10n_util.h" 13 #include "ui/base/resource/resource_bundle.h" 14 #include "ui/gfx/canvas.h" 15 #include "ui/gfx/path.h" 16 #include "ui/resources/grit/ui_resources.h" 17 #include "ui/strings/grit/ui_strings.h" 18 #include "ui/views/animation/bounds_animator.h" 19 #include "ui/views/background.h" 20 #include "ui/views/controls/button/image_button.h" 21 #include "ui/views/controls/image_view.h" 22 #include "ui/views/controls/label.h" 23 #include "ui/views/layout/fill_layout.h" 24 #include "ui/views/masked_targeter_delegate.h" 25 #include "ui/views/shadow_border.h" 26 27 namespace app_list { 28 29 namespace { 30 31 const int kShadowOffset = 1; 32 const int kShadowBlur = 4; 33 const int kSpeechViewMaxHeight = 300; 34 const int kMicButtonMargin = 12; 35 const int kTextMargin = 32; 36 const int kLogoMarginLeft = 30; 37 const int kLogoMarginTop = 28; 38 const int kLogoWidth = 104; 39 const int kLogoHeight = 36; 40 const int kIndicatorCenterOffsetY = -1; 41 const int kIndicatorRadiusMinOffset = -3; 42 const int kIndicatorRadiusMax = 100; 43 const int kIndicatorAnimationDuration = 100; 44 const SkColor kShadowColor = SkColorSetARGB(0.3 * 255, 0, 0, 0); 45 const SkColor kHintTextColor = SkColorSetRGB(119, 119, 119); 46 const SkColor kResultTextColor = SkColorSetRGB(178, 178, 178); 47 const SkColor kSoundLevelIndicatorColor = SkColorSetRGB(219, 219, 219); 48 49 class SoundLevelIndicator : public views::View { 50 public: 51 SoundLevelIndicator(); 52 virtual ~SoundLevelIndicator(); 53 54 private: 55 // Overridden from views::View: 56 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE; 57 58 DISALLOW_COPY_AND_ASSIGN(SoundLevelIndicator); 59 }; 60 61 SoundLevelIndicator::SoundLevelIndicator() {} 62 63 SoundLevelIndicator::~SoundLevelIndicator() {} 64 65 void SoundLevelIndicator::OnPaint(gfx::Canvas* canvas) { 66 SkPaint paint; 67 paint.setStyle(SkPaint::kFill_Style); 68 paint.setColor(kSoundLevelIndicatorColor); 69 paint.setAntiAlias(true); 70 canvas->DrawCircle(bounds().CenterPoint(), width() / 2, paint); 71 } 72 73 // MicButton is an image button with a circular hit test mask. 74 class MicButton : public views::ImageButton, 75 public views::MaskedTargeterDelegate { 76 public: 77 explicit MicButton(views::ButtonListener* listener); 78 virtual ~MicButton(); 79 80 private: 81 // views::MaskedTargeterDelegate: 82 virtual bool GetHitTestMask(gfx::Path* mask) const OVERRIDE; 83 84 DISALLOW_COPY_AND_ASSIGN(MicButton); 85 }; 86 87 MicButton::MicButton(views::ButtonListener* listener) 88 : views::ImageButton(listener) {} 89 90 MicButton::~MicButton() {} 91 92 bool MicButton::GetHitTestMask(gfx::Path* mask) const { 93 DCHECK(mask); 94 95 // The mic button icon is a circle. 96 gfx::Rect local_bounds = GetLocalBounds(); 97 int radius = local_bounds.width() / 2 + kIndicatorRadiusMinOffset; 98 gfx::Point center = local_bounds.CenterPoint(); 99 center.set_y(center.y() + kIndicatorCenterOffsetY); 100 mask->addCircle(SkIntToScalar(center.x()), 101 SkIntToScalar(center.y()), 102 SkIntToScalar(radius)); 103 return true; 104 } 105 106 } // namespace 107 108 // static 109 110 SpeechView::SpeechView(AppListViewDelegate* delegate) 111 : delegate_(delegate), 112 logo_(NULL) { 113 SetBorder(scoped_ptr<views::Border>( 114 new views::ShadowBorder(kShadowBlur, 115 kShadowColor, 116 kShadowOffset, // Vertical offset. 117 0))); 118 119 // To keep the painting order of the border and the background, this class 120 // actually has a single child of 'container' which has white background and 121 // contains all components. 122 views::View* container = new views::View(); 123 container->set_background( 124 views::Background::CreateSolidBackground(SK_ColorWHITE)); 125 126 const gfx::ImageSkia& logo_image = delegate_->GetSpeechUI()->logo(); 127 if (!logo_image.isNull()) { 128 logo_ = new views::ImageView(); 129 logo_->SetImage(&logo_image); 130 container->AddChildView(logo_); 131 } 132 133 indicator_ = new SoundLevelIndicator(); 134 indicator_->SetVisible(false); 135 container->AddChildView(indicator_); 136 137 MicButton* mic_button = new MicButton(this); 138 mic_button_ = mic_button; 139 container->AddChildView(mic_button_); 140 mic_button_->SetEventTargeter( 141 scoped_ptr<views::ViewTargeter>(new views::ViewTargeter(mic_button))); 142 143 // TODO(mukai): use BoundedLabel to cap 2 lines. 144 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); 145 speech_result_ = new views::Label( 146 base::string16(), bundle.GetFontList(ui::ResourceBundle::LargeFont)); 147 speech_result_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 148 149 speech_result_->SetMultiLine(true); 150 container->AddChildView(speech_result_); 151 152 AddChildView(container); 153 154 delegate_->GetSpeechUI()->AddObserver(this); 155 indicator_animator_.reset(new views::BoundsAnimator(container)); 156 indicator_animator_->SetAnimationDuration(kIndicatorAnimationDuration); 157 indicator_animator_->set_tween_type(gfx::Tween::LINEAR); 158 159 Reset(); 160 } 161 162 SpeechView::~SpeechView() { 163 delegate_->GetSpeechUI()->RemoveObserver(this); 164 } 165 166 void SpeechView::Reset() { 167 OnSpeechRecognitionStateChanged(delegate_->GetSpeechUI()->state()); 168 } 169 170 int SpeechView::GetIndicatorRadius(uint8 level) { 171 int radius_min = mic_button_->width() / 2 + kIndicatorRadiusMinOffset; 172 int range = kIndicatorRadiusMax - radius_min; 173 return level * range / kuint8max + radius_min; 174 } 175 176 void SpeechView::Layout() { 177 views::View* container = child_at(0); 178 container->SetBoundsRect(GetContentsBounds()); 179 180 // Because container is a pure View, this class should layout its children. 181 const gfx::Rect contents_bounds = container->GetContentsBounds(); 182 if (logo_) 183 logo_->SetBounds(kLogoMarginLeft, kLogoMarginTop, kLogoWidth, kLogoHeight); 184 gfx::Size mic_size = mic_button_->GetPreferredSize(); 185 gfx::Point mic_origin( 186 contents_bounds.right() - kMicButtonMargin - mic_size.width(), 187 contents_bounds.y() + kMicButtonMargin); 188 mic_button_->SetBoundsRect(gfx::Rect(mic_origin, mic_size)); 189 190 int speech_width = contents_bounds.width() - kTextMargin * 2; 191 speech_result_->SizeToFit(speech_width); 192 int speech_height = speech_result_->GetHeightForWidth(speech_width); 193 speech_result_->SetBounds( 194 contents_bounds.x() + kTextMargin, 195 contents_bounds.bottom() - kTextMargin - speech_height, 196 speech_width, 197 speech_height); 198 } 199 200 gfx::Size SpeechView::GetPreferredSize() const { 201 return gfx::Size(0, kSpeechViewMaxHeight); 202 } 203 204 void SpeechView::ButtonPressed(views::Button* sender, const ui::Event& event) { 205 delegate_->ToggleSpeechRecognition(); 206 } 207 208 void SpeechView::OnSpeechSoundLevelChanged(uint8 level) { 209 if (!visible() || 210 delegate_->GetSpeechUI()->state() == SPEECH_RECOGNITION_NETWORK_ERROR) 211 return; 212 213 gfx::Point origin = mic_button_->bounds().CenterPoint(); 214 int radius = GetIndicatorRadius(level); 215 origin.Offset(-radius, -radius + kIndicatorCenterOffsetY); 216 gfx::Rect indicator_bounds = 217 gfx::Rect(origin, gfx::Size(radius * 2, radius * 2)); 218 if (indicator_->visible()) { 219 indicator_animator_->AnimateViewTo(indicator_, indicator_bounds); 220 } else { 221 indicator_->SetVisible(true); 222 indicator_->SetBoundsRect(indicator_bounds); 223 } 224 } 225 226 void SpeechView::OnSpeechResult(const base::string16& result, 227 bool is_final) { 228 speech_result_->SetText(result); 229 speech_result_->SetEnabledColor(kResultTextColor); 230 } 231 232 void SpeechView::OnSpeechRecognitionStateChanged( 233 SpeechRecognitionState new_state) { 234 int resource_id = IDR_APP_LIST_SPEECH_MIC_OFF; 235 if (new_state == SPEECH_RECOGNITION_RECOGNIZING) 236 resource_id = IDR_APP_LIST_SPEECH_MIC_ON; 237 else if (new_state == SPEECH_RECOGNITION_IN_SPEECH) 238 resource_id = IDR_APP_LIST_SPEECH_MIC_RECORDING; 239 240 int text_resource_id = IDS_APP_LIST_SPEECH_HINT_TEXT; 241 242 if (new_state == SPEECH_RECOGNITION_NETWORK_ERROR) { 243 text_resource_id = IDS_APP_LIST_SPEECH_NETWORK_ERROR_HINT_TEXT; 244 indicator_->SetVisible(false); 245 } 246 speech_result_->SetText(l10n_util::GetStringUTF16(text_resource_id)); 247 speech_result_->SetEnabledColor(kHintTextColor); 248 249 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); 250 mic_button_->SetImage(views::Button::STATE_NORMAL, 251 bundle.GetImageSkiaNamed(resource_id)); 252 } 253 254 } // namespace app_list 255