Allow scroll events to open custom launcher pages.
[chromium-blink-merge.git] / ui / app_list / views / contents_view.cc
bloba076519be2e29ba4b49d041d0db243d5ed2f003a
1 // Copyright (c) 2012 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/contents_view.h"
7 #include <algorithm>
8 #include <vector>
10 #include "base/logging.h"
11 #include "ui/app_list/app_list_constants.h"
12 #include "ui/app_list/app_list_switches.h"
13 #include "ui/app_list/app_list_view_delegate.h"
14 #include "ui/app_list/views/app_list_folder_view.h"
15 #include "ui/app_list/views/app_list_main_view.h"
16 #include "ui/app_list/views/apps_container_view.h"
17 #include "ui/app_list/views/apps_grid_view.h"
18 #include "ui/app_list/views/contents_animator.h"
19 #include "ui/app_list/views/search_box_view.h"
20 #include "ui/app_list/views/search_result_list_view.h"
21 #include "ui/app_list/views/search_result_page_view.h"
22 #include "ui/app_list/views/search_result_tile_item_list_view.h"
23 #include "ui/app_list/views/start_page_view.h"
24 #include "ui/events/event.h"
25 #include "ui/resources/grit/ui_resources.h"
26 #include "ui/views/view_model.h"
27 #include "ui/views/view_model_utils.h"
28 #include "ui/views/widget/widget.h"
30 namespace app_list {
32 ContentsView::ContentsView(AppListMainView* app_list_main_view)
33 : apps_container_view_(nullptr),
34 search_results_page_view_(nullptr),
35 start_page_view_(nullptr),
36 custom_page_view_(nullptr),
37 app_list_main_view_(app_list_main_view),
38 view_model_(new views::ViewModel),
39 page_before_search_(0) {
40 pagination_model_.SetTransitionDurations(kPageTransitionDurationInMs,
41 kOverscrollPageTransitionDurationMs);
42 pagination_model_.AddObserver(this);
45 ContentsView::~ContentsView() {
46 pagination_model_.RemoveObserver(this);
49 void ContentsView::Init(AppListModel* model) {
50 DCHECK(model);
52 AppListViewDelegate* view_delegate = app_list_main_view_->view_delegate();
54 if (app_list::switches::IsExperimentalAppListEnabled()) {
55 std::vector<views::View*> custom_page_views =
56 view_delegate->CreateCustomPageWebViews(GetLocalBounds().size());
57 for (std::vector<views::View*>::const_iterator it =
58 custom_page_views.begin();
59 it != custom_page_views.end();
60 ++it) {
61 // Only the first launcher page is considered to represent
62 // STATE_CUSTOM_LAUNCHER_PAGE.
63 if (it == custom_page_views.begin()) {
64 custom_page_view_ = *it;
66 AddLauncherPage(*it, AppListModel::STATE_CUSTOM_LAUNCHER_PAGE);
67 } else {
68 AddLauncherPage(*it);
72 // Start page.
73 start_page_view_ = new StartPageView(app_list_main_view_, view_delegate);
74 AddLauncherPage(start_page_view_, AppListModel::STATE_START);
77 // Search results UI.
78 search_results_page_view_ = new SearchResultPageView();
80 AppListModel::SearchResults* results = view_delegate->GetModel()->results();
81 search_results_page_view_->AddSearchResultContainerView(
82 results, new SearchResultListView(app_list_main_view_, view_delegate));
84 if (app_list::switches::IsExperimentalAppListEnabled()) {
85 search_results_page_view_->AddSearchResultContainerView(
86 results,
87 new SearchResultTileItemListView(GetSearchBoxView()->search_box()));
89 AddLauncherPage(search_results_page_view_,
90 AppListModel::STATE_SEARCH_RESULTS);
92 apps_container_view_ = new AppsContainerView(app_list_main_view_, model);
94 AddLauncherPage(apps_container_view_, AppListModel::STATE_APPS);
96 int initial_page_index = app_list::switches::IsExperimentalAppListEnabled()
97 ? GetPageIndexForState(AppListModel::STATE_START)
98 : GetPageIndexForState(AppListModel::STATE_APPS);
99 DCHECK_GE(initial_page_index, 0);
101 page_before_search_ = initial_page_index;
102 // Must only call SetTotalPages once all the launcher pages have been added
103 // (as it will trigger a SelectedPageChanged call).
104 pagination_model_.SetTotalPages(view_model_->view_size());
105 pagination_model_.SelectPage(initial_page_index, false);
107 ActivePageChanged();
109 // Populate the contents animators.
110 AddAnimator(AppListModel::STATE_START, AppListModel::STATE_APPS,
111 scoped_ptr<ContentsAnimator>(new StartToAppsAnimator(this)));
112 AddAnimator(AppListModel::STATE_START,
113 AppListModel::STATE_CUSTOM_LAUNCHER_PAGE,
114 scoped_ptr<ContentsAnimator>(new StartToCustomAnimator(this)));
115 default_animator_.reset(new DefaultAnimator(this));
118 void ContentsView::CancelDrag() {
119 if (apps_container_view_->apps_grid_view()->has_dragged_view())
120 apps_container_view_->apps_grid_view()->EndDrag(true);
121 if (apps_container_view_->app_list_folder_view()
122 ->items_grid_view()
123 ->has_dragged_view()) {
124 apps_container_view_->app_list_folder_view()->items_grid_view()->EndDrag(
125 true);
129 void ContentsView::SetDragAndDropHostOfCurrentAppList(
130 ApplicationDragAndDropHost* drag_and_drop_host) {
131 apps_container_view_->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host);
134 void ContentsView::SetActivePage(int page_index) {
135 SetActivePage(page_index, true);
138 void ContentsView::SetActivePage(int page_index, bool animate) {
139 if (GetActivePageIndex() == page_index)
140 return;
142 SetActivePageInternal(page_index, false, animate);
145 int ContentsView::GetActivePageIndex() const {
146 // The active page is changed at the beginning of an animation, not the end.
147 return pagination_model_.SelectedTargetPage();
150 AppListModel::State ContentsView::GetActiveState() const {
151 return GetStateForPageIndex(GetActivePageIndex());
154 bool ContentsView::IsStateActive(AppListModel::State state) const {
155 int active_page_index = GetActivePageIndex();
156 return active_page_index >= 0 &&
157 GetPageIndexForState(state) == active_page_index;
160 int ContentsView::GetPageIndexForState(AppListModel::State state) const {
161 // Find the index of the view corresponding to the given state.
162 std::map<AppListModel::State, int>::const_iterator it =
163 state_to_view_.find(state);
164 if (it == state_to_view_.end())
165 return -1;
167 return it->second;
170 AppListModel::State ContentsView::GetStateForPageIndex(int index) const {
171 std::map<int, AppListModel::State>::const_iterator it =
172 view_to_state_.find(index);
173 if (it == view_to_state_.end())
174 return AppListModel::INVALID_STATE;
176 return it->second;
179 int ContentsView::NumLauncherPages() const {
180 return pagination_model_.total_pages();
183 void ContentsView::SetActivePageInternal(int page_index,
184 bool show_search_results,
185 bool animate) {
186 if (!show_search_results)
187 page_before_search_ = page_index;
188 // Start animating to the new page.
189 pagination_model_.SelectPage(page_index, animate);
190 ActivePageChanged();
192 if (!animate)
193 Layout();
196 void ContentsView::ActivePageChanged() {
197 AppListModel::State state = AppListModel::INVALID_STATE;
199 // TODO(calamity): This does not report search results being shown in the
200 // experimental app list as a boolean is currently used to indicate whether
201 // search results are showing. See http://crbug.com/427787/.
202 std::map<int, AppListModel::State>::const_iterator it =
203 view_to_state_.find(pagination_model_.SelectedTargetPage());
204 if (it != view_to_state_.end())
205 state = it->second;
207 app_list_main_view_->model()->SetState(state);
209 if (switches::IsExperimentalAppListEnabled()) {
210 DCHECK(start_page_view_);
212 // Set the visibility of the search box's back button.
213 app_list_main_view_->search_box_view()->back_button()->SetVisible(
214 state != AppListModel::STATE_START);
215 app_list_main_view_->search_box_view()->Layout();
217 // Whenever the page changes, the custom launcher page is considered to have
218 // been reset.
219 app_list_main_view_->model()->ClearCustomLauncherPageSubpages();
222 if (custom_page_view_) {
223 custom_page_view_->SetFocusable(state ==
224 AppListModel::STATE_CUSTOM_LAUNCHER_PAGE);
228 void ContentsView::ShowSearchResults(bool show) {
229 int search_page = GetPageIndexForState(AppListModel::STATE_SEARCH_RESULTS);
230 DCHECK_GE(search_page, 0);
232 SetActivePageInternal(show ? search_page : page_before_search_, show, true);
235 bool ContentsView::IsShowingSearchResults() const {
236 return IsStateActive(AppListModel::STATE_SEARCH_RESULTS);
239 void ContentsView::NotifyCustomLauncherPageAnimationChanged(double progress,
240 int current_page,
241 int target_page) {
242 int custom_launcher_page_index =
243 GetPageIndexForState(AppListModel::STATE_CUSTOM_LAUNCHER_PAGE);
244 if (custom_launcher_page_index == target_page) {
245 app_list_main_view_->view_delegate()->CustomLauncherPageAnimationChanged(
246 progress);
247 } else if (custom_launcher_page_index == current_page) {
248 app_list_main_view_->view_delegate()->CustomLauncherPageAnimationChanged(
249 1 - progress);
253 void ContentsView::UpdatePageBounds() {
254 // The bounds calculations will potentially be mid-transition (depending on
255 // the state of the PaginationModel).
256 int current_page = std::max(0, pagination_model_.selected_page());
257 int target_page = current_page;
258 double progress = 1;
259 if (pagination_model_.has_transition()) {
260 const PaginationModel::Transition& transition =
261 pagination_model_.transition();
262 if (pagination_model_.is_valid_page(transition.target_page)) {
263 target_page = transition.target_page;
264 progress = transition.progress;
268 NotifyCustomLauncherPageAnimationChanged(progress, current_page, target_page);
270 bool reverse;
271 ContentsAnimator* animator =
272 GetAnimatorForTransition(current_page, target_page, &reverse);
274 // Animate linearly (the PaginationModel handles easing).
275 if (reverse)
276 animator->Update(1 - progress, target_page, current_page);
277 else
278 animator->Update(progress, current_page, target_page);
281 PaginationModel* ContentsView::GetAppsPaginationModel() {
282 return apps_container_view_->apps_grid_view()->pagination_model();
285 void ContentsView::AddAnimator(AppListModel::State from_state,
286 AppListModel::State to_state,
287 scoped_ptr<ContentsAnimator> animator) {
288 int from_page = GetPageIndexForState(from_state);
289 int to_page = GetPageIndexForState(to_state);
290 contents_animators_.insert(
291 std::make_pair(std::make_pair(from_page, to_page),
292 linked_ptr<ContentsAnimator>(animator.release())));
295 ContentsAnimator* ContentsView::GetAnimatorForTransition(int from_page,
296 int to_page,
297 bool* reverse) const {
298 auto it = contents_animators_.find(std::make_pair(from_page, to_page));
299 if (it != contents_animators_.end()) {
300 *reverse = false;
301 return it->second.get();
304 it = contents_animators_.find(std::make_pair(to_page, from_page));
305 if (it != contents_animators_.end()) {
306 *reverse = true;
307 return it->second.get();
310 *reverse = false;
311 return default_animator_.get();
314 void ContentsView::ShowFolderContent(AppListFolderItem* item) {
315 apps_container_view_->ShowActiveFolder(item);
318 void ContentsView::Prerender() {
319 apps_container_view_->apps_grid_view()->Prerender();
322 views::View* ContentsView::GetPageView(int index) const {
323 return view_model_->view_at(index);
326 SearchBoxView* ContentsView::GetSearchBoxView() const {
327 return app_list_main_view_->search_box_view();
330 void ContentsView::AddBlankPageForTesting() {
331 AddLauncherPage(new views::View);
332 pagination_model_.SetTotalPages(view_model_->view_size());
335 int ContentsView::AddLauncherPage(views::View* view) {
336 int page_index = view_model_->view_size();
337 AddChildView(view);
338 view_model_->Add(view, page_index);
339 return page_index;
342 int ContentsView::AddLauncherPage(views::View* view,
343 AppListModel::State state) {
344 int page_index = AddLauncherPage(view);
345 bool success =
346 state_to_view_.insert(std::make_pair(state, page_index)).second;
347 success = success &&
348 view_to_state_.insert(std::make_pair(page_index, state)).second;
350 // There shouldn't be duplicates in either map.
351 DCHECK(success);
352 return page_index;
355 gfx::Rect ContentsView::GetOnscreenPageBounds(int page_index) const {
356 AppListModel::State state = GetStateForPageIndex(page_index);
357 bool fills_contents_view =
358 state == AppListModel::STATE_CUSTOM_LAUNCHER_PAGE ||
359 state == AppListModel::STATE_START;
360 return fills_contents_view ? GetContentsBounds() : GetDefaultContentsBounds();
363 gfx::Rect ContentsView::GetOffscreenPageBounds(int page_index) const {
364 AppListModel::State state = GetStateForPageIndex(page_index);
365 gfx::Rect bounds(GetOnscreenPageBounds(page_index));
366 // The start page and search page origins are above; all other pages' origins
367 // are below.
368 bool origin_above = state == AppListModel::STATE_START ||
369 state == AppListModel::STATE_SEARCH_RESULTS;
370 bounds.set_y(origin_above ? -bounds.height()
371 : GetContentsBounds().height() + bounds.y());
372 return bounds;
375 gfx::Rect ContentsView::GetDefaultSearchBoxBounds() const {
376 gfx::Rect search_box_bounds(0, 0, GetDefaultContentsSize().width(),
377 GetSearchBoxView()->GetPreferredSize().height());
378 if (switches::IsExperimentalAppListEnabled()) {
379 search_box_bounds.set_y(kExperimentalSearchBoxPadding);
380 search_box_bounds.Inset(kExperimentalSearchBoxPadding, 0);
382 return search_box_bounds;
385 gfx::Rect ContentsView::GetSearchBoxBoundsForState(
386 AppListModel::State state) const {
387 // On the start page, the search box is in a different location.
388 if (state == AppListModel::STATE_START) {
389 DCHECK(start_page_view_);
390 // Convert to ContentsView space, assuming that the StartPageView is in the
391 // ContentsView's default bounds.
392 return start_page_view_->GetSearchBoxBounds() +
393 GetOnscreenPageBounds(GetPageIndexForState(state))
394 .OffsetFromOrigin();
397 return GetDefaultSearchBoxBounds();
400 gfx::Rect ContentsView::GetSearchBoxBoundsForPageIndex(int index) const {
401 return GetSearchBoxBoundsForState(GetStateForPageIndex(index));
404 gfx::Rect ContentsView::GetDefaultContentsBounds() const {
405 gfx::Rect bounds(gfx::Point(0, GetDefaultSearchBoxBounds().bottom()),
406 GetDefaultContentsSize());
407 return bounds;
410 gfx::Rect ContentsView::GetCustomPageCollapsedBounds() const {
411 gfx::Rect bounds(GetContentsBounds());
412 int page_height = bounds.height();
413 bounds.set_y(page_height - kCustomPageCollapsedHeight);
414 return bounds;
417 bool ContentsView::ShouldShowCustomPageClickzone() const {
418 return custom_page_view_ && IsStateActive(AppListModel::STATE_START) &&
419 app_list_main_view_->model()->custom_launcher_page_enabled();
422 bool ContentsView::Back() {
423 AppListModel::State state = view_to_state_[GetActivePageIndex()];
424 switch (state) {
425 case AppListModel::STATE_START:
426 // Close the app list when Back() is called from the start page.
427 return false;
428 case AppListModel::STATE_CUSTOM_LAUNCHER_PAGE:
429 if (app_list_main_view_->model()->PopCustomLauncherPageSubpage())
430 app_list_main_view_->view_delegate()->CustomLauncherPagePopSubpage();
431 else
432 SetActivePage(GetPageIndexForState(AppListModel::STATE_START));
433 break;
434 case AppListModel::STATE_APPS:
435 if (apps_container_view_->IsInFolderView())
436 apps_container_view_->app_list_folder_view()->CloseFolderPage();
437 else
438 SetActivePage(GetPageIndexForState(AppListModel::STATE_START));
439 break;
440 case AppListModel::STATE_SEARCH_RESULTS:
441 GetSearchBoxView()->ClearSearch();
442 ShowSearchResults(false);
443 break;
444 case AppListModel::INVALID_STATE:
445 NOTREACHED();
446 break;
448 return true;
451 gfx::Size ContentsView::GetDefaultContentsSize() const {
452 return apps_container_view_->apps_grid_view()->GetPreferredSize();
455 gfx::Size ContentsView::GetPreferredSize() const {
456 gfx::Rect search_box_bounds = GetDefaultSearchBoxBounds();
457 gfx::Rect default_contents_bounds = GetDefaultContentsBounds();
458 gfx::Vector2d bottom_right =
459 search_box_bounds.bottom_right().OffsetFromOrigin();
460 bottom_right.SetToMax(
461 default_contents_bounds.bottom_right().OffsetFromOrigin());
462 return gfx::Size(bottom_right.x(), bottom_right.y());
465 void ContentsView::Layout() {
466 // The search box is contained in a widget so set the bounds of the widget
467 // rather than the SearchBoxView. In athena, the search box widget will be the
468 // same as the app list widget so don't move it.
469 views::Widget* search_box_widget = GetSearchBoxView()->GetWidget();
470 if (search_box_widget && search_box_widget != GetWidget()) {
471 gfx::Rect search_box_bounds = GetSearchBoxBoundsForState(GetActiveState());
472 search_box_widget->SetBounds(ConvertRectToWidget(
473 GetSearchBoxView()->GetViewBoundsForSearchBoxContentsBounds(
474 search_box_bounds)));
477 // Immediately finish all current animations.
478 pagination_model_.FinishAnimation();
480 // Move the current view onto the screen, and all other views off screen to
481 // the left. (Since we are not animating, we don't need to be careful about
482 // which side we place the off-screen views onto.)
483 gfx::Rect rect = GetOnscreenPageBounds(GetActivePageIndex());
484 double progress =
485 IsStateActive(AppListModel::STATE_CUSTOM_LAUNCHER_PAGE) ? 1 : 0;
487 // Notify the custom launcher page that the active page has changed.
488 app_list_main_view_->view_delegate()->CustomLauncherPageAnimationChanged(
489 progress);
491 if (rect.IsEmpty())
492 return;
494 gfx::Rect offscreen_target(rect);
495 offscreen_target.set_x(-rect.width());
497 int current_page = GetActivePageIndex();
499 for (int i = 0; i < view_model_->view_size(); ++i) {
500 view_model_->view_at(i)
501 ->SetBoundsRect(i == current_page ? rect : offscreen_target);
504 // Custom locations of pages in certain states.
505 // Within the start page, the custom page is given its collapsed bounds.
506 int start_page_index = GetPageIndexForState(AppListModel::STATE_START);
507 if (current_page == start_page_index) {
508 if (custom_page_view_)
509 custom_page_view_->SetBoundsRect(GetCustomPageCollapsedBounds());
513 bool ContentsView::OnKeyPressed(const ui::KeyEvent& event) {
514 return view_model_->view_at(GetActivePageIndex())->OnKeyPressed(event);
517 void ContentsView::TotalPagesChanged() {
520 void ContentsView::SelectedPageChanged(int old_selected, int new_selected) {
521 // TODO(mgiuca): This should be generalized so we call a virtual OnShow and
522 // OnHide method for each page.
523 if (!start_page_view_)
524 return;
526 if (new_selected == GetPageIndexForState(AppListModel::STATE_START)) {
527 start_page_view_->OnShow();
528 // Show or hide the custom page view, based on whether it is enabled.
529 if (custom_page_view_) {
530 custom_page_view_->SetVisible(
531 app_list_main_view_->model()->custom_launcher_page_enabled());
536 void ContentsView::TransitionStarted() {
539 void ContentsView::TransitionChanged() {
540 UpdatePageBounds();
543 } // namespace app_list