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/custom_launcher_page_view.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/widget/widget.h"
31 ContentsView::ContentsView(AppListMainView
* app_list_main_view
)
32 : apps_container_view_(nullptr),
33 search_results_page_view_(nullptr),
34 start_page_view_(nullptr),
35 custom_page_view_(nullptr),
36 app_list_main_view_(app_list_main_view
),
37 page_before_search_(0) {
38 pagination_model_
.SetTransitionDurations(kPageTransitionDurationInMs
,
39 kOverscrollPageTransitionDurationMs
);
40 pagination_model_
.AddObserver(this);
43 ContentsView::~ContentsView() {
44 pagination_model_
.RemoveObserver(this);
47 void ContentsView::Init(AppListModel
* model
) {
50 AppListViewDelegate
* view_delegate
= app_list_main_view_
->view_delegate();
52 if (app_list::switches::IsExperimentalAppListEnabled()) {
53 std::vector
<views::View
*> custom_page_views
=
54 view_delegate
->CreateCustomPageWebViews(GetLocalBounds().size());
55 // Only add the first custom page view as STATE_CUSTOM_LAUNCHER_PAGE. Ignore
56 // any subsequent custom pages.
57 if (!custom_page_views
.empty()) {
58 custom_page_view_
= new CustomLauncherPageView(custom_page_views
[0]);
60 AddLauncherPage(custom_page_view_
,
61 AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
);
65 start_page_view_
= new StartPageView(app_list_main_view_
, view_delegate
);
66 AddLauncherPage(start_page_view_
, AppListModel::STATE_START
);
70 search_results_page_view_
= new SearchResultPageView();
72 AppListModel::SearchResults
* results
= view_delegate
->GetModel()->results();
73 search_results_page_view_
->AddSearchResultContainerView(
74 results
, new SearchResultListView(app_list_main_view_
, view_delegate
));
76 if (app_list::switches::IsExperimentalAppListEnabled()) {
77 search_results_page_view_
->AddSearchResultContainerView(
79 new SearchResultTileItemListView(GetSearchBoxView()->search_box()));
81 AddLauncherPage(search_results_page_view_
,
82 AppListModel::STATE_SEARCH_RESULTS
);
84 apps_container_view_
= new AppsContainerView(app_list_main_view_
, model
);
86 AddLauncherPage(apps_container_view_
, AppListModel::STATE_APPS
);
88 int initial_page_index
= app_list::switches::IsExperimentalAppListEnabled()
89 ? GetPageIndexForState(AppListModel::STATE_START
)
90 : GetPageIndexForState(AppListModel::STATE_APPS
);
91 DCHECK_GE(initial_page_index
, 0);
93 page_before_search_
= initial_page_index
;
94 // Must only call SetTotalPages once all the launcher pages have been added
95 // (as it will trigger a SelectedPageChanged call).
96 pagination_model_
.SetTotalPages(app_list_pages_
.size());
98 // Page 0 is selected by SetTotalPages and needs to be 'hidden' when selecting
100 app_list_pages_
[GetActivePageIndex()]->OnWillBeHidden();
102 pagination_model_
.SelectPage(initial_page_index
, false);
107 void ContentsView::CancelDrag() {
108 if (apps_container_view_
->apps_grid_view()->has_dragged_view())
109 apps_container_view_
->apps_grid_view()->EndDrag(true);
110 if (apps_container_view_
->app_list_folder_view()
112 ->has_dragged_view()) {
113 apps_container_view_
->app_list_folder_view()->items_grid_view()->EndDrag(
118 void ContentsView::SetDragAndDropHostOfCurrentAppList(
119 ApplicationDragAndDropHost
* drag_and_drop_host
) {
120 apps_container_view_
->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host
);
123 void ContentsView::SetActiveState(AppListModel::State state
) {
124 SetActiveState(state
, true);
127 void ContentsView::SetActiveState(AppListModel::State state
, bool animate
) {
128 if (IsStateActive(state
))
131 SetActiveStateInternal(GetPageIndexForState(state
), false, animate
);
134 int ContentsView::GetActivePageIndex() const {
135 // The active page is changed at the beginning of an animation, not the end.
136 return pagination_model_
.SelectedTargetPage();
139 AppListModel::State
ContentsView::GetActiveState() const {
140 return GetStateForPageIndex(GetActivePageIndex());
143 bool ContentsView::IsStateActive(AppListModel::State state
) const {
144 int active_page_index
= GetActivePageIndex();
145 return active_page_index
>= 0 &&
146 GetPageIndexForState(state
) == active_page_index
;
149 int ContentsView::GetPageIndexForState(AppListModel::State state
) const {
150 // Find the index of the view corresponding to the given state.
151 std::map
<AppListModel::State
, int>::const_iterator it
=
152 state_to_view_
.find(state
);
153 if (it
== state_to_view_
.end())
159 AppListModel::State
ContentsView::GetStateForPageIndex(int index
) const {
160 std::map
<int, AppListModel::State
>::const_iterator it
=
161 view_to_state_
.find(index
);
162 if (it
== view_to_state_
.end())
163 return AppListModel::INVALID_STATE
;
168 int ContentsView::NumLauncherPages() const {
169 return pagination_model_
.total_pages();
172 void ContentsView::SetActiveStateInternal(int page_index
,
173 bool show_search_results
,
175 if (!GetPageView(page_index
)->visible())
178 if (!show_search_results
)
179 page_before_search_
= page_index
;
181 app_list_pages_
[GetActivePageIndex()]->OnWillBeHidden();
183 // Start animating to the new page.
184 pagination_model_
.SelectPage(page_index
, animate
);
191 void ContentsView::ActivePageChanged() {
192 AppListModel::State state
= AppListModel::INVALID_STATE
;
194 std::map
<int, AppListModel::State
>::const_iterator it
=
195 view_to_state_
.find(GetActivePageIndex());
196 if (it
!= view_to_state_
.end())
199 app_list_pages_
[GetActivePageIndex()]->OnWillBeShown();
201 app_list_main_view_
->model()->SetState(state
);
203 if (switches::IsExperimentalAppListEnabled()) {
204 DCHECK(start_page_view_
);
206 // Set the visibility of the search box's back button.
207 app_list_main_view_
->search_box_view()->back_button()->SetVisible(
208 state
!= AppListModel::STATE_START
);
209 app_list_main_view_
->search_box_view()->Layout();
211 // Whenever the page changes, the custom launcher page is considered to have
213 app_list_main_view_
->model()->ClearCustomLauncherPageSubpages();
216 app_list_main_view_
->search_box_view()->ResetTabFocus(false);
219 void ContentsView::ShowSearchResults(bool show
) {
220 int search_page
= GetPageIndexForState(AppListModel::STATE_SEARCH_RESULTS
);
221 DCHECK_GE(search_page
, 0);
223 SetActiveStateInternal(show
? search_page
: page_before_search_
, show
, true);
226 bool ContentsView::IsShowingSearchResults() const {
227 return IsStateActive(AppListModel::STATE_SEARCH_RESULTS
);
230 void ContentsView::NotifyCustomLauncherPageAnimationChanged(double progress
,
233 int custom_launcher_page_index
=
234 GetPageIndexForState(AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
);
235 if (custom_launcher_page_index
== target_page
) {
236 app_list_main_view_
->view_delegate()->CustomLauncherPageAnimationChanged(
238 } else if (custom_launcher_page_index
== current_page
) {
239 app_list_main_view_
->view_delegate()->CustomLauncherPageAnimationChanged(
244 void ContentsView::UpdatePageBounds() {
245 // The bounds calculations will potentially be mid-transition (depending on
246 // the state of the PaginationModel).
247 int current_page
= std::max(0, pagination_model_
.selected_page());
248 int target_page
= current_page
;
250 if (pagination_model_
.has_transition()) {
251 const PaginationModel::Transition
& transition
=
252 pagination_model_
.transition();
253 if (pagination_model_
.is_valid_page(transition
.target_page
)) {
254 target_page
= transition
.target_page
;
255 progress
= transition
.progress
;
259 NotifyCustomLauncherPageAnimationChanged(progress
, current_page
, target_page
);
261 AppListModel::State current_state
= GetStateForPageIndex(current_page
);
262 AppListModel::State target_state
= GetStateForPageIndex(target_page
);
264 // Update app list pages.
265 for (AppListPage
* page
: app_list_pages_
) {
266 gfx::Rect to_rect
= page
->GetPageBoundsForState(target_state
);
267 gfx::Rect from_rect
= page
->GetPageBoundsForState(current_state
);
268 if (from_rect
== to_rect
)
271 // Animate linearly (the PaginationModel handles easing).
273 gfx::Tween::RectValueBetween(progress
, from_rect
, to_rect
));
275 page
->SetBoundsRect(bounds
);
276 page
->OnAnimationUpdated(progress
, current_state
, target_state
);
279 // Update the search box.
280 UpdateSearchBox(progress
, current_state
, target_state
);
283 void ContentsView::UpdateSearchBox(double progress
,
284 AppListModel::State current_state
,
285 AppListModel::State target_state
) {
286 AppListPage
* from_page
= GetPageView(GetPageIndexForState(current_state
));
287 AppListPage
* to_page
= GetPageView(GetPageIndexForState(target_state
));
289 SearchBoxView
* search_box
= GetSearchBoxView();
291 gfx::Rect
search_box_from(from_page
->GetSearchBoxBounds());
292 gfx::Rect
search_box_to(to_page
->GetSearchBoxBounds());
293 gfx::Rect search_box_rect
=
294 gfx::Tween::RectValueBetween(progress
, search_box_from
, search_box_to
);
296 int original_z_height
= from_page
->GetSearchBoxZHeight();
297 int target_z_height
= to_page
->GetSearchBoxZHeight();
299 if (original_z_height
!= target_z_height
) {
300 gfx::ShadowValue original_shadow
= GetShadowForZHeight(original_z_height
);
301 gfx::ShadowValue target_shadow
= GetShadowForZHeight(target_z_height
);
303 gfx::Vector2d
offset(gfx::Tween::LinearIntValueBetween(
304 progress
, original_shadow
.x(), target_shadow
.x()),
305 gfx::Tween::LinearIntValueBetween(
306 progress
, original_shadow
.y(), target_shadow
.y()));
307 search_box
->SetShadow(gfx::ShadowValue(
308 offset
, gfx::Tween::LinearIntValueBetween(
309 progress
, original_shadow
.blur(), target_shadow
.blur()),
310 gfx::Tween::ColorValueBetween(progress
, original_shadow
.color(),
311 target_shadow
.color())));
313 search_box
->GetWidget()->SetBounds(
314 search_box
->GetViewBoundsForSearchBoxContentsBounds(
315 ConvertRectToWidget(search_box_rect
)));
318 PaginationModel
* ContentsView::GetAppsPaginationModel() {
319 return apps_container_view_
->apps_grid_view()->pagination_model();
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 AppListPage
* ContentsView::GetPageView(int index
) const {
331 DCHECK_GT(static_cast<int>(app_list_pages_
.size()), index
);
332 return app_list_pages_
[index
];
335 SearchBoxView
* ContentsView::GetSearchBoxView() const {
336 return app_list_main_view_
->search_box_view();
339 int ContentsView::AddLauncherPage(AppListPage
* view
) {
340 view
->set_contents_view(this);
342 app_list_pages_
.push_back(view
);
343 return app_list_pages_
.size() - 1;
346 int ContentsView::AddLauncherPage(AppListPage
* view
,
347 AppListModel::State state
) {
348 int page_index
= AddLauncherPage(view
);
350 state_to_view_
.insert(std::make_pair(state
, page_index
)).second
;
352 view_to_state_
.insert(std::make_pair(page_index
, state
)).second
;
354 // There shouldn't be duplicates in either map.
359 gfx::Rect
ContentsView::GetDefaultSearchBoxBounds() const {
360 gfx::Rect
search_box_bounds(0, 0, GetDefaultContentsSize().width(),
361 GetSearchBoxView()->GetPreferredSize().height());
362 if (switches::IsExperimentalAppListEnabled()) {
363 search_box_bounds
.set_y(kExperimentalSearchBoxPadding
);
364 search_box_bounds
.Inset(kExperimentalSearchBoxPadding
, 0);
366 return search_box_bounds
;
369 gfx::Rect
ContentsView::GetSearchBoxBoundsForState(
370 AppListModel::State state
) const {
371 AppListPage
* page
= GetPageView(GetPageIndexForState(state
));
372 return page
->GetSearchBoxBounds();
375 gfx::Rect
ContentsView::GetDefaultContentsBounds() const {
376 gfx::Rect
bounds(gfx::Point(0, GetDefaultSearchBoxBounds().bottom()),
377 GetDefaultContentsSize());
381 bool ContentsView::Back() {
382 AppListModel::State state
= view_to_state_
[GetActivePageIndex()];
384 case AppListModel::STATE_START
:
385 // Close the app list when Back() is called from the start page.
387 case AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
:
388 if (app_list_main_view_
->model()->PopCustomLauncherPageSubpage())
389 app_list_main_view_
->view_delegate()->CustomLauncherPagePopSubpage();
391 SetActiveState(AppListModel::STATE_START
);
393 case AppListModel::STATE_APPS
:
394 if (apps_container_view_
->IsInFolderView())
395 apps_container_view_
->app_list_folder_view()->CloseFolderPage();
397 SetActiveState(AppListModel::STATE_START
);
399 case AppListModel::STATE_SEARCH_RESULTS
:
400 GetSearchBoxView()->ClearSearch();
401 ShowSearchResults(false);
403 case AppListModel::INVALID_STATE
: // Falls through.
410 gfx::Size
ContentsView::GetDefaultContentsSize() const {
411 return apps_container_view_
->apps_grid_view()->GetPreferredSize();
414 gfx::Size
ContentsView::GetPreferredSize() const {
415 gfx::Rect search_box_bounds
= GetDefaultSearchBoxBounds();
416 gfx::Rect default_contents_bounds
= GetDefaultContentsBounds();
417 gfx::Vector2d bottom_right
=
418 search_box_bounds
.bottom_right().OffsetFromOrigin();
419 bottom_right
.SetToMax(
420 default_contents_bounds
.bottom_right().OffsetFromOrigin());
421 return gfx::Size(bottom_right
.x(), bottom_right
.y());
424 void ContentsView::Layout() {
425 // Immediately finish all current animations.
426 pagination_model_
.FinishAnimation();
429 IsStateActive(AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
) ? 1 : 0;
431 // Notify the custom launcher page that the active page has changed.
432 app_list_main_view_
->view_delegate()->CustomLauncherPageAnimationChanged(
435 if (GetContentsBounds().IsEmpty())
438 for (AppListPage
* page
: app_list_pages_
) {
439 page
->SetBoundsRect(page
->GetPageBoundsForState(GetActiveState()));
442 // The search box is contained in a widget so set the bounds of the widget
443 // rather than the SearchBoxView. In athena, the search box widget will be the
444 // same as the app list widget so don't move it.
445 views::Widget
* search_box_widget
= GetSearchBoxView()->GetWidget();
446 if (search_box_widget
&& search_box_widget
!= GetWidget()) {
447 gfx::Rect search_box_bounds
= GetSearchBoxBoundsForState(GetActiveState());
448 search_box_widget
->SetBounds(ConvertRectToWidget(
449 GetSearchBoxView()->GetViewBoundsForSearchBoxContentsBounds(
450 search_box_bounds
)));
454 bool ContentsView::OnKeyPressed(const ui::KeyEvent
& event
) {
455 bool handled
= app_list_pages_
[GetActivePageIndex()]->OnKeyPressed(event
);
458 if (event
.key_code() == ui::VKEY_TAB
&& event
.IsShiftDown()) {
459 GetSearchBoxView()->MoveTabFocus(true);
467 void ContentsView::TotalPagesChanged() {
470 void ContentsView::SelectedPageChanged(int old_selected
, int new_selected
) {
471 if (old_selected
>= 0)
472 app_list_pages_
[old_selected
]->OnHidden();
474 if (new_selected
>= 0)
475 app_list_pages_
[new_selected
]->OnShown();
478 void ContentsView::TransitionStarted() {
481 void ContentsView::TransitionChanged() {
485 } // namespace app_list