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.
5 #include "ui/app_list/views/speech_view.h"
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"
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);
49 class SoundLevelIndicator
: public views::View
{
51 SoundLevelIndicator();
52 virtual ~SoundLevelIndicator();
55 // Overridden from views::View:
56 virtual void OnPaint(gfx::Canvas
* canvas
) OVERRIDE
;
58 DISALLOW_COPY_AND_ASSIGN(SoundLevelIndicator
);
61 SoundLevelIndicator::SoundLevelIndicator() {}
63 SoundLevelIndicator::~SoundLevelIndicator() {}
65 void SoundLevelIndicator::OnPaint(gfx::Canvas
* canvas
) {
67 paint
.setStyle(SkPaint::kFill_Style
);
68 paint
.setColor(kSoundLevelIndicatorColor
);
69 paint
.setAntiAlias(true);
70 canvas
->DrawCircle(bounds().CenterPoint(), width() / 2, paint
);
73 // MicButton is an image button with a circular hit test mask.
74 class MicButton
: public views::ImageButton
,
75 public views::MaskedTargeterDelegate
{
77 explicit MicButton(views::ButtonListener
* listener
);
81 // views::MaskedTargeterDelegate:
82 virtual bool GetHitTestMask(gfx::Path
* mask
) const OVERRIDE
;
84 DISALLOW_COPY_AND_ASSIGN(MicButton
);
87 MicButton::MicButton(views::ButtonListener
* listener
)
88 : views::ImageButton(listener
) {}
90 MicButton::~MicButton() {}
92 bool MicButton::GetHitTestMask(gfx::Path
* mask
) const {
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
));
110 SpeechView::SpeechView(AppListViewDelegate
* delegate
)
111 : delegate_(delegate
),
113 SetBorder(scoped_ptr
<views::Border
>(
114 new views::ShadowBorder(kShadowBlur
,
116 kShadowOffset
, // Vertical offset.
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
));
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_
);
133 indicator_
= new SoundLevelIndicator();
134 indicator_
->SetVisible(false);
135 container
->AddChildView(indicator_
);
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
)));
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
);
149 speech_result_
->SetMultiLine(true);
150 container
->AddChildView(speech_result_
);
152 AddChildView(container
);
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
);
162 SpeechView::~SpeechView() {
163 delegate_
->GetSpeechUI()->RemoveObserver(this);
166 void SpeechView::Reset() {
167 OnSpeechRecognitionStateChanged(delegate_
->GetSpeechUI()->state());
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
;
176 void SpeechView::Layout() {
177 views::View
* container
= child_at(0);
178 container
->SetBoundsRect(GetContentsBounds());
180 // Because container is a pure View, this class should layout its children.
181 const gfx::Rect contents_bounds
= container
->GetContentsBounds();
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
));
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
,
200 gfx::Size
SpeechView::GetPreferredSize() const {
201 return gfx::Size(0, kSpeechViewMaxHeight
);
204 void SpeechView::ButtonPressed(views::Button
* sender
, const ui::Event
& event
) {
205 delegate_
->ToggleSpeechRecognition();
208 void SpeechView::OnSpeechSoundLevelChanged(uint8 level
) {
210 delegate_
->GetSpeechUI()->state() == SPEECH_RECOGNITION_NETWORK_ERROR
)
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
);
221 indicator_
->SetVisible(true);
222 indicator_
->SetBoundsRect(indicator_bounds
);
226 void SpeechView::OnSpeechResult(const base::string16
& result
,
228 speech_result_
->SetText(result
);
229 speech_result_
->SetEnabledColor(kResultTextColor
);
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
;
240 int text_resource_id
= IDS_APP_LIST_SPEECH_HINT_TEXT
;
242 if (new_state
== SPEECH_RECOGNITION_NETWORK_ERROR
) {
243 text_resource_id
= IDS_APP_LIST_SPEECH_NETWORK_ERROR_HINT_TEXT
;
244 indicator_
->SetVisible(false);
246 speech_result_
->SetText(l10n_util::GetStringUTF16(text_resource_id
));
247 speech_result_
->SetEnabledColor(kHintTextColor
);
249 ui::ResourceBundle
& bundle
= ui::ResourceBundle::GetSharedInstance();
250 mic_button_
->SetImage(views::Button::STATE_NORMAL
,
251 bundle
.GetImageSkiaNamed(resource_id
));
254 } // namespace app_list