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