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/gfx/shadow_value.h"
18 #include "ui/resources/grit/ui_resources.h"
19 #include "ui/strings/grit/ui_strings.h"
20 #include "ui/views/animation/bounds_animator.h"
21 #include "ui/views/background.h"
22 #include "ui/views/controls/button/image_button.h"
23 #include "ui/views/controls/image_view.h"
24 #include "ui/views/controls/label.h"
25 #include "ui/views/layout/fill_layout.h"
26 #include "ui/views/masked_targeter_delegate.h"
27 #include "ui/views/shadow_border.h"
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 kHintTextColor
= SkColorSetRGB(119, 119, 119);
45 const SkColor kResultTextColor
= SkColorSetRGB(178, 178, 178);
46 const SkColor kSoundLevelIndicatorColor
= SkColorSetRGB(219, 219, 219);
48 class SoundLevelIndicator
: public views::View
{
50 SoundLevelIndicator();
51 ~SoundLevelIndicator() override
;
54 // Overridden from views::View:
55 void OnPaint(gfx::Canvas
* canvas
) override
;
57 DISALLOW_COPY_AND_ASSIGN(SoundLevelIndicator
);
60 SoundLevelIndicator::SoundLevelIndicator() {}
62 SoundLevelIndicator::~SoundLevelIndicator() {}
64 void SoundLevelIndicator::OnPaint(gfx::Canvas
* canvas
) {
66 paint
.setStyle(SkPaint::kFill_Style
);
67 paint
.setColor(kSoundLevelIndicatorColor
);
68 paint
.setAntiAlias(true);
69 canvas
->DrawCircle(bounds().CenterPoint(), width() / 2, paint
);
72 // MicButton is an image button with a circular hit test mask.
73 class MicButton
: public views::ImageButton
,
74 public views::MaskedTargeterDelegate
{
76 explicit MicButton(views::ButtonListener
* listener
);
77 ~MicButton() override
;
80 // views::MaskedTargeterDelegate:
81 bool GetHitTestMask(gfx::Path
* mask
) const override
;
83 DISALLOW_COPY_AND_ASSIGN(MicButton
);
86 MicButton::MicButton(views::ButtonListener
* listener
)
87 : views::ImageButton(listener
) {}
89 MicButton::~MicButton() {}
91 bool MicButton::GetHitTestMask(gfx::Path
* mask
) const {
94 // The mic button icon is a circle.
95 gfx::Rect local_bounds
= GetLocalBounds();
96 int radius
= local_bounds
.width() / 2 + kIndicatorRadiusMinOffset
;
97 gfx::Point center
= local_bounds
.CenterPoint();
98 center
.set_y(center
.y() + kIndicatorCenterOffsetY
);
99 mask
->addCircle(SkIntToScalar(center
.x()),
100 SkIntToScalar(center
.y()),
101 SkIntToScalar(radius
));
109 SpeechView::SpeechView(AppListViewDelegate
* delegate
)
110 : delegate_(delegate
),
112 SetBorder(scoped_ptr
<views::Border
>(
113 new views::ShadowBorder(GetShadowForZHeight(1))));
115 // To keep the painting order of the border and the background, this class
116 // actually has a single child of 'container' which has white background and
117 // contains all components.
118 views::View
* container
= new views::View();
119 container
->set_background(
120 views::Background::CreateSolidBackground(SK_ColorWHITE
));
122 const gfx::ImageSkia
& logo_image
= delegate_
->GetSpeechUI()->logo();
123 if (!logo_image
.isNull()) {
124 logo_
= new views::ImageView();
125 logo_
->SetImage(&logo_image
);
126 container
->AddChildView(logo_
);
129 indicator_
= new SoundLevelIndicator();
130 indicator_
->SetVisible(false);
131 container
->AddChildView(indicator_
);
133 MicButton
* mic_button
= new MicButton(this);
134 mic_button_
= mic_button
;
135 container
->AddChildView(mic_button_
);
136 mic_button_
->SetEventTargeter(
137 scoped_ptr
<views::ViewTargeter
>(new views::ViewTargeter(mic_button
)));
139 // TODO(mukai): use BoundedLabel to cap 2 lines.
140 ui::ResourceBundle
& bundle
= ui::ResourceBundle::GetSharedInstance();
141 speech_result_
= new views::Label(
142 base::string16(), bundle
.GetFontList(ui::ResourceBundle::LargeFont
));
143 speech_result_
->SetMultiLine(true);
144 speech_result_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
146 container
->AddChildView(speech_result_
);
148 AddChildView(container
);
150 delegate_
->GetSpeechUI()->AddObserver(this);
151 indicator_animator_
.reset(new views::BoundsAnimator(container
));
152 indicator_animator_
->SetAnimationDuration(kIndicatorAnimationDuration
);
153 indicator_animator_
->set_tween_type(gfx::Tween::LINEAR
);
158 SpeechView::~SpeechView() {
159 delegate_
->GetSpeechUI()->RemoveObserver(this);
162 void SpeechView::Reset() {
163 OnSpeechRecognitionStateChanged(delegate_
->GetSpeechUI()->state());
166 int SpeechView::GetIndicatorRadius(uint8 level
) {
167 int radius_min
= mic_button_
->width() / 2 + kIndicatorRadiusMinOffset
;
168 int range
= kIndicatorRadiusMax
- radius_min
;
169 return level
* range
/ kuint8max
+ radius_min
;
172 void SpeechView::Layout() {
173 views::View
* container
= child_at(0);
174 container
->SetBoundsRect(GetContentsBounds());
176 // Because container is a pure View, this class should layout its children.
177 const gfx::Rect contents_bounds
= container
->GetContentsBounds();
179 logo_
->SetBounds(kLogoMarginLeft
, kLogoMarginTop
, kLogoWidth
, kLogoHeight
);
180 gfx::Size mic_size
= mic_button_
->GetPreferredSize();
181 gfx::Point
mic_origin(
182 contents_bounds
.right() - kMicButtonMargin
- mic_size
.width(),
183 contents_bounds
.y() + kMicButtonMargin
);
184 mic_button_
->SetBoundsRect(gfx::Rect(mic_origin
, mic_size
));
186 int speech_width
= contents_bounds
.width() - kTextMargin
* 2;
187 int speech_height
= speech_result_
->GetHeightForWidth(speech_width
);
188 speech_result_
->SetBounds(
189 contents_bounds
.x() + kTextMargin
,
190 contents_bounds
.bottom() - kTextMargin
- speech_height
,
195 gfx::Size
SpeechView::GetPreferredSize() const {
196 return gfx::Size(0, kSpeechViewMaxHeight
);
199 void SpeechView::ButtonPressed(views::Button
* sender
, const ui::Event
& event
) {
200 delegate_
->ToggleSpeechRecognition();
203 void SpeechView::OnSpeechSoundLevelChanged(uint8 level
) {
205 delegate_
->GetSpeechUI()->state() == SPEECH_RECOGNITION_NETWORK_ERROR
)
208 gfx::Point origin
= mic_button_
->bounds().CenterPoint();
209 int radius
= GetIndicatorRadius(level
);
210 origin
.Offset(-radius
, -radius
+ kIndicatorCenterOffsetY
);
211 gfx::Rect indicator_bounds
=
212 gfx::Rect(origin
, gfx::Size(radius
* 2, radius
* 2));
213 if (indicator_
->visible()) {
214 indicator_animator_
->AnimateViewTo(indicator_
, indicator_bounds
);
216 indicator_
->SetVisible(true);
217 indicator_
->SetBoundsRect(indicator_bounds
);
221 void SpeechView::OnSpeechResult(const base::string16
& result
,
223 speech_result_
->SetText(result
);
224 speech_result_
->SetEnabledColor(kResultTextColor
);
228 void SpeechView::OnSpeechRecognitionStateChanged(
229 SpeechRecognitionState new_state
) {
230 int resource_id
= IDR_APP_LIST_SPEECH_MIC_OFF
;
231 if (new_state
== SPEECH_RECOGNITION_RECOGNIZING
)
232 resource_id
= IDR_APP_LIST_SPEECH_MIC_ON
;
233 else if (new_state
== SPEECH_RECOGNITION_IN_SPEECH
)
234 resource_id
= IDR_APP_LIST_SPEECH_MIC_RECORDING
;
236 int text_resource_id
= IDS_APP_LIST_SPEECH_HINT_TEXT
;
238 if (new_state
== SPEECH_RECOGNITION_NETWORK_ERROR
) {
239 text_resource_id
= IDS_APP_LIST_SPEECH_NETWORK_ERROR_HINT_TEXT
;
240 indicator_
->SetVisible(false);
242 speech_result_
->SetText(l10n_util::GetStringUTF16(text_resource_id
));
243 speech_result_
->SetEnabledColor(kHintTextColor
);
245 ui::ResourceBundle
& bundle
= ui::ResourceBundle::GetSharedInstance();
246 mic_button_
->SetImage(views::Button::STATE_NORMAL
,
247 bundle
.GetImageSkiaNamed(resource_id
));
250 } // namespace app_list