1 // Copyright 2014 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 "athena/home/athena_start_page_view.h"
7 #include "athena/home/home_card_constants.h"
9 #include "base/strings/string_util.h"
10 #include "third_party/skia/include/core/SkPaint.h"
11 #include "third_party/skia/include/core/SkPath.h"
12 #include "ui/app_list/app_list_item.h"
13 #include "ui/app_list/app_list_item_list.h"
14 #include "ui/app_list/app_list_model.h"
15 #include "ui/app_list/app_list_view_delegate.h"
16 #include "ui/app_list/search_box_model.h"
17 #include "ui/app_list/views/search_box_view.h"
18 #include "ui/app_list/views/search_result_list_view.h"
19 #include "ui/compositor/closure_animation_observer.h"
20 #include "ui/compositor/scoped_layer_animation_settings.h"
21 #include "ui/gfx/canvas.h"
22 #include "ui/views/background.h"
23 #include "ui/views/border.h"
24 #include "ui/views/controls/textfield/textfield.h"
25 #include "ui/views/layout/box_layout.h"
26 #include "ui/views/layout/fill_layout.h"
27 #include "ui/views/round_rect_painter.h"
31 const size_t kMaxIconNum
= 3;
32 const int kIconSize
= 50;
33 const int kIconMargin
= 25;
35 const int kTopMargin
= 100;
37 // Copied from ui/app_list/views/start_page_view.cc
38 const int kInstantContainerSpacing
= 20;
39 const int kWebViewWidth
= 500;
40 const int kWebViewHeight
= 105;
41 const int kSearchBoxBorderWidth
= 1;
42 const int kSearchBoxCornerRadius
= 2;
43 const int kSearchBoxWidth
= 490;
44 const int kSearchBoxHeight
= 40;
46 class PlaceHolderButton
: public views::ImageButton
,
47 public views::ButtonListener
{
51 gfx::Canvas
canvas(gfx::Size(kIconSize
, kIconSize
), 1.0f
, true);
53 paint
.setStyle(SkPaint::kFill_Style
);
54 paint
.setColor(SkColorSetRGB(86, 119, 252));
55 paint
.setFlags(SkPaint::kAntiAlias_Flag
);
57 gfx::Point(kIconSize
/ 2, kIconSize
/ 2), kIconSize
/ 2, paint
);
59 scoped_ptr
<gfx::ImageSkia
> image(
60 new gfx::ImageSkia(canvas
.ExtractImageRep()));
61 SetImage(STATE_NORMAL
, image
.get());
65 // views::ButtonListener:
66 virtual void ButtonPressed(views::Button
* sender
,
67 const ui::Event
& event
) OVERRIDE
{
68 // Do nothing: remove these place holders.
71 DISALLOW_COPY_AND_ASSIGN(PlaceHolderButton
);
74 class AppIconButton
: public views::ImageButton
,
75 public views::ButtonListener
{
77 explicit AppIconButton(app_list::AppListItem
* item
)
80 // TODO(mukai): icon should be resized.
81 SetImage(STATE_NORMAL
, &item
->icon());
85 // views::ButtonListener:
86 virtual void ButtonPressed(views::Button
* sender
,
87 const ui::Event
& event
) OVERRIDE
{
88 DCHECK_EQ(sender
, this);
89 item_
->Activate(event
.flags());
92 app_list::AppListItem
* item_
;
94 DISALLOW_COPY_AND_ASSIGN(AppIconButton
);
97 // The background to paint the round rectangle of the view area.
98 class RoundRectBackground
: public views::Background
{
100 RoundRectBackground(SkColor color
, int corner_radius
)
102 corner_radius_(corner_radius
) {}
103 virtual ~RoundRectBackground() {}
106 // views::Background:
107 virtual void Paint(gfx::Canvas
* canvas
, views::View
* view
) const OVERRIDE
{
109 paint
.setStyle(SkPaint::kFill_Style
);
110 paint
.setColor(color_
);
111 canvas
->DrawRoundRect(view
->GetContentsBounds(), corner_radius_
, paint
);
117 DISALLOW_COPY_AND_ASSIGN(RoundRectBackground
);
120 class SearchBoxContainer
: public views::View
{
122 explicit SearchBoxContainer(app_list::SearchBoxView
* search_box
)
123 : search_box_(search_box
) {
124 search_box
->set_background(
125 new RoundRectBackground(SK_ColorWHITE
, kSearchBoxCornerRadius
));
126 search_box
->SetBorder(views::Border::CreateBorderPainter(
127 new views::RoundRectPainter(SK_ColorGRAY
, kSearchBoxCornerRadius
),
128 gfx::Insets(kSearchBoxBorderWidth
, kSearchBoxBorderWidth
,
129 kSearchBoxBorderWidth
, kSearchBoxBorderWidth
)));
130 SetLayoutManager(new views::FillLayout());
131 AddChildView(search_box_
);
133 virtual ~SearchBoxContainer() {}
137 virtual gfx::Size
GetPreferredSize() const OVERRIDE
{
138 return gfx::Size(kSearchBoxWidth
, kSearchBoxHeight
);
141 // Owned by the views hierarchy.
142 app_list::SearchBoxView
* search_box_
;
144 DISALLOW_COPY_AND_ASSIGN(SearchBoxContainer
);
152 const char AthenaStartPageView::kViewClassName
[] = "AthenaStartPageView";
154 AthenaStartPageView::LayoutData::LayoutData()
155 : logo_opacity(1.0f
),
156 background_opacity(1.0f
) {
159 AthenaStartPageView::AthenaStartPageView(
160 app_list::AppListViewDelegate
* view_delegate
)
161 : delegate_(view_delegate
),
163 weak_factory_(this) {
164 background_
= new views::View();
165 background_
->set_background(
166 views::Background::CreateSolidBackground(SK_ColorWHITE
));
167 background_
->SetPaintToLayer(true);
168 background_
->SetFillsBoundsOpaquely(false);
169 AddChildView(background_
);
171 logo_
= view_delegate
->CreateStartPageWebView(
172 gfx::Size(kWebViewWidth
, kWebViewHeight
));
173 logo_
->SetPaintToLayer(true);
174 logo_
->SetFillsBoundsOpaquely(false);
175 logo_
->SetSize(gfx::Size(kWebViewWidth
, kWebViewHeight
));
178 search_results_view_
= new app_list::SearchResultListView(
179 NULL
, view_delegate
);
180 // search_results_view_'s size will shrink after settings results.
181 search_results_height_
= static_cast<views::View
*>(
182 search_results_view_
)->GetHeightForWidth(kSearchBoxWidth
);
183 search_results_view_
->SetResults(view_delegate
->GetModel()->results());
185 search_results_view_
->SetVisible(false);
186 search_results_view_
->SetPaintToLayer(true);
187 search_results_view_
->SetFillsBoundsOpaquely(false);
188 AddChildView(search_results_view_
);
190 app_icon_container_
= new views::View();
191 AddChildView(app_icon_container_
);
192 app_icon_container_
->SetPaintToLayer(true);
193 app_icon_container_
->layer()->SetFillsBoundsOpaquely(false);
194 app_icon_container_
->SetLayoutManager(new views::BoxLayout(
195 views::BoxLayout::kHorizontal
, 0, 0, kIconMargin
));
196 app_list::AppListItemList
* top_level
=
197 view_delegate
->GetModel()->top_level_item_list();
198 for (size_t i
= 0; i
< std::min(top_level
->item_count(), kMaxIconNum
); ++i
)
199 app_icon_container_
->AddChildView(new AppIconButton(top_level
->item_at(i
)));
200 app_icon_container_
->SetSize(app_icon_container_
->GetPreferredSize());
202 control_icon_container_
= new views::View();
203 control_icon_container_
->SetPaintToLayer(true);
204 control_icon_container_
->SetFillsBoundsOpaquely(false);
205 AddChildView(control_icon_container_
);
206 control_icon_container_
->SetLayoutManager(new views::BoxLayout(
207 views::BoxLayout::kHorizontal
, 0, 0, kIconMargin
));
208 for (size_t i
= 0; i
< kMaxIconNum
; ++i
)
209 control_icon_container_
->AddChildView(new PlaceHolderButton());
210 control_icon_container_
->SetSize(control_icon_container_
->GetPreferredSize());
212 search_box_view_
= new app_list::SearchBoxView(this, view_delegate
);
213 search_box_view_
->set_contents_view(this);
214 search_box_container_
= new SearchBoxContainer(search_box_view_
);
215 search_box_container_
->SetPaintToLayer(true);
216 search_box_container_
->SetFillsBoundsOpaquely(false);
217 search_box_container_
->SetSize(search_box_container_
->GetPreferredSize());
218 AddChildView(search_box_container_
);
221 AthenaStartPageView::~AthenaStartPageView() {}
223 void AthenaStartPageView::RequestFocusOnSearchBox() {
224 search_box_view_
->search_box()->RequestFocus();
227 void AthenaStartPageView::SetLayoutState(float layout_state
) {
228 layout_state_
= layout_state
;
232 void AthenaStartPageView::SetLayoutStateWithAnimation(float layout_state
) {
233 ui::ScopedLayerAnimationSettings
logo(logo_
->layer()->GetAnimator());
234 ui::ScopedLayerAnimationSettings
search_box(
235 search_box_container_
->layer()->GetAnimator());
236 ui::ScopedLayerAnimationSettings
icons(
237 app_icon_container_
->layer()->GetAnimator());
238 ui::ScopedLayerAnimationSettings
controls(
239 control_icon_container_
->layer()->GetAnimator());
241 logo
.SetTweenType(gfx::Tween::EASE_IN_OUT
);
242 search_box
.SetTweenType(gfx::Tween::EASE_IN_OUT
);
243 icons
.SetTweenType(gfx::Tween::EASE_IN_OUT
);
244 controls
.SetTweenType(gfx::Tween::EASE_IN_OUT
);
246 SetLayoutState(layout_state
);
249 AthenaStartPageView::LayoutData
AthenaStartPageView::CreateBottomBounds(
252 state
.icons
.set_size(app_icon_container_
->size());
253 state
.icons
.set_x(kIconMargin
);
254 state
.icons
.set_y(kIconMargin
);
256 state
.controls
.set_size(control_icon_container_
->size());
257 state
.controls
.set_x(width
- kIconMargin
- state
.controls
.width());
258 state
.controls
.set_y(kIconMargin
);
260 state
.search_box
.set_size(search_box_container_
->size());
261 state
.search_box
.set_x((width
- state
.search_box
.width()) / 2);
262 state
.search_box
.set_y((kHomeCardHeight
- state
.search_box
.height()) / 2);
264 state
.logo_opacity
= 0.0f
;
265 state
.background_opacity
= 0.9f
;
269 AthenaStartPageView::LayoutData
AthenaStartPageView::CreateCenteredBounds(
273 state
.search_box
.set_size(search_box_container_
->size());
274 state
.search_box
.set_x((width
- state
.search_box
.width()) / 2);
275 state
.search_box
.set_y(logo_
->bounds().bottom() + kInstantContainerSpacing
);
277 state
.icons
.set_size(app_icon_container_
->size());
278 state
.icons
.set_x(width
/ 2 - state
.icons
.width() - kIconMargin
/ 2);
279 state
.icons
.set_y(state
.search_box
.bottom() + kInstantContainerSpacing
);
281 state
.controls
.set_size(control_icon_container_
->size());
282 state
.controls
.set_x(width
/ 2 + kIconMargin
/ 2 + kIconMargin
% 2);
283 state
.controls
.set_y(state
.icons
.y());
285 state
.logo_opacity
= 1.0f
;
286 state
.background_opacity
= 1.0f
;
290 void AthenaStartPageView::LayoutSearchResults(bool should_show_search_results
) {
291 if (should_show_search_results
==
292 search_results_view_
->layer()->GetTargetVisibility()) {
295 if (GetContentsBounds().height() <= kHomeCardHeight
) {
296 search_results_view_
->SetVisible(false);
301 gfx::Rect search_box_bounds
= search_box_container_
->bounds();
302 if (!search_results_view_
->visible()) {
303 search_results_view_
->SetVisible(true);
304 search_results_view_
->SetBounds(
305 search_box_bounds
.x(), search_box_bounds
.bottom(),
306 search_box_bounds
.width(), 0);
308 logo_
->SetVisible(true);
311 ui::ScopedLayerAnimationSettings
logo_settings(
312 logo_
->layer()->GetAnimator());
313 logo_settings
.SetTweenType(gfx::Tween::EASE_IN_OUT
);
314 logo_settings
.AddObserver(new ui::ClosureAnimationObserver(
315 base::Bind(&AthenaStartPageView::OnSearchResultLayoutAnimationCompleted
,
316 weak_factory_
.GetWeakPtr(),
317 should_show_search_results
)));
319 ui::ScopedLayerAnimationSettings
search_box_settings(
320 search_box_container_
->layer()->GetAnimator());
321 search_box_settings
.SetTweenType(gfx::Tween::EASE_IN_OUT
);
323 ui::ScopedLayerAnimationSettings
search_results_settings(
324 search_results_view_
->layer()->GetAnimator());
325 search_results_settings
.SetTweenType(gfx::Tween::EASE_IN_OUT
);
327 if (should_show_search_results
) {
328 logo_
->layer()->SetOpacity(0.0f
);
329 search_box_bounds
.set_y(
330 search_box_bounds
.y() - search_results_height_
-
331 kInstantContainerSpacing
);
332 search_box_container_
->SetBoundsRect(search_box_bounds
);
333 search_results_view_
->SetBounds(
334 search_box_bounds
.x(),
335 search_box_bounds
.bottom() + kInstantContainerSpacing
,
336 search_box_bounds
.width(),
337 search_results_height_
);
339 logo_
->layer()->SetOpacity(1.0f
);
340 search_box_bounds
.set_y(
341 logo_
->bounds().bottom() + kInstantContainerSpacing
);
342 search_box_container_
->SetBoundsRect(search_box_bounds
);
344 gfx::Rect search_results_bounds
= search_results_view_
->bounds();
345 search_results_bounds
.set_y(search_results_bounds
.bottom());
346 search_results_bounds
.set_height(0);
347 search_results_view_
->SetBoundsRect(search_results_bounds
);
352 void AthenaStartPageView::OnSearchResultLayoutAnimationCompleted(
353 bool should_show_search_results
) {
354 logo_
->SetVisible(!should_show_search_results
);
355 search_results_view_
->SetVisible(should_show_search_results
);
358 void AthenaStartPageView::Layout() {
359 search_results_view_
->SetVisible(false);
360 gfx::Rect
logo_bounds(x() + width() / 2 - kWebViewWidth
/ 2, y() + kTopMargin
,
361 kWebViewWidth
, kWebViewHeight
);
362 logo_
->SetBoundsRect(logo_bounds
);
364 LayoutData bottom_bounds
= CreateBottomBounds(width());
365 LayoutData centered_bounds
= CreateCenteredBounds(width());
367 logo_
->layer()->SetOpacity(gfx::Tween::FloatValueBetween(
368 gfx::Tween::CalculateValue(gfx::Tween::EASE_IN_2
, layout_state_
),
369 bottom_bounds
.logo_opacity
, centered_bounds
.logo_opacity
));
370 logo_
->SetVisible(logo_
->layer()->GetTargetOpacity() != 0.0f
);
372 app_icon_container_
->SetBoundsRect(gfx::Tween::RectValueBetween(
373 layout_state_
, bottom_bounds
.icons
, centered_bounds
.icons
));
374 control_icon_container_
->SetBoundsRect(gfx::Tween::RectValueBetween(
375 layout_state_
, bottom_bounds
.controls
, centered_bounds
.controls
));
376 search_box_container_
->SetBoundsRect(gfx::Tween::RectValueBetween(
377 layout_state_
, bottom_bounds
.search_box
, centered_bounds
.search_box
));
379 background_
->SetBoundsRect(bounds());
380 background_
->layer()->SetOpacity(gfx::Tween::FloatValueBetween(
382 bottom_bounds
.background_opacity
,
383 centered_bounds
.background_opacity
));
386 bool AthenaStartPageView::OnKeyPressed(const ui::KeyEvent
& key_event
) {
387 return search_results_view_
->visible() &&
388 search_results_view_
->OnKeyPressed(key_event
);
391 void AthenaStartPageView::QueryChanged(app_list::SearchBoxView
* sender
) {
392 delegate_
->StartSearch();
394 base::string16 query
;
395 base::TrimWhitespace(
396 delegate_
->GetModel()->search_box()->text(), base::TRIM_ALL
, &query
);
399 search_results_view_
->SetSelectedIndex(0);
401 LayoutSearchResults(!query
.empty());
404 } // namespace athena