App list: Fixed rendering issues upon opening the window in RTL mode.
[chromium-blink-merge.git] / ui / app_list / views / contents_view.cc
blob40d18f1f44b750c3e780d956b572212acb5ff413
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_list_view_(nullptr),
35 search_results_page_view_(nullptr),
36 start_page_view_(nullptr),
37 custom_page_view_(nullptr),
38 app_list_main_view_(app_list_main_view),
39 view_model_(new views::ViewModel),
40 page_before_search_(0) {
41 pagination_model_.SetTransitionDurations(kPageTransitionDurationInMs,
42 kOverscrollPageTransitionDurationMs);
43 pagination_model_.AddObserver(this);
46 ContentsView::~ContentsView() {
47 pagination_model_.RemoveObserver(this);
50 void ContentsView::Init(AppListModel* model) {
51 DCHECK(model);
53 AppListViewDelegate* view_delegate = app_list_main_view_->view_delegate();
55 if (app_list::switches::IsExperimentalAppListEnabled()) {
56 std::vector<views::View*> custom_page_views =
57 view_delegate->CreateCustomPageWebViews(GetLocalBounds().size());
58 for (std::vector<views::View*>::const_iterator it =
59 custom_page_views.begin();
60 it != custom_page_views.end();
61 ++it) {
62 // Only the first launcher page is considered to represent
63 // STATE_CUSTOM_LAUNCHER_PAGE.
64 if (it == custom_page_views.begin()) {
65 custom_page_view_ = *it;
67 AddLauncherPage(*it, AppListModel::STATE_CUSTOM_LAUNCHER_PAGE);
68 } else {
69 AddLauncherPage(*it);
73 // Start page.
74 start_page_view_ = new StartPageView(app_list_main_view_, view_delegate);
75 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));
83 search_results_page_view_->AddSearchResultContainerView(
84 results,
85 new SearchResultTileItemListView(GetSearchBoxView()->search_box()));
87 AddLauncherPage(search_results_page_view_,
88 AppListModel::STATE_SEARCH_RESULTS);
89 } else {
90 search_results_list_view_ =
91 new SearchResultListView(app_list_main_view_, view_delegate);
92 AddLauncherPage(search_results_list_view_,
93 AppListModel::STATE_SEARCH_RESULTS);
94 search_results_list_view_->SetResults(model->results());
97 apps_container_view_ = new AppsContainerView(app_list_main_view_, model);
99 AddLauncherPage(apps_container_view_, AppListModel::STATE_APPS);
101 int initial_page_index = app_list::switches::IsExperimentalAppListEnabled()
102 ? GetPageIndexForState(AppListModel::STATE_START)
103 : GetPageIndexForState(AppListModel::STATE_APPS);
104 DCHECK_GE(initial_page_index, 0);
106 page_before_search_ = initial_page_index;
107 // Must only call SetTotalPages once all the launcher pages have been added
108 // (as it will trigger a SelectedPageChanged call).
109 pagination_model_.SetTotalPages(view_model_->view_size());
110 pagination_model_.SelectPage(initial_page_index, false);
112 ActivePageChanged();
114 // Populate the contents animators.
115 AddAnimator(AppListModel::STATE_START, AppListModel::STATE_APPS,
116 scoped_ptr<ContentsAnimator>(new StartToAppsAnimator(this)));
117 AddAnimator(AppListModel::STATE_START,
118 AppListModel::STATE_CUSTOM_LAUNCHER_PAGE,
119 scoped_ptr<ContentsAnimator>(new StartToCustomAnimator(this)));
120 default_animator_.reset(new DefaultAnimator(this));
123 void ContentsView::CancelDrag() {
124 if (apps_container_view_->apps_grid_view()->has_dragged_view())
125 apps_container_view_->apps_grid_view()->EndDrag(true);
126 if (apps_container_view_->app_list_folder_view()
127 ->items_grid_view()
128 ->has_dragged_view()) {
129 apps_container_view_->app_list_folder_view()->items_grid_view()->EndDrag(
130 true);
134 void ContentsView::SetDragAndDropHostOfCurrentAppList(
135 ApplicationDragAndDropHost* drag_and_drop_host) {
136 apps_container_view_->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host);
139 void ContentsView::SetActivePage(int page_index) {
140 SetActivePage(page_index, true);
143 void ContentsView::SetActivePage(int page_index, bool animate) {
144 if (GetActivePageIndex() == page_index)
145 return;
147 SetActivePageInternal(page_index, false, animate);
150 int ContentsView::GetActivePageIndex() const {
151 // The active page is changed at the beginning of an animation, not the end.
152 return pagination_model_.SelectedTargetPage();
155 AppListModel::State ContentsView::GetActiveState() const {
156 return GetStateForPageIndex(GetActivePageIndex());
159 bool ContentsView::IsStateActive(AppListModel::State state) const {
160 int active_page_index = GetActivePageIndex();
161 return active_page_index >= 0 &&
162 GetPageIndexForState(state) == active_page_index;
165 int ContentsView::GetPageIndexForState(AppListModel::State state) const {
166 // Find the index of the view corresponding to the given state.
167 std::map<AppListModel::State, int>::const_iterator it =
168 state_to_view_.find(state);
169 if (it == state_to_view_.end())
170 return -1;
172 return it->second;
175 AppListModel::State ContentsView::GetStateForPageIndex(int index) const {
176 std::map<int, AppListModel::State>::const_iterator it =
177 view_to_state_.find(index);
178 if (it == view_to_state_.end())
179 return AppListModel::INVALID_STATE;
181 return it->second;
184 int ContentsView::NumLauncherPages() const {
185 return pagination_model_.total_pages();
188 void ContentsView::SetActivePageInternal(int page_index,
189 bool show_search_results,
190 bool animate) {
191 if (!show_search_results)
192 page_before_search_ = page_index;
193 // Start animating to the new page.
194 pagination_model_.SelectPage(page_index, animate);
195 ActivePageChanged();
197 if (!animate)
198 Layout();
201 void ContentsView::ActivePageChanged() {
202 AppListModel::State state = AppListModel::INVALID_STATE;
204 // TODO(calamity): This does not report search results being shown in the
205 // experimental app list as a boolean is currently used to indicate whether
206 // search results are showing. See http://crbug.com/427787/.
207 std::map<int, AppListModel::State>::const_iterator it =
208 view_to_state_.find(pagination_model_.SelectedTargetPage());
209 if (it != view_to_state_.end())
210 state = it->second;
212 app_list_main_view_->model()->SetState(state);
214 if (switches::IsExperimentalAppListEnabled()) {
215 DCHECK(start_page_view_);
217 // Set the visibility of the search box's back button.
218 app_list_main_view_->search_box_view()->back_button()->SetVisible(
219 state != AppListModel::STATE_START);
220 app_list_main_view_->search_box_view()->Layout();
222 // Whenever the page changes, the custom launcher page is considered to have
223 // been reset.
224 app_list_main_view_->model()->ClearCustomLauncherPageSubpages();
227 // TODO(xiyuan): Highlight default match instead of the first.
228 if (state == AppListModel::STATE_SEARCH_RESULTS &&
229 search_results_list_view_ && search_results_list_view_->visible()) {
230 search_results_list_view_->OnContainerSelected(false);
233 if (search_results_list_view_)
234 search_results_list_view_->UpdateAutoLaunchState();
236 if (custom_page_view_) {
237 custom_page_view_->SetFocusable(state ==
238 AppListModel::STATE_CUSTOM_LAUNCHER_PAGE);
242 void ContentsView::ShowSearchResults(bool show) {
243 int search_page = GetPageIndexForState(AppListModel::STATE_SEARCH_RESULTS);
244 DCHECK_GE(search_page, 0);
246 SetActivePageInternal(show ? search_page : page_before_search_, show, true);
249 bool ContentsView::IsShowingSearchResults() const {
250 return IsStateActive(AppListModel::STATE_SEARCH_RESULTS);
253 void ContentsView::NotifyCustomLauncherPageAnimationChanged(double progress,
254 int current_page,
255 int target_page) {
256 int custom_launcher_page_index =
257 GetPageIndexForState(AppListModel::STATE_CUSTOM_LAUNCHER_PAGE);
258 if (custom_launcher_page_index == target_page) {
259 app_list_main_view_->view_delegate()->CustomLauncherPageAnimationChanged(
260 progress);
261 } else if (custom_launcher_page_index == current_page) {
262 app_list_main_view_->view_delegate()->CustomLauncherPageAnimationChanged(
263 1 - progress);
267 void ContentsView::UpdatePageBounds() {
268 // The bounds calculations will potentially be mid-transition (depending on
269 // the state of the PaginationModel).
270 int current_page = std::max(0, pagination_model_.selected_page());
271 int target_page = current_page;
272 double progress = 1;
273 if (pagination_model_.has_transition()) {
274 const PaginationModel::Transition& transition =
275 pagination_model_.transition();
276 if (pagination_model_.is_valid_page(transition.target_page)) {
277 target_page = transition.target_page;
278 progress = transition.progress;
282 NotifyCustomLauncherPageAnimationChanged(progress, current_page, target_page);
284 bool reverse;
285 ContentsAnimator* animator =
286 GetAnimatorForTransition(current_page, target_page, &reverse);
288 // Animate linearly (the PaginationModel handles easing).
289 if (reverse)
290 animator->Update(1 - progress, target_page, current_page);
291 else
292 animator->Update(progress, current_page, target_page);
295 PaginationModel* ContentsView::GetAppsPaginationModel() {
296 return apps_container_view_->apps_grid_view()->pagination_model();
299 void ContentsView::AddAnimator(AppListModel::State from_state,
300 AppListModel::State to_state,
301 scoped_ptr<ContentsAnimator> animator) {
302 int from_page = GetPageIndexForState(from_state);
303 int to_page = GetPageIndexForState(to_state);
304 contents_animators_.insert(
305 std::make_pair(std::make_pair(from_page, to_page),
306 linked_ptr<ContentsAnimator>(animator.release())));
309 ContentsAnimator* ContentsView::GetAnimatorForTransition(int from_page,
310 int to_page,
311 bool* reverse) const {
312 auto it = contents_animators_.find(std::make_pair(from_page, to_page));
313 if (it != contents_animators_.end()) {
314 *reverse = false;
315 return it->second.get();
318 it = contents_animators_.find(std::make_pair(to_page, from_page));
319 if (it != contents_animators_.end()) {
320 *reverse = true;
321 return it->second.get();
324 *reverse = false;
325 return default_animator_.get();
328 void ContentsView::ShowFolderContent(AppListFolderItem* item) {
329 apps_container_view_->ShowActiveFolder(item);
332 void ContentsView::Prerender() {
333 apps_container_view_->apps_grid_view()->Prerender();
336 views::View* ContentsView::GetPageView(int index) const {
337 return view_model_->view_at(index);
340 SearchBoxView* ContentsView::GetSearchBoxView() const {
341 return app_list_main_view_->search_box_view();
344 void ContentsView::AddBlankPageForTesting() {
345 AddLauncherPage(new views::View);
346 pagination_model_.SetTotalPages(view_model_->view_size());
349 int ContentsView::AddLauncherPage(views::View* view) {
350 int page_index = view_model_->view_size();
351 AddChildView(view);
352 view_model_->Add(view, page_index);
353 return page_index;
356 int ContentsView::AddLauncherPage(views::View* view,
357 AppListModel::State state) {
358 int page_index = AddLauncherPage(view);
359 bool success =
360 state_to_view_.insert(std::make_pair(state, page_index)).second;
361 success = success &&
362 view_to_state_.insert(std::make_pair(page_index, state)).second;
364 // There shouldn't be duplicates in either map.
365 DCHECK(success);
366 return page_index;
369 gfx::Rect ContentsView::GetDefaultSearchBoxBounds() const {
370 gfx::Rect search_box_bounds(0, 0, GetDefaultContentsSize().width(),
371 GetSearchBoxView()->GetPreferredSize().height());
372 if (switches::IsExperimentalAppListEnabled()) {
373 search_box_bounds.set_y(kExperimentalWindowPadding);
374 search_box_bounds.Inset(kExperimentalWindowPadding, 0);
376 return search_box_bounds;
379 gfx::Rect ContentsView::GetSearchBoxBoundsForState(
380 AppListModel::State state) const {
381 // On the start page, the search box is in a different location.
382 if (state == AppListModel::STATE_START) {
383 DCHECK(start_page_view_);
384 // Convert to ContentsView space, assuming that the StartPageView is in the
385 // ContentsView's default bounds.
386 return start_page_view_->GetSearchBoxBounds() +
387 GetDefaultContentsBounds().OffsetFromOrigin();
390 return GetDefaultSearchBoxBounds();
393 gfx::Rect ContentsView::GetSearchBoxBoundsForPageIndex(int index) const {
394 return GetSearchBoxBoundsForState(GetStateForPageIndex(index));
397 gfx::Rect ContentsView::GetDefaultContentsBounds() const {
398 gfx::Rect bounds(gfx::Point(0, GetDefaultSearchBoxBounds().bottom()),
399 GetDefaultContentsSize());
400 return bounds;
403 gfx::Rect ContentsView::GetCustomPageCollapsedBounds() const {
404 gfx::Rect bounds(GetContentsBounds());
405 int page_height = bounds.height();
406 bounds.set_y(page_height - kCustomPageCollapsedHeight);
407 return bounds;
410 bool ContentsView::ShouldShowCustomPageClickzone() const {
411 return custom_page_view_ && IsStateActive(AppListModel::STATE_START) &&
412 app_list_main_view_->model()->custom_launcher_page_enabled();
415 bool ContentsView::Back() {
416 AppListModel::State state = view_to_state_[GetActivePageIndex()];
417 switch (state) {
418 case AppListModel::STATE_START:
419 // Close the app list when Back() is called from the start page.
420 return false;
421 case AppListModel::STATE_CUSTOM_LAUNCHER_PAGE:
422 if (app_list_main_view_->model()->PopCustomLauncherPageSubpage())
423 app_list_main_view_->view_delegate()->CustomLauncherPagePopSubpage();
424 else
425 SetActivePage(GetPageIndexForState(AppListModel::STATE_START));
426 break;
427 case AppListModel::STATE_APPS:
428 if (apps_container_view_->IsInFolderView())
429 apps_container_view_->app_list_folder_view()->CloseFolderPage();
430 else
431 SetActivePage(GetPageIndexForState(AppListModel::STATE_START));
432 break;
433 case AppListModel::STATE_SEARCH_RESULTS:
434 GetSearchBoxView()->ClearSearch();
435 ShowSearchResults(false);
436 break;
437 case AppListModel::INVALID_STATE:
438 NOTREACHED();
439 break;
441 return true;
444 gfx::Size ContentsView::GetDefaultContentsSize() const {
445 const gfx::Size container_size =
446 apps_container_view_->apps_grid_view()->GetPreferredSize();
447 const gfx::Size results_size =
448 search_results_list_view_ ? search_results_list_view_->GetPreferredSize()
449 : gfx::Size();
451 int width = std::max(container_size.width(), results_size.width());
452 int height = std::max(container_size.height(), results_size.height());
453 return gfx::Size(width, height);
456 gfx::Size ContentsView::GetPreferredSize() const {
457 gfx::Rect search_box_bounds = GetDefaultSearchBoxBounds();
458 gfx::Rect default_contents_bounds = GetDefaultContentsBounds();
459 gfx::Vector2d bottom_right =
460 search_box_bounds.bottom_right().OffsetFromOrigin();
461 bottom_right.SetToMax(
462 default_contents_bounds.bottom_right().OffsetFromOrigin());
463 return gfx::Size(bottom_right.x(), bottom_right.y());
466 void ContentsView::Layout() {
467 // The search box is contained in a widget so set the bounds of the widget
468 // rather than the SearchBoxView. In athena, the search box widget will be the
469 // same as the app list widget so don't move it.
470 views::Widget* search_box_widget = GetSearchBoxView()->GetWidget();
471 if (search_box_widget && search_box_widget != GetWidget()) {
472 search_box_widget->SetBounds(
473 ConvertRectToWidget(GetSearchBoxBoundsForState(GetActiveState())));
476 // Immediately finish all current animations.
477 pagination_model_.FinishAnimation();
479 // Move the current view onto the screen, and all other views off screen to
480 // the left. (Since we are not animating, we don't need to be careful about
481 // which side we place the off-screen views onto.)
482 gfx::Rect rect(GetDefaultContentsBounds());
483 // Custom pages are aligned to the top of the window, not under the search
484 // box.
485 double progress = 0;
486 if (IsStateActive(AppListModel::STATE_CUSTOM_LAUNCHER_PAGE)) {
487 rect = GetContentsBounds();
488 progress = 1;
491 // Notify the custom launcher page that the active page has changed.
492 app_list_main_view_->view_delegate()->CustomLauncherPageAnimationChanged(
493 progress);
495 if (rect.IsEmpty())
496 return;
497 // TODO(mgiuca): Temporary work-around for http://crbug.com/441962 and
498 // http://crbug.com/446978. This will first be called while ContentsView is
499 // 0x0, which means that the child views will be positioned incorrectly in RTL
500 // mode (the position is based on the parent's size). When the parent is later
501 // resized, the children are not repositioned due to http://crbug.com/446407.
502 // Therefore, we must not position the children until the parent is the
503 // correct size.
504 // NOTE: There is a similar hack in AppsGridView::CalculateIdealBounds; both
505 // should be removed once http://crbug.com/446407 is resolved.
506 if (GetContentsBounds().IsEmpty())
507 return;
509 gfx::Rect offscreen_target(rect);
510 offscreen_target.set_x(-rect.width());
512 int current_page = GetActivePageIndex();
514 for (int i = 0; i < view_model_->view_size(); ++i) {
515 view_model_->view_at(i)
516 ->SetBoundsRect(i == current_page ? rect : offscreen_target);
519 // Custom locations of pages in certain states.
520 // Within the start page, the custom page is given its collapsed bounds.
521 int start_page_index = GetPageIndexForState(AppListModel::STATE_START);
522 if (current_page == start_page_index) {
523 if (custom_page_view_)
524 custom_page_view_->SetBoundsRect(GetCustomPageCollapsedBounds());
528 bool ContentsView::OnKeyPressed(const ui::KeyEvent& event) {
529 return view_model_->view_at(GetActivePageIndex())->OnKeyPressed(event);
532 void ContentsView::TotalPagesChanged() {
535 void ContentsView::SelectedPageChanged(int old_selected, int new_selected) {
536 // TODO(mgiuca): This should be generalized so we call a virtual OnShow and
537 // OnHide method for each page.
538 if (!start_page_view_)
539 return;
541 if (ShouldShowCustomPageClickzone())
542 start_page_view_->OnShow();
543 else
544 start_page_view_->OnHide();
547 void ContentsView::TransitionStarted() {
550 void ContentsView::TransitionChanged() {
551 UpdatePageBounds();
554 } // namespace app_list