Home | History | Annotate | Download | only in home
      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 
     30 namespace {
     31 
     32 const size_t kMaxIconNum = 3;
     33 const int kIconSize = 50;
     34 const int kIconMargin = 25;
     35 
     36 const int kTopMargin = 100;
     37 
     38 // Copied from ui/app_list/views/start_page_view.cc
     39 const int kInstantContainerSpacing = 20;
     40 const int kWebViewWidth = 500;
     41 const int kWebViewHeight = 105;
     42 const int kSearchBoxBorderWidth = 1;
     43 const 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.
     47 const int kSearchBoxWidth = kIconSize * 6 + kIconMargin * 7;
     48 const int kSearchBoxHeight = 40;
     49 
     50 gfx::Size GetIconContainerSize() {
     51   return gfx::Size(kIconSize *  kMaxIconNum + kIconMargin * (kMaxIconNum - 1),
     52                    kIconSize);
     53 }
     54 
     55 class 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 
     83 class 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.
    107 class 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 
    129 class 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 
    158 namespace athena {
    159 
    160 // static
    161 const char AthenaStartPageView::kViewClassName[] = "AthenaStartPageView";
    162 
    163 AthenaStartPageView::LayoutData::LayoutData()
    164     : system_info_opacity(1.0f),
    165       logo_opacity(1.0f),
    166       background_opacity(1.0f) {
    167 }
    168 
    169 AthenaStartPageView::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 
    238 AthenaStartPageView::~AthenaStartPageView() {}
    239 
    240 void AthenaStartPageView::RequestFocusOnSearchBox() {
    241   search_box_view_->search_box()->RequestFocus();
    242 }
    243 
    244 void AthenaStartPageView::SetLayoutState(float layout_state) {
    245   layout_state_ = layout_state;
    246   Layout();
    247 }
    248 
    249 void 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 
    271 AthenaStartPageView::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 
    295 AthenaStartPageView::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 
    317 void 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 
    379 void 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 
    385 void 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 
    423 bool AthenaStartPageView::OnKeyPressed(const ui::KeyEvent& key_event) {
    424   return search_results_view_->visible() &&
    425       search_results_view_->OnKeyPressed(key_event);
    426 }
    427 
    428 void 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