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_constants.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/gfx/path.h"
17 #include "ui/resources/grit/ui_resources.h"
18 #include "ui/strings/grit/ui_strings.h"
19 #include "ui/views/animation/bounds_animator.h"
20 #include "ui/views/background.h"
21 #include "ui/views/controls/button/image_button.h"
22 #include "ui/views/controls/image_view.h"
23 #include "ui/views/controls/label.h"
24 #include "ui/views/layout/fill_layout.h"
25 #include "ui/views/masked_targeter_delegate.h"
26 #include "ui/views/shadow_border.h"
32 const int kSpeechViewMaxHeight
= 300;
33 const int kMicButtonMargin
= 12;
34 const int kTextMargin
= 32;
35 const int kLogoMarginLeft
= 30;
36 const int kLogoMarginTop
= 28;
37 const int kLogoWidth
= 104;
38 const int kLogoHeight
= 36;
39 const int kIndicatorCenterOffsetY
= -1;
40 const int kIndicatorRadiusMinOffset
= -3;
41 const int kIndicatorRadiusMax
= 100;
42 const int kIndicatorAnimationDuration
= 100;
43 const SkColor kHintTextColor
= SkColorSetRGB(119, 119, 119);
44 const SkColor kResultTextColor
= SkColorSetRGB(178, 178, 178);
45 const SkColor kSoundLevelIndicatorColor
= SkColorSetRGB(219, 219, 219);
47 class SoundLevelIndicator
: public views::View
{
49 SoundLevelIndicator();
50 ~SoundLevelIndicator() override
;
53 // Overridden from views::View:
54 void OnPaint(gfx::Canvas
* canvas
) override
;
56 DISALLOW_COPY_AND_ASSIGN(SoundLevelIndicator
);
59 SoundLevelIndicator::SoundLevelIndicator() {}
61 SoundLevelIndicator::~SoundLevelIndicator() {}
63 void SoundLevelIndicator::OnPaint(gfx::Canvas
* canvas
) {
65 paint
.setStyle(SkPaint::kFill_Style
);
66 paint
.setColor(kSoundLevelIndicatorColor
);
67 paint
.setAntiAlias(true);
68 canvas
->DrawCircle(bounds().CenterPoint(), width() / 2, paint
);
71 // MicButton is an image button with a circular hit test mask.
72 class MicButton
: public views::ImageButton
,
73 public views::MaskedTargeterDelegate
{
75 explicit MicButton(views::ButtonListener
* listener
);
76 ~MicButton() override
;
79 // views::MaskedTargeterDelegate:
80 bool GetHitTestMask(gfx::Path
* mask
) const override
;
82 DISALLOW_COPY_AND_ASSIGN(MicButton
);
85 MicButton::MicButton(views::ButtonListener
* listener
)
86 : views::ImageButton(listener
) {}
88 MicButton::~MicButton() {}
90 bool MicButton::GetHitTestMask(gfx::Path
* mask
) const {
93 // The mic button icon is a circle.
94 gfx::Rect local_bounds
= GetLocalBounds();
95 int radius
= local_bounds
.width() / 2 + kIndicatorRadiusMinOffset
;
96 gfx::Point center
= local_bounds
.CenterPoint();
97 center
.set_y(center
.y() + kIndicatorCenterOffsetY
);
98 mask
->addCircle(SkIntToScalar(center
.x()),
99 SkIntToScalar(center
.y()),
100 SkIntToScalar(radius
));
108 SpeechView::SpeechView(AppListViewDelegate
* delegate
)
109 : delegate_(delegate
),
111 SetBorder(scoped_ptr
<views::Border
>(
112 new views::ShadowBorder(kCardShadowBlur
, kCardShadowColor
,
113 kCardShadowYOffset
, // Vertical offset.
116 // To keep the painting order of the border and the background, this class
117 // actually has a single child of 'container' which has white background and
118 // contains all components.
119 views::View
* container
= new views::View();
120 container
->set_background(
121 views::Background::CreateSolidBackground(SK_ColorWHITE
));
123 const gfx::ImageSkia
& logo_image
= delegate_
->GetSpeechUI()->logo();
124 if (!logo_image
.isNull()) {
125 logo_
= new views::ImageView();
126 logo_
->SetImage(&logo_image
);
127 container
->AddChildView(logo_
);
130 indicator_
= new SoundLevelIndicator();
131 indicator_
->SetVisible(false);
132 container
->AddChildView(indicator_
);
134 MicButton
* mic_button
= new MicButton(this);
135 mic_button_
= mic_button
;
136 container
->AddChildView(mic_button_
);
137 mic_button_
->SetEventTargeter(
138 scoped_ptr
<views::ViewTargeter
>(new views::ViewTargeter(mic_button
)));
140 // TODO(mukai): use BoundedLabel to cap 2 lines.
141 ui::ResourceBundle
& bundle
= ui::ResourceBundle::GetSharedInstance();
142 speech_result_
= new views::Label(
143 base::string16(), bundle
.GetFontList(ui::ResourceBundle::LargeFont
));
144 speech_result_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
146 speech_result_
->SetMultiLine(true);
147 container
->AddChildView(speech_result_
);
149 AddChildView(container
);
151 delegate_
->GetSpeechUI()->AddObserver(this);
152 indicator_animator_
.reset(new views::BoundsAnimator(container
));
153 indicator_animator_
->SetAnimationDuration(kIndicatorAnimationDuration
);
154 indicator_animator_
->set_tween_type(gfx::Tween::LINEAR
);
159 SpeechView::~SpeechView() {
160 delegate_
->GetSpeechUI()->RemoveObserver(this);
163 void SpeechView::Reset() {
164 OnSpeechRecognitionStateChanged(delegate_
->GetSpeechUI()->state());
167 int SpeechView::GetIndicatorRadius(uint8 level
) {
168 int radius_min
= mic_button_
->width() / 2 + kIndicatorRadiusMinOffset
;
169 int range
= kIndicatorRadiusMax
- radius_min
;
170 return level
* range
/ kuint8max
+ radius_min
;
173 void SpeechView::Layout() {
174 views::View
* container
= child_at(0);
175 container
->SetBoundsRect(GetContentsBounds());
177 // Because container is a pure View, this class should layout its children.
178 const gfx::Rect contents_bounds
= container
->GetContentsBounds();
180 logo_
->SetBounds(kLogoMarginLeft
, kLogoMarginTop
, kLogoWidth
, kLogoHeight
);
181 gfx::Size mic_size
= mic_button_
->GetPreferredSize();
182 gfx::Point
mic_origin(
183 contents_bounds
.right() - kMicButtonMargin
- mic_size
.width(),
184 contents_bounds
.y() + kMicButtonMargin
);
185 mic_button_
->SetBoundsRect(gfx::Rect(mic_origin
, mic_size
));
187 int speech_width
= contents_bounds
.width() - kTextMargin
* 2;
188 speech_result_
->SizeToFit(speech_width
);
189 int speech_height
= speech_result_
->GetHeightForWidth(speech_width
);
190 speech_result_
->SetBounds(
191 contents_bounds
.x() + kTextMargin
,
192 contents_bounds
.bottom() - kTextMargin
- speech_height
,
197 gfx::Size
SpeechView::GetPreferredSize() const {
198 return gfx::Size(0, kSpeechViewMaxHeight
);
201 void SpeechView::ButtonPressed(views::Button
* sender
, const ui::Event
& event
) {
202 delegate_
->ToggleSpeechRecognition();
205 void SpeechView::OnSpeechSoundLevelChanged(uint8 level
) {
207 delegate_
->GetSpeechUI()->state() == SPEECH_RECOGNITION_NETWORK_ERROR
)
210 gfx::Point origin
= mic_button_
->bounds().CenterPoint();
211 int radius
= GetIndicatorRadius(level
);
212 origin
.Offset(-radius
, -radius
+ kIndicatorCenterOffsetY
);
213 gfx::Rect indicator_bounds
=
214 gfx::Rect(origin
, gfx::Size(radius
* 2, radius
* 2));
215 if (indicator_
->visible()) {
216 indicator_animator_
->AnimateViewTo(indicator_
, indicator_bounds
);
218 indicator_
->SetVisible(true);
219 indicator_
->SetBoundsRect(indicator_bounds
);
223 void SpeechView::OnSpeechResult(const base::string16
& result
,
225 speech_result_
->SetText(result
);
226 speech_result_
->SetEnabledColor(kResultTextColor
);
229 void SpeechView::OnSpeechRecognitionStateChanged(
230 SpeechRecognitionState new_state
) {
231 int resource_id
= IDR_APP_LIST_SPEECH_MIC_OFF
;
232 if (new_state
== SPEECH_RECOGNITION_RECOGNIZING
)
233 resource_id
= IDR_APP_LIST_SPEECH_MIC_ON
;
234 else if (new_state
== SPEECH_RECOGNITION_IN_SPEECH
)
235 resource_id
= IDR_APP_LIST_SPEECH_MIC_RECORDING
;
237 int text_resource_id
= IDS_APP_LIST_SPEECH_HINT_TEXT
;
239 if (new_state
== SPEECH_RECOGNITION_NETWORK_ERROR
) {
240 text_resource_id
= IDS_APP_LIST_SPEECH_NETWORK_ERROR_HINT_TEXT
;
241 indicator_
->SetVisible(false);
243 speech_result_
->SetText(l10n_util::GetStringUTF16(text_resource_id
));
244 speech_result_
->SetEnabledColor(kHintTextColor
);
246 ui::ResourceBundle
& bundle
= ui::ResourceBundle::GetSharedInstance();
247 mic_button_
->SetImage(views::Button::STATE_NORMAL
,
248 bundle
.GetImageSkiaNamed(resource_id
));
251 } // namespace app_list