blob: f1ddb2b500b1a21e080aa9f523c9eff4bdeed7fc [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/contents_switcher_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/start_page_view.h"
#include "ui/events/event.h"
#include "ui/gfx/animation/tween.h"
#include "ui/resources/grit/ui_resources.h"
#include "ui/views/view_model.h"
#include "ui/views/view_model_utils.h"
namespace app_list {
ContentsView::ContentsView(AppListMainView* app_list_main_view)
: search_results_view_(NULL),
start_page_view_(NULL),
app_list_main_view_(app_list_main_view),
contents_switcher_view_(NULL),
view_model_(new views::ViewModel),
page_before_search_(0) {
pagination_model_.SetTransitionDurations(kPageTransitionDurationInMs,
kOverscrollPageTransitionDurationMs);
pagination_model_.AddObserver(this);
}
ContentsView::~ContentsView() {
pagination_model_.RemoveObserver(this);
if (contents_switcher_view_)
pagination_model_.RemoveObserver(contents_switcher_view_);
}
void ContentsView::Init(AppListModel* model,
AppListViewDelegate* view_delegate) {
DCHECK(model);
if (app_list::switches::IsExperimentalAppListEnabled()) {
std::vector<views::View*> custom_page_views =
view_delegate->CreateCustomPageWebViews(GetLocalBounds().size());
for (std::vector<views::View*>::const_iterator it =
custom_page_views.begin();
it != custom_page_views.end();
++it) {
AddLauncherPage(*it, IDR_APP_LIST_NOTIFICATIONS_ICON);
}
start_page_view_ = new StartPageView(app_list_main_view_, view_delegate);
AddLauncherPage(
start_page_view_, IDR_APP_LIST_SEARCH_ICON, AppListModel::STATE_START);
} else {
search_results_view_ =
new SearchResultListView(app_list_main_view_, view_delegate);
AddLauncherPage(
search_results_view_, 0, AppListModel::STATE_SEARCH_RESULTS);
search_results_view_->SetResults(model->results());
}
apps_container_view_ = new AppsContainerView(app_list_main_view_, model);
AddLauncherPage(
apps_container_view_, IDR_APP_LIST_APPS_ICON, 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;
pagination_model_.SelectPage(initial_page_index, false);
ActivePageChanged(false);
}
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::SetContentsSwitcherView(
ContentsSwitcherView* contents_switcher_view) {
DCHECK(!contents_switcher_view_);
contents_switcher_view_ = contents_switcher_view;
if (contents_switcher_view_)
pagination_model_.AddObserver(contents_switcher_view_);
}
void ContentsView::SetActivePage(int page_index) {
if (GetActivePageIndex() == page_index)
return;
SetActivePageInternal(page_index, false);
}
int ContentsView::GetActivePageIndex() const {
// The active page is changed at the beginning of an animation, not the end.
return pagination_model_.SelectedTargetPage();
}
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;
}
int ContentsView::NumLauncherPages() const {
return pagination_model_.total_pages();
}
void ContentsView::SetActivePageInternal(int page_index,
bool show_search_results) {
if (!show_search_results)
page_before_search_ = page_index;
// Start animating to the new page.
pagination_model_.SelectPage(page_index, true);
ActivePageChanged(show_search_results);
}
void ContentsView::ActivePageChanged(bool show_search_results) {
AppListModel::State state = AppListModel::INVALID_STATE;
// TODO(calamity): This does not report search results being shown in the
// experimental app list as a boolean is currently used to indicate whether
// search results are showing. See http://crbug.com/427787/.
std::map<int, AppListModel::State>::const_iterator it =
view_to_state_.find(pagination_model_.SelectedTargetPage());
if (it != view_to_state_.end())
state = it->second;
app_list_main_view_->model()->SetState(state);
// TODO(xiyuan): Highlight default match instead of the first.
if (IsStateActive(AppListModel::STATE_SEARCH_RESULTS) &&
search_results_view_->visible()) {
search_results_view_->SetSelectedIndex(0);
}
if (search_results_view_)
search_results_view_->UpdateAutoLaunchState();
if (IsStateActive(AppListModel::STATE_START)) {
if (show_search_results)
start_page_view_->ShowSearchResults();
else
start_page_view_->Reset();
}
// Notify parent AppListMainView of the page change.
app_list_main_view_->UpdateSearchBoxVisibility();
}
void ContentsView::ShowSearchResults(bool show) {
int search_page =
GetPageIndexForState(app_list::switches::IsExperimentalAppListEnabled()
? AppListModel::STATE_START
: AppListModel::STATE_SEARCH_RESULTS);
DCHECK_GE(search_page, 0);
SetActivePageInternal(show ? search_page : page_before_search_, show);
}
bool ContentsView::IsShowingSearchResults() const {
return app_list::switches::IsExperimentalAppListEnabled()
? IsStateActive(AppListModel::STATE_START) &&
start_page_view_->IsShowingSearchResults()
: IsStateActive(AppListModel::STATE_SEARCH_RESULTS);
}
gfx::Rect ContentsView::GetOffscreenPageBounds(int page_index) const {
gfx::Rect bounds(GetContentsBounds());
// The start page and search page origins are above; all other pages' origins
// are below.
int page_height = bounds.height();
bool origin_above =
GetPageIndexForState(AppListModel::STATE_START) == page_index ||
GetPageIndexForState(AppListModel::STATE_SEARCH_RESULTS) == page_index;
bounds.set_y(origin_above ? -page_height : page_height);
return bounds;
}
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;
}
}
// Move |current_page| from 0 to its origin. Move |target_page| from its
// origin to 0.
gfx::Rect on_screen(GetDefaultContentsBounds());
gfx::Rect current_page_origin(GetOffscreenPageBounds(current_page));
gfx::Rect target_page_origin(GetOffscreenPageBounds(target_page));
gfx::Rect current_page_rect(
gfx::Tween::RectValueBetween(progress, on_screen, current_page_origin));
gfx::Rect target_page_rect(
gfx::Tween::RectValueBetween(progress, target_page_origin, on_screen));
view_model_->view_at(current_page)->SetBoundsRect(current_page_rect);
view_model_->view_at(target_page)->SetBoundsRect(target_page_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();
}
views::View* ContentsView::GetPageView(int index) {
return view_model_->view_at(index);
}
void ContentsView::AddBlankPageForTesting() {
AddLauncherPage(new views::View, 0);
}
int ContentsView::AddLauncherPage(views::View* view, int resource_id) {
int page_index = view_model_->view_size();
AddChildView(view);
view_model_->Add(view, page_index);
if (contents_switcher_view_ && resource_id)
contents_switcher_view_->AddSwitcherButton(resource_id, page_index);
pagination_model_.SetTotalPages(view_model_->view_size());
return page_index;
}
int ContentsView::AddLauncherPage(views::View* view,
int resource_id,
AppListModel::State state) {
int page_index = AddLauncherPage(view, resource_id);
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(),
app_list_main_view_->search_box_view()->GetPreferredSize().height());
if (switches::IsExperimentalAppListEnabled()) {
search_box_bounds.set_y(kExperimentalWindowPadding);
search_box_bounds.Inset(kExperimentalWindowPadding, 0);
}
return search_box_bounds;
}
gfx::Rect ContentsView::GetDefaultContentsBounds() const {
gfx::Rect bounds(gfx::Point(0, GetDefaultSearchBoxBounds().bottom()),
GetDefaultContentsSize());
return bounds;
}
gfx::Size ContentsView::GetDefaultContentsSize() const {
const gfx::Size container_size =
apps_container_view_->apps_grid_view()->GetPreferredSize();
const gfx::Size results_size = search_results_view_
? search_results_view_->GetPreferredSize()
: gfx::Size();
int width = std::max(container_size.width(), results_size.width());
int height = std::max(container_size.height(), results_size.height());
return gfx::Size(width, height);
}
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();
// Move the current view onto the screen, and all other views off screen to
// the left. (Since we are not animating, we don't need to be careful about
// which side we place the off-screen views onto.)
gfx::Rect rect(GetDefaultContentsBounds());
if (rect.IsEmpty())
return;
// TODO(mgiuca): Temporary work-around for http://crbug.com/441962 and
// http://crbug.com/446978. This will first be called while ContentsView is
// 0x0, which means that the child views will be positioned incorrectly in RTL
// mode (the position is based on the parent's size). When the parent is later
// resized, the children are not repositioned due to http://crbug.com/446407.
// Therefore, we must not position the children until the parent is the
// correct size.
// NOTE: There is a similar hack in AppsGridView::CalculateIdealBounds; both
// should be removed once http://crbug.com/446407 is resolved.
if (GetContentsBounds().IsEmpty())
return;
gfx::Rect offscreen_target(rect);
offscreen_target.set_x(-rect.width());
for (int i = 0; i < view_model_->view_size(); ++i) {
view_model_->view_at(i)->SetBoundsRect(
i == pagination_model_.SelectedTargetPage() ? rect : offscreen_target);
}
}
bool ContentsView::OnKeyPressed(const ui::KeyEvent& event) {
return view_model_->view_at(GetActivePageIndex())->OnKeyPressed(event);
}
void ContentsView::TotalPagesChanged() {
}
void ContentsView::SelectedPageChanged(int old_selected, int new_selected) {
}
void ContentsView::TransitionStarted() {
}
void ContentsView::TransitionChanged() {
UpdatePageBounds();
}
} // namespace app_list