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"
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"
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
) {
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();
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
);
73 start_page_view_
= new StartPageView(app_list_main_view_
, view_delegate
);
74 AddLauncherPage(start_page_view_
, AppListModel::STATE_START
);
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(
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);
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()
123 ->has_dragged_view()) {
124 apps_container_view_
->app_list_folder_view()->items_grid_view()->EndDrag(
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
)
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())
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
;
179 int ContentsView::NumLauncherPages() const {
180 return pagination_model_
.total_pages();
183 void ContentsView::SetActivePageInternal(int page_index
,
184 bool show_search_results
,
186 if (!GetPageView(page_index
)->visible())
189 if (!show_search_results
)
190 page_before_search_
= page_index
;
191 // Start animating to the new page.
192 pagination_model_
.SelectPage(page_index
, animate
);
199 void ContentsView::ActivePageChanged() {
200 AppListModel::State state
= AppListModel::INVALID_STATE
;
202 // TODO(calamity): This does not report search results being shown in the
203 // experimental app list as a boolean is currently used to indicate whether
204 // search results are showing. See http://crbug.com/427787/.
205 std::map
<int, AppListModel::State
>::const_iterator it
=
206 view_to_state_
.find(pagination_model_
.SelectedTargetPage());
207 if (it
!= view_to_state_
.end())
210 app_list_main_view_
->model()->SetState(state
);
212 if (switches::IsExperimentalAppListEnabled()) {
213 DCHECK(start_page_view_
);
215 // Set the visibility of the search box's back button.
216 app_list_main_view_
->search_box_view()->back_button()->SetVisible(
217 state
!= AppListModel::STATE_START
);
218 app_list_main_view_
->search_box_view()->Layout();
220 // Whenever the page changes, the custom launcher page is considered to have
222 app_list_main_view_
->model()->ClearCustomLauncherPageSubpages();
225 app_list_main_view_
->search_box_view()->ResetTabFocus(false);
226 apps_container_view_
->apps_grid_view()->ClearAnySelectedView();
227 apps_container_view_
->app_list_folder_view()->items_grid_view()
228 ->ClearAnySelectedView();
230 if (custom_page_view_
) {
231 custom_page_view_
->SetFocusable(state
==
232 AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
);
236 void ContentsView::ShowSearchResults(bool show
) {
237 int search_page
= GetPageIndexForState(AppListModel::STATE_SEARCH_RESULTS
);
238 DCHECK_GE(search_page
, 0);
240 SetActivePageInternal(show
? search_page
: page_before_search_
, show
, true);
243 bool ContentsView::IsShowingSearchResults() const {
244 return IsStateActive(AppListModel::STATE_SEARCH_RESULTS
);
247 void ContentsView::NotifyCustomLauncherPageAnimationChanged(double progress
,
250 int custom_launcher_page_index
=
251 GetPageIndexForState(AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
);
252 if (custom_launcher_page_index
== target_page
) {
253 app_list_main_view_
->view_delegate()->CustomLauncherPageAnimationChanged(
255 } else if (custom_launcher_page_index
== current_page
) {
256 app_list_main_view_
->view_delegate()->CustomLauncherPageAnimationChanged(
261 void ContentsView::UpdatePageBounds() {
262 // The bounds calculations will potentially be mid-transition (depending on
263 // the state of the PaginationModel).
264 int current_page
= std::max(0, pagination_model_
.selected_page());
265 int target_page
= current_page
;
267 if (pagination_model_
.has_transition()) {
268 const PaginationModel::Transition
& transition
=
269 pagination_model_
.transition();
270 if (pagination_model_
.is_valid_page(transition
.target_page
)) {
271 target_page
= transition
.target_page
;
272 progress
= transition
.progress
;
276 NotifyCustomLauncherPageAnimationChanged(progress
, current_page
, target_page
);
279 ContentsAnimator
* animator
=
280 GetAnimatorForTransition(current_page
, target_page
, &reverse
);
282 // Animate linearly (the PaginationModel handles easing).
284 animator
->Update(1 - progress
, target_page
, current_page
);
286 animator
->Update(progress
, current_page
, target_page
);
289 PaginationModel
* ContentsView::GetAppsPaginationModel() {
290 return apps_container_view_
->apps_grid_view()->pagination_model();
293 void ContentsView::AddAnimator(AppListModel::State from_state
,
294 AppListModel::State to_state
,
295 scoped_ptr
<ContentsAnimator
> animator
) {
296 int from_page
= GetPageIndexForState(from_state
);
297 int to_page
= GetPageIndexForState(to_state
);
298 contents_animators_
.insert(
299 std::make_pair(std::make_pair(from_page
, to_page
),
300 linked_ptr
<ContentsAnimator
>(animator
.release())));
303 ContentsAnimator
* ContentsView::GetAnimatorForTransition(int from_page
,
305 bool* reverse
) const {
306 auto it
= contents_animators_
.find(std::make_pair(from_page
, to_page
));
307 if (it
!= contents_animators_
.end()) {
309 return it
->second
.get();
312 it
= contents_animators_
.find(std::make_pair(to_page
, from_page
));
313 if (it
!= contents_animators_
.end()) {
315 return it
->second
.get();
319 return default_animator_
.get();
322 void ContentsView::ShowFolderContent(AppListFolderItem
* item
) {
323 apps_container_view_
->ShowActiveFolder(item
);
326 void ContentsView::Prerender() {
327 apps_container_view_
->apps_grid_view()->Prerender();
330 views::View
* ContentsView::GetPageView(int index
) const {
331 return view_model_
->view_at(index
);
334 SearchBoxView
* ContentsView::GetSearchBoxView() const {
335 return app_list_main_view_
->search_box_view();
338 void ContentsView::AddBlankPageForTesting() {
339 AddLauncherPage(new views::View
);
340 pagination_model_
.SetTotalPages(view_model_
->view_size());
343 int ContentsView::AddLauncherPage(views::View
* view
) {
344 int page_index
= view_model_
->view_size();
346 view_model_
->Add(view
, page_index
);
350 int ContentsView::AddLauncherPage(views::View
* view
,
351 AppListModel::State state
) {
352 int page_index
= AddLauncherPage(view
);
354 state_to_view_
.insert(std::make_pair(state
, page_index
)).second
;
356 view_to_state_
.insert(std::make_pair(page_index
, state
)).second
;
358 // There shouldn't be duplicates in either map.
363 gfx::Rect
ContentsView::GetOnscreenPageBounds(int page_index
) const {
364 AppListModel::State state
= GetStateForPageIndex(page_index
);
365 bool fills_contents_view
=
366 state
== AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
||
367 state
== AppListModel::STATE_START
;
368 return fills_contents_view
? GetContentsBounds() : GetDefaultContentsBounds();
371 gfx::Rect
ContentsView::GetOffscreenPageBounds(int page_index
) const {
372 AppListModel::State state
= GetStateForPageIndex(page_index
);
373 gfx::Rect
bounds(GetOnscreenPageBounds(page_index
));
374 // The start page and search page origins are above; all other pages' origins
376 bool origin_above
= state
== AppListModel::STATE_START
||
377 state
== AppListModel::STATE_SEARCH_RESULTS
;
378 bounds
.set_y(origin_above
? -bounds
.height()
379 : GetContentsBounds().height() + bounds
.y());
383 gfx::Rect
ContentsView::GetDefaultSearchBoxBounds() const {
384 gfx::Rect
search_box_bounds(0, 0, GetDefaultContentsSize().width(),
385 GetSearchBoxView()->GetPreferredSize().height());
386 if (switches::IsExperimentalAppListEnabled()) {
387 search_box_bounds
.set_y(kExperimentalSearchBoxPadding
);
388 search_box_bounds
.Inset(kExperimentalSearchBoxPadding
, 0);
390 return search_box_bounds
;
393 gfx::Rect
ContentsView::GetSearchBoxBoundsForState(
394 AppListModel::State state
) const {
395 // On the start page, the search box is in a different location.
396 if (state
== AppListModel::STATE_START
) {
397 DCHECK(start_page_view_
);
398 // Convert to ContentsView space, assuming that the StartPageView is in the
399 // ContentsView's default bounds.
400 return start_page_view_
->GetSearchBoxBounds() +
401 GetOnscreenPageBounds(GetPageIndexForState(state
))
405 return GetDefaultSearchBoxBounds();
408 gfx::Rect
ContentsView::GetSearchBoxBoundsForPageIndex(int index
) const {
409 return GetSearchBoxBoundsForState(GetStateForPageIndex(index
));
412 gfx::Rect
ContentsView::GetDefaultContentsBounds() const {
413 gfx::Rect
bounds(gfx::Point(0, GetDefaultSearchBoxBounds().bottom()),
414 GetDefaultContentsSize());
418 gfx::Rect
ContentsView::GetCustomPageCollapsedBounds() const {
419 gfx::Rect
bounds(GetContentsBounds());
420 int page_height
= bounds
.height();
421 bounds
.set_y(page_height
- kCustomPageCollapsedHeight
);
425 bool ContentsView::Back() {
426 AppListModel::State state
= view_to_state_
[GetActivePageIndex()];
428 case AppListModel::STATE_START
:
429 // Close the app list when Back() is called from the start page.
431 case AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
:
432 if (app_list_main_view_
->model()->PopCustomLauncherPageSubpage())
433 app_list_main_view_
->view_delegate()->CustomLauncherPagePopSubpage();
435 SetActivePage(GetPageIndexForState(AppListModel::STATE_START
));
437 case AppListModel::STATE_APPS
:
438 if (apps_container_view_
->IsInFolderView())
439 apps_container_view_
->app_list_folder_view()->CloseFolderPage();
441 SetActivePage(GetPageIndexForState(AppListModel::STATE_START
));
443 case AppListModel::STATE_SEARCH_RESULTS
:
444 GetSearchBoxView()->ClearSearch();
445 ShowSearchResults(false);
447 case AppListModel::INVALID_STATE
: // Falls through.
454 gfx::Size
ContentsView::GetDefaultContentsSize() const {
455 return apps_container_view_
->apps_grid_view()->GetPreferredSize();
458 gfx::Size
ContentsView::GetPreferredSize() const {
459 gfx::Rect search_box_bounds
= GetDefaultSearchBoxBounds();
460 gfx::Rect default_contents_bounds
= GetDefaultContentsBounds();
461 gfx::Vector2d bottom_right
=
462 search_box_bounds
.bottom_right().OffsetFromOrigin();
463 bottom_right
.SetToMax(
464 default_contents_bounds
.bottom_right().OffsetFromOrigin());
465 return gfx::Size(bottom_right
.x(), bottom_right
.y());
468 void ContentsView::Layout() {
469 // Immediately finish all current animations.
470 pagination_model_
.FinishAnimation();
472 // Move the current view onto the screen, and all other views off screen to
473 // the left. (Since we are not animating, we don't need to be careful about
474 // which side we place the off-screen views onto.)
475 gfx::Rect rect
= GetOnscreenPageBounds(GetActivePageIndex());
477 IsStateActive(AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
) ? 1 : 0;
479 // Notify the custom launcher page that the active page has changed.
480 app_list_main_view_
->view_delegate()->CustomLauncherPageAnimationChanged(
486 gfx::Rect
offscreen_target(rect
);
487 offscreen_target
.set_x(-rect
.width());
489 int current_page
= GetActivePageIndex();
491 for (int i
= 0; i
< view_model_
->view_size(); ++i
) {
492 view_model_
->view_at(i
)
493 ->SetBoundsRect(i
== current_page
? rect
: offscreen_target
);
496 // Custom locations of pages in certain states.
497 // Within the start page, the custom page is given its collapsed bounds.
498 int start_page_index
= GetPageIndexForState(AppListModel::STATE_START
);
499 if (current_page
== start_page_index
) {
500 if (custom_page_view_
)
501 custom_page_view_
->SetBoundsRect(GetCustomPageCollapsedBounds());
504 // The search box is contained in a widget so set the bounds of the widget
505 // rather than the SearchBoxView. In athena, the search box widget will be the
506 // same as the app list widget so don't move it.
507 views::Widget
* search_box_widget
= GetSearchBoxView()->GetWidget();
508 if (search_box_widget
&& search_box_widget
!= GetWidget()) {
509 gfx::Rect search_box_bounds
= GetSearchBoxBoundsForState(GetActiveState());
510 search_box_widget
->SetBounds(ConvertRectToWidget(
511 GetSearchBoxView()->GetViewBoundsForSearchBoxContentsBounds(
512 search_box_bounds
)));
516 bool ContentsView::OnKeyPressed(const ui::KeyEvent
& event
) {
518 view_model_
->view_at(GetActivePageIndex())->OnKeyPressed(event
);
521 if (event
.key_code() == ui::VKEY_TAB
&& event
.IsShiftDown()) {
522 GetSearchBoxView()->MoveTabFocus(true);
530 void ContentsView::TotalPagesChanged() {
533 void ContentsView::SelectedPageChanged(int old_selected
, int new_selected
) {
534 // TODO(mgiuca): This should be generalized so we call a virtual OnShow and
535 // OnHide method for each page.
536 if (!start_page_view_
)
539 if (new_selected
== GetPageIndexForState(AppListModel::STATE_START
)) {
540 start_page_view_
->OnShow();
541 // Show or hide the custom page view, based on whether it is enabled.
542 if (custom_page_view_
) {
543 custom_page_view_
->SetVisible(
544 app_list_main_view_
->ShouldShowCustomLauncherPage());
549 void ContentsView::TransitionStarted() {
552 void ContentsView::TransitionChanged() {
556 } // namespace app_list