blob: 44fef48911dcaa2f6beb3508e45d9cb36682bbb8 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/app_list/views/contents_view.h"
#include <algorithm>
#include <vector>
#include "base/logging.h"
#include "ui/app_list/app_list_constants.h"
#include "ui/app_list/app_list_switches.h"
#include "ui/app_list/app_list_view_delegate.h"
#include "ui/app_list/views/app_list_folder_view.h"
#include "ui/app_list/views/app_list_main_view.h"
#include "ui/app_list/views/apps_container_view.h"
#include "ui/app_list/views/apps_grid_view.h"
#include "ui/app_list/views/custom_launcher_page_view.h"
#include "ui/app_list/views/search_box_view.h"
#include "ui/app_list/views/search_result_list_view.h"
#include "ui/app_list/views/search_result_page_view.h"
#include "ui/app_list/views/search_result_tile_item_list_view.h"
#include "ui/app_list/views/start_page_view.h"
#include "ui/events/event.h"
#include "ui/resources/grit/ui_resources.h"
#include "ui/views/view_model.h"
#include "ui/views/widget/widget.h"
namespace app_list {
ContentsView::ContentsView(AppListMainView* app_list_main_view)
: apps_container_view_(nullptr),
search_results_page_view_(nullptr),
start_page_view_(nullptr),
custom_page_view_(nullptr),
app_list_main_view_(app_list_main_view),
page_before_search_(0) {
pagination_model_.SetTransitionDurations(kPageTransitionDurationInMs,
kOverscrollPageTransitionDurationMs);
pagination_model_.AddObserver(this);
}
ContentsView::~ContentsView() {
pagination_model_.RemoveObserver(this);
}
void ContentsView::Init(AppListModel* model) {
DCHECK(model);
AppListViewDelegate* view_delegate = app_list_main_view_->view_delegate();
if (app_list::switches::IsExperimentalAppListEnabled()) {
std::vector<views::View*> custom_page_views =
view_delegate->CreateCustomPageWebViews(GetLocalBounds().size());
// Only add the first custom page view as STATE_CUSTOM_LAUNCHER_PAGE. Ignore
// any subsequent custom pages.
if (!custom_page_views.empty()) {
custom_page_view_ = new CustomLauncherPageView(custom_page_views[0]);
AddLauncherPage(custom_page_view_,
AppListModel::STATE_CUSTOM_LAUNCHER_PAGE);
}
// Start page.
start_page_view_ = new StartPageView(app_list_main_view_, view_delegate);
AddLauncherPage(start_page_view_, AppListModel::STATE_START);
}
// Search results UI.
search_results_page_view_ = new SearchResultPageView();
AppListModel::SearchResults* results = view_delegate->GetModel()->results();
search_results_page_view_->AddSearchResultContainerView(
results, new SearchResultListView(app_list_main_view_, view_delegate));
if (app_list::switches::IsExperimentalAppListEnabled()) {
search_results_page_view_->AddSearchResultContainerView(
results, new SearchResultTileItemListView(
GetSearchBoxView()->search_box(), view_delegate));
}
AddLauncherPage(search_results_page_view_,
AppListModel::STATE_SEARCH_RESULTS);
apps_container_view_ = new AppsContainerView(app_list_main_view_, model);
AddLauncherPage(apps_container_view_, AppListModel::STATE_APPS);
int initial_page_index = app_list::switches::IsExperimentalAppListEnabled()
? GetPageIndexForState(AppListModel::STATE_START)
: GetPageIndexForState(AppListModel::STATE_APPS);
DCHECK_GE(initial_page_index, 0);
page_before_search_ = initial_page_index;
// Must only call SetTotalPages once all the launcher pages have been added
// (as it will trigger a SelectedPageChanged call).
pagination_model_.SetTotalPages(app_list_pages_.size());
// Page 0 is selected by SetTotalPages and needs to be 'hidden' when selecting
// the initial page.
app_list_pages_[GetActivePageIndex()]->OnWillBeHidden();
pagination_model_.SelectPage(initial_page_index, false);
ActivePageChanged();
}
void ContentsView::CancelDrag() {
if (apps_container_view_->apps_grid_view()->has_dragged_view())
apps_container_view_->apps_grid_view()->EndDrag(true);
if (apps_container_view_->app_list_folder_view()
->items_grid_view()
->has_dragged_view()) {
apps_container_view_->app_list_folder_view()->items_grid_view()->EndDrag(
true);
}
}
void ContentsView::SetDragAndDropHostOfCurrentAppList(
ApplicationDragAndDropHost* drag_and_drop_host) {
apps_container_view_->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host);
}
void ContentsView::SetActiveState(AppListModel::State state) {
SetActiveState(state, true);
}
void ContentsView::SetActiveState(AppListModel::State state, bool animate) {
if (IsStateActive(state))
return;
SetActiveStateInternal(GetPageIndexForState(state), false, animate);
}
int ContentsView::GetActivePageIndex() const {
// The active page is changed at the beginning of an animation, not the end.
return pagination_model_.SelectedTargetPage();
}
AppListModel::State ContentsView::GetActiveState() const {
return GetStateForPageIndex(GetActivePageIndex());
}
bool ContentsView::IsStateActive(AppListModel::State state) const {
int active_page_index = GetActivePageIndex();
return active_page_index >= 0 &&
GetPageIndexForState(state) == active_page_index;
}
int ContentsView::GetPageIndexForState(AppListModel::State state) const {
// Find the index of the view corresponding to the given state.
std::map<AppListModel::State, int>::const_iterator it =
state_to_view_.find(state);
if (it == state_to_view_.end())
return -1;
return it->second;
}
AppListModel::State ContentsView::GetStateForPageIndex(int index) const {
std::map<int, AppListModel::State>::const_iterator it =
view_to_state_.find(index);
if (it == view_to_state_.end())
return AppListModel::INVALID_STATE;
return it->second;
}
int ContentsView::NumLauncherPages() const {
return pagination_model_.total_pages();
}
void ContentsView::SetActiveStateInternal(int page_index,
bool show_search_results,
bool animate) {
if (!GetPageView(page_index)->visible())
return;
if (!show_search_results)
page_before_search_ = page_index;
app_list_pages_[GetActivePageIndex()]->OnWillBeHidden();
// Start animating to the new page.
pagination_model_.SelectPage(page_index, animate);
ActivePageChanged();
if (!animate)
Layout();
}
void ContentsView::ActivePageChanged() {
AppListModel::State state = AppListModel::INVALID_STATE;
std::map<int, AppListModel::State>::const_iterator it =
view_to_state_.find(GetActivePageIndex());
if (it != view_to_state_.end())
state = it->second;
app_list_pages_[GetActivePageIndex()]->OnWillBeShown();
app_list_main_view_->model()->SetState(state);
if (switches::IsExperimentalAppListEnabled()) {
DCHECK(start_page_view_);
// Set the visibility of the search box's back button.
app_list_main_view_->search_box_view()->back_button()->SetVisible(
state != AppListModel::STATE_START);
app_list_main_view_->search_box_view()->Layout();
bool folder_active = (state == AppListModel::STATE_APPS)
? apps_container_view_->IsInFolderView() : false;
app_list_main_view_->search_box_view()->SetBackButtonLabel(folder_active);
// Whenever the page changes, the custom launcher page is considered to have
// been reset.
app_list_main_view_->model()->ClearCustomLauncherPageSubpages();
}
app_list_main_view_->search_box_view()->ResetTabFocus(false);
}
void ContentsView::ShowSearchResults(bool show) {
int search_page = GetPageIndexForState(AppListModel::STATE_SEARCH_RESULTS);
DCHECK_GE(search_page, 0);
search_results_page_view_->ClearSelectedIndex();
SetActiveStateInternal(show ? search_page : page_before_search_, show, true);
}
bool ContentsView::IsShowingSearchResults() const {
return IsStateActive(AppListModel::STATE_SEARCH_RESULTS);
}
void ContentsView::NotifyCustomLauncherPageAnimationChanged(double progress,
int current_page,
int target_page) {
int custom_launcher_page_index =
GetPageIndexForState(AppListModel::STATE_CUSTOM_LAUNCHER_PAGE);
if (custom_launcher_page_index == target_page) {
app_list_main_view_->view_delegate()->CustomLauncherPageAnimationChanged(
progress);
} else if (custom_launcher_page_index == current_page) {
app_list_main_view_->view_delegate()->CustomLauncherPageAnimationChanged(
1 - progress);
}
}
void ContentsView::UpdatePageBounds() {
// The bounds calculations will potentially be mid-transition (depending on
// the state of the PaginationModel).
int current_page = std::max(0, pagination_model_.selected_page());
int target_page = current_page;
double progress = 1;
if (pagination_model_.has_transition()) {
const PaginationModel::Transition& transition =
pagination_model_.transition();
if (pagination_model_.is_valid_page(transition.target_page)) {
target_page = transition.target_page;
progress = transition.progress;
}
}
NotifyCustomLauncherPageAnimationChanged(progress, current_page, target_page);
AppListModel::State current_state = GetStateForPageIndex(current_page);
AppListModel::State target_state = GetStateForPageIndex(target_page);
// Update app list pages.
for (AppListPage* page : app_list_pages_) {
gfx::Rect to_rect = page->GetPageBoundsForState(target_state);
gfx::Rect from_rect = page->GetPageBoundsForState(current_state);
if (from_rect == to_rect)
continue;
// Animate linearly (the PaginationModel handles easing).
gfx::Rect bounds(
gfx::Tween::RectValueBetween(progress, from_rect, to_rect));
page->SetBoundsRect(bounds);
page->OnAnimationUpdated(progress, current_state, target_state);
}
// Update the search box.
UpdateSearchBox(progress, current_state, target_state);
}
void ContentsView::UpdateSearchBox(double progress,
AppListModel::State current_state,
AppListModel::State target_state) {
AppListPage* from_page = GetPageView(GetPageIndexForState(current_state));
AppListPage* to_page = GetPageView(GetPageIndexForState(target_state));
SearchBoxView* search_box = GetSearchBoxView();
gfx::Rect search_box_from(from_page->GetSearchBoxBounds());
gfx::Rect search_box_to(to_page->GetSearchBoxBounds());
gfx::Rect search_box_rect =
gfx::Tween::RectValueBetween(progress, search_box_from, search_box_to);
int original_z_height = from_page->GetSearchBoxZHeight();
int target_z_height = to_page->GetSearchBoxZHeight();
if (original_z_height != target_z_height) {
gfx::ShadowValue original_shadow = GetShadowForZHeight(original_z_height);
gfx::ShadowValue target_shadow = GetShadowForZHeight(target_z_height);
gfx::Vector2d offset(gfx::Tween::LinearIntValueBetween(
progress, original_shadow.x(), target_shadow.x()),
gfx::Tween::LinearIntValueBetween(
progress, original_shadow.y(), target_shadow.y()));
search_box->SetShadow(gfx::ShadowValue(
offset, gfx::Tween::LinearIntValueBetween(
progress, original_shadow.blur(), target_shadow.blur()),
gfx::Tween::ColorValueBetween(progress, original_shadow.color(),
target_shadow.color())));
}
search_box->GetWidget()->SetBounds(
search_box->GetViewBoundsForSearchBoxContentsBounds(
ConvertRectToWidget(search_box_rect)));
}
PaginationModel* ContentsView::GetAppsPaginationModel() {
return apps_container_view_->apps_grid_view()->pagination_model();
}
void ContentsView::ShowFolderContent(AppListFolderItem* item) {
apps_container_view_->ShowActiveFolder(item);
}
void ContentsView::Prerender() {
apps_container_view_->apps_grid_view()->Prerender();
}
AppListPage* ContentsView::GetPageView(int index) const {
DCHECK_GT(static_cast<int>(app_list_pages_.size()), index);
return app_list_pages_[index];
}
SearchBoxView* ContentsView::GetSearchBoxView() const {
return app_list_main_view_->search_box_view();
}
int ContentsView::AddLauncherPage(AppListPage* view) {
view->set_contents_view(this);
AddChildView(view);
app_list_pages_.push_back(view);
return app_list_pages_.size() - 1;
}
int ContentsView::AddLauncherPage(AppListPage* view,
AppListModel::State state) {
int page_index = AddLauncherPage(view);
bool success =
state_to_view_.insert(std::make_pair(state, page_index)).second;
success = success &&
view_to_state_.insert(std::make_pair(page_index, state)).second;
// There shouldn't be duplicates in either map.
DCHECK(success);
return page_index;
}
gfx::Rect ContentsView::GetDefaultSearchBoxBounds() const {
gfx::Rect search_box_bounds(0, 0, GetDefaultContentsSize().width(),
GetSearchBoxView()->GetPreferredSize().height());
if (switches::IsExperimentalAppListEnabled()) {
search_box_bounds.set_y(kExperimentalSearchBoxPadding);
search_box_bounds.Inset(kExperimentalSearchBoxPadding, 0);
}
return search_box_bounds;
}
gfx::Rect ContentsView::GetSearchBoxBoundsForState(
AppListModel::State state) const {
AppListPage* page = GetPageView(GetPageIndexForState(state));
return page->GetSearchBoxBounds();
}
gfx::Rect ContentsView::GetDefaultContentsBounds() const {
gfx::Rect bounds(gfx::Point(0, GetDefaultSearchBoxBounds().bottom()),
GetDefaultContentsSize());
return bounds;
}
bool ContentsView::Back() {
AppListModel::State state = view_to_state_[GetActivePageIndex()];
switch (state) {
case AppListModel::STATE_START:
// Close the app list when Back() is called from the start page.
return false;
case AppListModel::STATE_CUSTOM_LAUNCHER_PAGE:
if (app_list_main_view_->model()->PopCustomLauncherPageSubpage())
app_list_main_view_->view_delegate()->CustomLauncherPagePopSubpage();
else
SetActiveState(AppListModel::STATE_START);
break;
case AppListModel::STATE_APPS:
if (apps_container_view_->IsInFolderView())
apps_container_view_->app_list_folder_view()->CloseFolderPage();
else
SetActiveState(AppListModel::STATE_START);
break;
case AppListModel::STATE_SEARCH_RESULTS:
GetSearchBoxView()->ClearSearch();
ShowSearchResults(false);
break;
case AppListModel::INVALID_STATE: // Falls through.
NOTREACHED();
break;
}
return true;
}
gfx::Size ContentsView::GetDefaultContentsSize() const {
return apps_container_view_->apps_grid_view()->GetPreferredSize();
}
gfx::Size ContentsView::GetPreferredSize() const {
gfx::Rect search_box_bounds = GetDefaultSearchBoxBounds();
gfx::Rect default_contents_bounds = GetDefaultContentsBounds();
gfx::Vector2d bottom_right =
search_box_bounds.bottom_right().OffsetFromOrigin();
bottom_right.SetToMax(
default_contents_bounds.bottom_right().OffsetFromOrigin());
return gfx::Size(bottom_right.x(), bottom_right.y());
}
void ContentsView::Layout() {
// Immediately finish all current animations.
pagination_model_.FinishAnimation();
double progress =
IsStateActive(AppListModel::STATE_CUSTOM_LAUNCHER_PAGE) ? 1 : 0;
// Notify the custom launcher page that the active page has changed.
app_list_main_view_->view_delegate()->CustomLauncherPageAnimationChanged(
progress);
if (GetContentsBounds().IsEmpty())
return;
for (AppListPage* page : app_list_pages_) {
page->SetBoundsRect(page->GetPageBoundsForState(GetActiveState()));
}
// The search box is contained in a widget so set the bounds of the widget
// rather than the SearchBoxView.
views::Widget* search_box_widget = GetSearchBoxView()->GetWidget();
if (search_box_widget && search_box_widget != GetWidget()) {
gfx::Rect search_box_bounds = GetSearchBoxBoundsForState(GetActiveState());
search_box_widget->SetBounds(ConvertRectToWidget(
GetSearchBoxView()->GetViewBoundsForSearchBoxContentsBounds(
search_box_bounds)));
}
}
bool ContentsView::OnKeyPressed(const ui::KeyEvent& event) {
bool handled = app_list_pages_[GetActivePageIndex()]->OnKeyPressed(event);
if (!handled) {
if (event.key_code() == ui::VKEY_TAB && event.IsShiftDown()) {
GetSearchBoxView()->MoveTabFocus(true);
handled = true;
}
}
return handled;
}
void ContentsView::TotalPagesChanged() {
}
void ContentsView::SelectedPageChanged(int old_selected, int new_selected) {
if (old_selected >= 0)
app_list_pages_[old_selected]->OnHidden();
if (new_selected >= 0)
app_list_pages_[new_selected]->OnShown();
}
void ContentsView::TransitionStarted() {
}
void ContentsView::TransitionChanged() {
UpdatePageBounds();
}
} // namespace app_list