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.
4
5#include "athena/home/athena_start_page_view.h"
6
7#include "athena/home/home_card_constants.h"
8#include "athena/system/public/system_ui.h"
9#include "base/bind.h"
10#include "base/strings/string_util.h"
11#include "third_party/skia/include/core/SkPaint.h"
12#include "third_party/skia/include/core/SkPath.h"
13#include "ui/app_list/app_list_item.h"
14#include "ui/app_list/app_list_item_list.h"
15#include "ui/app_list/app_list_model.h"
16#include "ui/app_list/app_list_view_delegate.h"
17#include "ui/app_list/search_box_model.h"
18#include "ui/app_list/views/search_box_view.h"
19#include "ui/app_list/views/search_result_list_view.h"
20#include "ui/compositor/closure_animation_observer.h"
21#include "ui/compositor/scoped_layer_animation_settings.h"
22#include "ui/gfx/canvas.h"
23#include "ui/views/background.h"
24#include "ui/views/border.h"
25#include "ui/views/controls/textfield/textfield.h"
26#include "ui/views/layout/box_layout.h"
27#include "ui/views/layout/fill_layout.h"
28#include "ui/views/round_rect_painter.h"
29
30namespace {
31
32const size_t kMaxIconNum = 3;
33const int kIconSize = 50;
34const int kIconMargin = 25;
35
36const int kTopMargin = 100;
37
38// Copied from ui/app_list/views/start_page_view.cc
39const int kInstantContainerSpacing = 20;
40const int kWebViewWidth = 500;
41const int kWebViewHeight = 105;
42const int kSearchBoxBorderWidth = 1;
43const int kSearchBoxCornerRadius = 2;
44
45// Taken from the mock. The width is not specified by pixel but the search box
46// covers 6 icons with margin.
47const int kSearchBoxWidth = kIconSize * 6 + kIconMargin * 7;
48const int kSearchBoxHeight = 40;
49
50gfx::Size GetIconContainerSize() {
51  return gfx::Size(kIconSize *  kMaxIconNum + kIconMargin * (kMaxIconNum - 1),
52                   kIconSize);
53}
54
55class PlaceHolderButton : public views::ImageButton,
56                          public views::ButtonListener {
57 public:
58  PlaceHolderButton()
59      : ImageButton(this) {
60    gfx::Canvas canvas(gfx::Size(kIconSize, kIconSize), 1.0f, true);
61    SkPaint paint;
62    paint.setStyle(SkPaint::kFill_Style);
63    paint.setColor(SkColorSetRGB(86, 119, 252));
64    paint.setFlags(SkPaint::kAntiAlias_Flag);
65    canvas.DrawCircle(
66        gfx::Point(kIconSize / 2, kIconSize / 2), kIconSize / 2, paint);
67
68    scoped_ptr<gfx::ImageSkia> image(
69        new gfx::ImageSkia(canvas.ExtractImageRep()));
70    SetImage(STATE_NORMAL, image.get());
71  }
72
73 private:
74  // views::ButtonListener:
75  virtual void ButtonPressed(views::Button* sender,
76                             const ui::Event& event) OVERRIDE {
77    // Do nothing: remove these place holders.
78  }
79
80  DISALLOW_COPY_AND_ASSIGN(PlaceHolderButton);
81};
82
83class AppIconButton : public views::ImageButton,
84                      public views::ButtonListener {
85 public:
86  explicit AppIconButton(app_list::AppListItem* item)
87      : ImageButton(this),
88        item_(item) {
89    // TODO(mukai): icon should be resized.
90    SetImage(STATE_NORMAL, &item->icon());
91  }
92
93 private:
94  // views::ButtonListener:
95  virtual void ButtonPressed(views::Button* sender,
96                             const ui::Event& event) OVERRIDE {
97    DCHECK_EQ(sender, this);
98    item_->Activate(event.flags());
99  }
100
101  app_list::AppListItem* item_;
102
103  DISALLOW_COPY_AND_ASSIGN(AppIconButton);
104};
105
106// The background to paint the round rectangle of the view area.
107class RoundRectBackground : public views::Background {
108 public:
109  RoundRectBackground(SkColor color, int corner_radius)
110      : color_(color),
111        corner_radius_(corner_radius) {}
112  virtual ~RoundRectBackground() {}
113
114 private:
115  // views::Background:
116  virtual void Paint(gfx::Canvas* canvas, views::View* view) const OVERRIDE {
117    SkPaint paint;
118    paint.setStyle(SkPaint::kFill_Style);
119    paint.setColor(color_);
120    canvas->DrawRoundRect(view->GetContentsBounds(), corner_radius_, paint);
121  }
122
123  SkColor color_;
124  int corner_radius_;
125
126  DISALLOW_COPY_AND_ASSIGN(RoundRectBackground);
127};
128
129class SearchBoxContainer : public views::View {
130 public:
131  explicit SearchBoxContainer(app_list::SearchBoxView* search_box)
132      : search_box_(search_box) {
133    search_box->set_background(
134        new RoundRectBackground(SK_ColorWHITE, kSearchBoxCornerRadius));
135    search_box->SetBorder(views::Border::CreateBorderPainter(
136        new views::RoundRectPainter(SK_ColorGRAY, kSearchBoxCornerRadius),
137        gfx::Insets(kSearchBoxBorderWidth, kSearchBoxBorderWidth,
138                    kSearchBoxBorderWidth, kSearchBoxBorderWidth)));
139    SetLayoutManager(new views::FillLayout());
140    AddChildView(search_box_);
141  }
142  virtual ~SearchBoxContainer() {}
143
144 private:
145  // views::View:
146  virtual gfx::Size GetPreferredSize() const OVERRIDE {
147    return gfx::Size(kSearchBoxWidth, kSearchBoxHeight);
148  }
149
150  // Owned by the views hierarchy.
151  app_list::SearchBoxView* search_box_;
152
153  DISALLOW_COPY_AND_ASSIGN(SearchBoxContainer);
154};
155
156}  // namespace
157
158namespace athena {
159
160// static
161const char AthenaStartPageView::kViewClassName[] = "AthenaStartPageView";
162
163AthenaStartPageView::LayoutData::LayoutData()
164    : system_info_opacity(1.0f),
165      logo_opacity(1.0f),
166      background_opacity(1.0f) {
167}
168
169AthenaStartPageView::AthenaStartPageView(
170    app_list::AppListViewDelegate* view_delegate)
171    : delegate_(view_delegate),
172      layout_state_(0.0f),
173      weak_factory_(this) {
174  background_ = new views::View();
175  background_->set_background(
176      views::Background::CreateSolidBackground(SK_ColorWHITE));
177  background_->SetPaintToLayer(true);
178  background_->SetFillsBoundsOpaquely(false);
179  AddChildView(background_);
180
181  system_info_view_ =
182      SystemUI::Get()->CreateSystemInfoView(SystemUI::COLOR_SCHEME_DARK);
183  system_info_view_->SetPaintToLayer(true);
184  system_info_view_->SetFillsBoundsOpaquely(false);
185  AddChildView(system_info_view_);
186
187  logo_ = view_delegate->CreateStartPageWebView(
188      gfx::Size(kWebViewWidth, kWebViewHeight));
189  logo_->SetPaintToLayer(true);
190  logo_->SetFillsBoundsOpaquely(false);
191  logo_->SetSize(gfx::Size(kWebViewWidth, kWebViewHeight));
192  AddChildView(logo_);
193
194  search_results_view_ = new app_list::SearchResultListView(
195      NULL, view_delegate);
196  // search_results_view_'s size will shrink after settings results.
197  search_results_height_ = static_cast<views::View*>(
198      search_results_view_)->GetHeightForWidth(kSearchBoxWidth);
199  search_results_view_->SetResults(view_delegate->GetModel()->results());
200
201  search_results_view_->SetVisible(false);
202  search_results_view_->SetPaintToLayer(true);
203  search_results_view_->SetFillsBoundsOpaquely(false);
204  AddChildView(search_results_view_);
205
206  app_icon_container_ = new views::View();
207  AddChildView(app_icon_container_);
208  app_icon_container_->SetPaintToLayer(true);
209  app_icon_container_->layer()->SetFillsBoundsOpaquely(false);
210  app_icon_container_->SetLayoutManager(new views::BoxLayout(
211      views::BoxLayout::kHorizontal, 0, 0, kIconMargin));
212  app_list::AppListItemList* top_level =
213      view_delegate->GetModel()->top_level_item_list();
214  for (size_t i = 0; i < std::min(top_level->item_count(), kMaxIconNum); ++i)
215    app_icon_container_->AddChildView(new AppIconButton(top_level->item_at(i)));
216  app_icon_container_->SetSize(GetIconContainerSize());
217
218  control_icon_container_ = new views::View();
219  control_icon_container_->SetPaintToLayer(true);
220  control_icon_container_->SetFillsBoundsOpaquely(false);
221  AddChildView(control_icon_container_);
222  control_icon_container_->SetLayoutManager(new views::BoxLayout(
223      views::BoxLayout::kHorizontal, 0, 0, kIconMargin));
224  for (size_t i = 0; i < kMaxIconNum; ++i)
225    control_icon_container_->AddChildView(new PlaceHolderButton());
226  control_icon_container_->SetSize(GetIconContainerSize());
227
228  search_box_view_ = new app_list::SearchBoxView(this, view_delegate);
229  search_box_view_->set_contents_view(this);
230  search_box_view_->search_box()->set_id(kHomeCardSearchBoxId);
231  search_box_container_ = new SearchBoxContainer(search_box_view_);
232  search_box_container_->SetPaintToLayer(true);
233  search_box_container_->SetFillsBoundsOpaquely(false);
234  search_box_container_->SetSize(search_box_container_->GetPreferredSize());
235  AddChildView(search_box_container_);
236}
237
238AthenaStartPageView::~AthenaStartPageView() {}
239
240void AthenaStartPageView::RequestFocusOnSearchBox() {
241  search_box_view_->search_box()->RequestFocus();
242}
243
244void AthenaStartPageView::SetLayoutState(float layout_state) {
245  layout_state_ = layout_state;
246  Layout();
247}
248
249void AthenaStartPageView::SetLayoutStateWithAnimation(
250    float layout_state,
251    gfx::Tween::Type tween_type) {
252  ui::ScopedLayerAnimationSettings system_info(
253      system_info_view_->layer()->GetAnimator());
254  ui::ScopedLayerAnimationSettings logo(logo_->layer()->GetAnimator());
255  ui::ScopedLayerAnimationSettings search_box(
256      search_box_container_->layer()->GetAnimator());
257  ui::ScopedLayerAnimationSettings icons(
258      app_icon_container_->layer()->GetAnimator());
259  ui::ScopedLayerAnimationSettings controls(
260      control_icon_container_->layer()->GetAnimator());
261
262  system_info.SetTweenType(tween_type);
263  logo.SetTweenType(tween_type);
264  search_box.SetTweenType(tween_type);
265  icons.SetTweenType(tween_type);
266  controls.SetTweenType(tween_type);
267
268  SetLayoutState(layout_state);
269}
270
271AthenaStartPageView::LayoutData AthenaStartPageView::CreateBottomBounds(
272    int width) {
273  LayoutData state;
274  state.icons.set_size(app_icon_container_->size());
275  state.icons.set_x(kIconMargin);
276  state.icons.set_y(kIconMargin);
277
278  state.controls.set_size(control_icon_container_->size());
279  state.controls.set_x(width - kIconMargin - state.controls.width());
280  state.controls.set_y(kIconMargin);
281
282  int search_box_max_width =
283      state.controls.x() - state.icons.right() - kIconMargin * 2;
284  state.search_box.set_width(std::min(search_box_max_width, kSearchBoxWidth));
285  state.search_box.set_height(search_box_container_->height());
286  state.search_box.set_x((width - state.search_box.width()) / 2);
287  state.search_box.set_y((kHomeCardHeight - state.search_box.height()) / 2);
288
289  state.system_info_opacity = 0.0f;
290  state.logo_opacity = 0.0f;
291  state.background_opacity = 0.9f;
292  return state;
293}
294
295AthenaStartPageView::LayoutData AthenaStartPageView::CreateCenteredBounds(
296    int width) {
297  LayoutData state;
298
299  state.search_box.set_size(search_box_container_->GetPreferredSize());
300  state.search_box.set_x((width - state.search_box.width()) / 2);
301  state.search_box.set_y(logo_->bounds().bottom() + kInstantContainerSpacing);
302
303  state.icons.set_size(app_icon_container_->size());
304  state.icons.set_x(width / 2 - state.icons.width() - kIconMargin / 2);
305  state.icons.set_y(state.search_box.bottom() + kInstantContainerSpacing);
306
307  state.controls.set_size(control_icon_container_->size());
308  state.controls.set_x(width / 2 + kIconMargin / 2 + kIconMargin % 2);
309  state.controls.set_y(state.icons.y());
310
311  state.system_info_opacity = 1.0f;
312  state.logo_opacity = 1.0f;
313  state.background_opacity = 1.0f;
314  return state;
315}
316
317void AthenaStartPageView::LayoutSearchResults(bool should_show_search_results) {
318  if (should_show_search_results ==
319      search_results_view_->layer()->GetTargetVisibility()) {
320    return;
321  }
322  if (GetContentsBounds().height() <= kHomeCardHeight) {
323    search_results_view_->SetVisible(false);
324    Layout();
325    return;
326  }
327
328  gfx::Rect search_box_bounds = search_box_container_->bounds();
329  if (!search_results_view_->visible()) {
330    search_results_view_->SetVisible(true);
331    search_results_view_->SetBounds(
332        search_box_bounds.x(), search_box_bounds.bottom(),
333        search_box_bounds.width(), 0);
334  }
335  logo_->SetVisible(true);
336
337  {
338    ui::ScopedLayerAnimationSettings logo_settings(
339        logo_->layer()->GetAnimator());
340    logo_settings.SetTweenType(gfx::Tween::EASE_IN_OUT);
341    logo_settings.AddObserver(new ui::ClosureAnimationObserver(
342        base::Bind(&AthenaStartPageView::OnSearchResultLayoutAnimationCompleted,
343                   weak_factory_.GetWeakPtr(),
344                   should_show_search_results)));
345
346    ui::ScopedLayerAnimationSettings search_box_settings(
347        search_box_container_->layer()->GetAnimator());
348    search_box_settings.SetTweenType(gfx::Tween::EASE_IN_OUT);
349
350    ui::ScopedLayerAnimationSettings search_results_settings(
351        search_results_view_->layer()->GetAnimator());
352    search_results_settings.SetTweenType(gfx::Tween::EASE_IN_OUT);
353
354    if (should_show_search_results) {
355      logo_->layer()->SetOpacity(0.0f);
356      search_box_bounds.set_y(
357          search_box_bounds.y() - search_results_height_ -
358          kInstantContainerSpacing);
359      search_box_container_->SetBoundsRect(search_box_bounds);
360      search_results_view_->SetBounds(
361          search_box_bounds.x(),
362          search_box_bounds.bottom() + kInstantContainerSpacing,
363          search_box_bounds.width(),
364          search_results_height_);
365    } else {
366      logo_->layer()->SetOpacity(1.0f);
367      search_box_bounds.set_y(
368          logo_->bounds().bottom() + kInstantContainerSpacing);
369      search_box_container_->SetBoundsRect(search_box_bounds);
370
371      gfx::Rect search_results_bounds = search_results_view_->bounds();
372      search_results_bounds.set_y(search_results_bounds.bottom());
373      search_results_bounds.set_height(0);
374      search_results_view_->SetBoundsRect(search_results_bounds);
375    }
376  }
377}
378
379void AthenaStartPageView::OnSearchResultLayoutAnimationCompleted(
380    bool should_show_search_results) {
381  logo_->SetVisible(!should_show_search_results);
382  search_results_view_->SetVisible(should_show_search_results);
383}
384
385void AthenaStartPageView::Layout() {
386  search_results_view_->SetVisible(false);
387
388  system_info_view_->SetBounds(
389      0, 0, width(), system_info_view_->GetPreferredSize().height());
390
391  gfx::Rect logo_bounds(x() + width() / 2 - kWebViewWidth / 2, y() + kTopMargin,
392                        kWebViewWidth, kWebViewHeight);
393  logo_->SetBoundsRect(logo_bounds);
394
395  LayoutData bottom_bounds = CreateBottomBounds(width());
396  LayoutData centered_bounds = CreateCenteredBounds(width());
397
398  system_info_view_->layer()->SetOpacity(gfx::Tween::FloatValueBetween(
399      gfx::Tween::CalculateValue(gfx::Tween::EASE_IN_2, layout_state_),
400      bottom_bounds.system_info_opacity, centered_bounds.system_info_opacity));
401  system_info_view_->SetVisible(
402      system_info_view_->layer()->GetTargetOpacity() != 0.0f);
403
404  logo_->layer()->SetOpacity(gfx::Tween::FloatValueBetween(
405      gfx::Tween::CalculateValue(gfx::Tween::EASE_IN_2, layout_state_),
406      bottom_bounds.logo_opacity, centered_bounds.logo_opacity));
407  logo_->SetVisible(logo_->layer()->GetTargetOpacity() != 0.0f);
408
409  app_icon_container_->SetBoundsRect(gfx::Tween::RectValueBetween(
410      layout_state_, bottom_bounds.icons, centered_bounds.icons));
411  control_icon_container_->SetBoundsRect(gfx::Tween::RectValueBetween(
412      layout_state_, bottom_bounds.controls, centered_bounds.controls));
413  search_box_container_->SetBoundsRect(gfx::Tween::RectValueBetween(
414      layout_state_, bottom_bounds.search_box, centered_bounds.search_box));
415
416  background_->SetBoundsRect(bounds());
417  background_->layer()->SetOpacity(gfx::Tween::FloatValueBetween(
418      layout_state_,
419      bottom_bounds.background_opacity,
420      centered_bounds.background_opacity));
421}
422
423bool AthenaStartPageView::OnKeyPressed(const ui::KeyEvent& key_event) {
424  return search_results_view_->visible() &&
425      search_results_view_->OnKeyPressed(key_event);
426}
427
428void AthenaStartPageView::QueryChanged(app_list::SearchBoxView* sender) {
429  delegate_->StartSearch();
430
431  base::string16 query;
432  base::TrimWhitespace(
433      delegate_->GetModel()->search_box()->text(), base::TRIM_ALL, &query);
434
435  if (!query.empty())
436    search_results_view_->SetSelectedIndex(0);
437
438  LayoutSearchResults(!query.empty());
439}
440
441}  // namespace athena
442