Commit c17f2150 authored by mgiuca@chromium.org's avatar mgiuca@chromium.org

App list uses PaginationModel to transition between pages.

Should not affect behaviour of the normal app list. The experimental app
list now transitions pages in from the correct side of the screen,
instead of simply sliding pages out to the left.

BUG=377381

Review URL: https://codereview.chromium.org/316393002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@278361 0039d316-1c4b-4281-b951-d872f2087c98
parent 33fe4626
......@@ -99,6 +99,10 @@ void PaginationModel::SelectPageRelative(int delta, bool animate) {
SelectPage(CalculateTargetPage(delta), animate);
}
void PaginationModel::FinishAnimation() {
SelectPage(SelectedTargetPage(), false);
}
void PaginationModel::SetTransition(const Transition& transition) {
// -1 and |total_pages_| is a valid target page, which means user is at
// the end and there is no target page for this scroll.
......@@ -177,6 +181,20 @@ void PaginationModel::RemoveObserver(PaginationModelObserver* observer) {
observers_.RemoveObserver(observer);
}
int PaginationModel::SelectedTargetPage() const {
// If no animation, or animation is in reverse, just the selected page.
if (!transition_animation_ || !transition_animation_->IsShowing())
return selected_page_;
// If, at the end of the current animation, we will animate to another page,
// return that eventual page.
if (pending_selected_page_ >= 0)
return pending_selected_page_;
// Just the target of the current animation.
return transition_.target_page;
}
void PaginationModel::NotifySelectedPageChanged(int old_selected,
int new_selected) {
FOR_EACH_OBSERVER(PaginationModelObserver,
......@@ -194,14 +212,7 @@ void PaginationModel::NotifyTransitionChanged() {
int PaginationModel::CalculateTargetPage(int delta) const {
DCHECK_GT(total_pages_, 0);
int current_page = selected_page_;
if (transition_animation_ && transition_animation_->IsShowing()) {
current_page = pending_selected_page_ >= 0 ?
pending_selected_page_ : transition_.target_page;
}
const int target_page = current_page + delta;
const int target_page = SelectedTargetPage() + delta;
int start_page = 0;
int end_page = total_pages_ - 1;
......
......@@ -59,6 +59,10 @@ class APP_LIST_EXPORT PaginationModel : public gfx::AnimationDelegate {
// Selects a page by relative |delta|.
void SelectPageRelative(int delta, bool animate);
// Immediately completes all queued animations, jumping directly to the final
// target page.
void FinishAnimation();
void SetTransition(const Transition& transition);
void SetTransitionDurations(int duration_ms, int overscroll_duration_ms);
......@@ -93,6 +97,10 @@ class APP_LIST_EXPORT PaginationModel : public gfx::AnimationDelegate {
return transition_.target_page != -1 || transition_.progress != 0;
}
// Gets the page that the animation will eventually land on. If there is no
// active animation, just returns selected_page().
int SelectedTargetPage() const;
private:
void NotifySelectedPageChanged(int old_selected, int new_selected);
void NotifyTransitionStarted();
......
......@@ -78,6 +78,9 @@ class AppListViewTestContext {
// Tests displaying of the experimental app list and shows the start page.
void RunStartPageTest();
// Tests switching rapidly between multiple pages of the launcher.
void RunPageSwitchingAnimationTest();
// Tests changing the App List profile.
void RunProfileChangeTest();
......@@ -316,6 +319,49 @@ void AppListViewTestContext::RunStartPageTest() {
Close();
}
void AppListViewTestContext::RunPageSwitchingAnimationTest() {
if (test_type_ == EXPERIMENTAL) {
Show();
AppListMainView* main_view = view_->app_list_main_view();
// Checks on the main view.
EXPECT_NO_FATAL_FAILURE(CheckView(main_view));
EXPECT_NO_FATAL_FAILURE(CheckView(main_view->contents_view()));
ContentsView* contents_view = main_view->contents_view();
// Pad the ContentsView with blank pages so we have at least 3 views.
while (contents_view->NumLauncherPages() < 3)
contents_view->AddBlankPageForTesting();
contents_view->SetActivePage(0);
contents_view->Layout();
EXPECT_TRUE(IsViewAtOrigin(contents_view->GetPageView(0)));
EXPECT_FALSE(IsViewAtOrigin(contents_view->GetPageView(1)));
EXPECT_FALSE(IsViewAtOrigin(contents_view->GetPageView(2)));
// Change pages. View should not have moved without Layout().
contents_view->SetActivePage(1);
EXPECT_TRUE(IsViewAtOrigin(contents_view->GetPageView(0)));
EXPECT_FALSE(IsViewAtOrigin(contents_view->GetPageView(1)));
EXPECT_FALSE(IsViewAtOrigin(contents_view->GetPageView(2)));
// Change to a third page. This queues up the second animation behind the
// first.
contents_view->SetActivePage(2);
EXPECT_TRUE(IsViewAtOrigin(contents_view->GetPageView(0)));
EXPECT_FALSE(IsViewAtOrigin(contents_view->GetPageView(1)));
EXPECT_FALSE(IsViewAtOrigin(contents_view->GetPageView(2)));
// Call Layout(). Should jump to the third page.
contents_view->Layout();
EXPECT_FALSE(IsViewAtOrigin(contents_view->GetPageView(0)));
EXPECT_FALSE(IsViewAtOrigin(contents_view->GetPageView(1)));
EXPECT_TRUE(IsViewAtOrigin(contents_view->GetPageView(2)));
}
Close();
}
void AppListViewTestContext::RunProfileChangeTest() {
EXPECT_FALSE(view_->GetWidget()->IsVisible());
EXPECT_EQ(-1, GetPaginationModel()->total_pages());
......@@ -529,6 +575,15 @@ TEST_P(AppListViewTestDesktop, StartPageTest) {
EXPECT_NO_FATAL_FAILURE(test_context_->RunStartPageTest());
}
// Tests that the start page view operates correctly.
TEST_P(AppListViewTestAura, PageSwitchingAnimationTest) {
EXPECT_NO_FATAL_FAILURE(test_context_->RunPageSwitchingAnimationTest());
}
TEST_P(AppListViewTestDesktop, PageSwitchingAnimationTest) {
EXPECT_NO_FATAL_FAILURE(test_context_->RunPageSwitchingAnimationTest());
}
// Tests that the profile changes operate correctly.
TEST_P(AppListViewTestAura, ProfileChangeTest) {
EXPECT_NO_FATAL_FAILURE(test_context_->RunProfileChangeTest());
......
......@@ -10,7 +10,6 @@
#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/pagination_model.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"
......@@ -18,7 +17,6 @@
#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/views/animation/bounds_animator.h"
#include "ui/views/view_model.h"
#include "ui/views/view_model_utils.h"
......@@ -40,8 +38,7 @@ ContentsView::ContentsView(AppListMainView* app_list_main_view,
: search_results_view_(NULL),
start_page_view_(NULL),
app_list_main_view_(app_list_main_view),
view_model_(new views::ViewModel),
bounds_animator_(new views::BoundsAnimator(this)) {
view_model_(new views::ViewModel) {
DCHECK(model);
if (app_list::switches::IsExperimentalAppListEnabled()) {
......@@ -55,11 +52,14 @@ ContentsView::ContentsView(AppListMainView* app_list_main_view,
}
apps_container_view_ = new AppsContainerView(app_list_main_view, model);
active_page_ = AddLauncherPage(apps_container_view_, NAMED_PAGE_APPS);
int apps_page_index = AddLauncherPage(apps_container_view_, NAMED_PAGE_APPS);
pagination_model_.SelectPage(apps_page_index, false);
pagination_model_.AddObserver(this);
}
ContentsView::~ContentsView() {
pagination_model_.RemoveObserver(this);
}
void ContentsView::CancelDrag() {
......@@ -79,18 +79,23 @@ void ContentsView::SetDragAndDropHostOfCurrentAppList(
}
void ContentsView::SetActivePage(int page_index) {
if (active_page_ == 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::IsNamedPageActive(NamedPage named_page) const {
std::map<NamedPage, int>::const_iterator it =
named_page_to_view_.find(named_page);
if (it == named_page_to_view_.end())
return false;
return it->second == active_page_;
return it->second == GetActivePageIndex();
}
int ContentsView::GetPageIndexForNamedPage(NamedPage named_page) const {
......@@ -103,9 +108,14 @@ int ContentsView::GetPageIndexForNamedPage(NamedPage named_page) const {
return it->second;
}
int ContentsView::NumLauncherPages() const {
return pagination_model_.total_pages();
}
void ContentsView::SetActivePageInternal(int page_index,
bool show_search_results) {
active_page_ = page_index;
// Start animating to the new page.
pagination_model_.SelectPage(page_index, true);
ActivePageChanged(show_search_results);
}
......@@ -127,8 +137,6 @@ void ContentsView::ActivePageChanged(bool show_search_results) {
// Notify parent AppListMainView of the page change.
app_list_main_view_->UpdateSearchBoxVisibility();
AnimateToIdealBounds();
}
void ContentsView::ShowSearchResults(bool show) {
......@@ -146,49 +154,49 @@ bool ContentsView::IsShowingSearchResults() const {
: IsNamedPageActive(NAMED_PAGE_SEARCH_RESULTS);
}
void ContentsView::CalculateIdealBounds() {
void ContentsView::UpdatePageBounds() {
gfx::Rect rect(GetContentsBounds());
if (rect.IsEmpty())
return;
if (app_list::switches::IsExperimentalAppListEnabled()) {
gfx::Rect incoming_target(rect);
gfx::Rect outgoing_target(rect);
outgoing_target.set_x(-outgoing_target.width());
for (int i = 0; i < view_model_->view_size(); ++i) {
view_model_->set_ideal_bounds(
i, i == active_page_ ? incoming_target : outgoing_target);
// 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;
}
return;
}
gfx::Rect container_frame(rect);
gfx::Rect results_frame(rect);
// Offsets apps grid and result list based on |active_page_|.
// SearchResultListView is on top of apps grid. Visible view is left in
// visible area and invisible ones is put out of the visible area.
int contents_area_height = rect.height();
if (IsNamedPageActive(NAMED_PAGE_APPS))
results_frame.Offset(0, -contents_area_height);
else if (IsNamedPageActive(NAMED_PAGE_SEARCH_RESULTS))
container_frame.Offset(0, contents_area_height);
else
NOTREACHED() << "Page " << active_page_ << " invalid in current app list.";
gfx::Rect incoming_target(rect);
gfx::Rect outgoing_target(rect);
int dir = target_page > current_page ? -1 : 1;
view_model_->set_ideal_bounds(GetPageIndexForNamedPage(NAMED_PAGE_APPS),
container_frame);
view_model_->set_ideal_bounds(
GetPageIndexForNamedPage(NAMED_PAGE_SEARCH_RESULTS), results_frame);
}
if (app_list::switches::IsExperimentalAppListEnabled()) {
// The experimental app list transitions horizontally.
int page_width = rect.width();
int transition_offset = progress * page_width * dir;
void ContentsView::AnimateToIdealBounds() {
CalculateIdealBounds();
for (int i = 0; i < view_model_->view_size(); ++i) {
bounds_animator_->AnimateViewTo(view_model_->view_at(i),
view_model_->ideal_bounds(i));
outgoing_target.set_x(transition_offset);
incoming_target.set_x(dir < 0 ? transition_offset + page_width
: transition_offset - page_width);
} else {
// The normal app list transitions vertically.
int page_height = rect.height();
int transition_offset = progress * page_height * dir;
outgoing_target.set_y(transition_offset);
incoming_target.set_y(dir < 0 ? transition_offset + page_height
: transition_offset - page_height);
}
view_model_->view_at(current_page)->SetBoundsRect(outgoing_target);
view_model_->view_at(target_page)->SetBoundsRect(incoming_target);
}
PaginationModel* ContentsView::GetAppsPaginationModel() {
......@@ -205,10 +213,19 @@ void ContentsView::Prerender() {
apps_container_view_->apps_grid_view()->Prerender(selected_page);
}
views::View* ContentsView::GetPageView(int index) {
return view_model_->view_at(index);
}
void ContentsView::AddBlankPageForTesting() {
AddLauncherPage(new views::View);
}
int ContentsView::AddLauncherPage(views::View* view) {
int page_index = view_model_->view_size();
AddChildView(view);
view_model_->Add(view, page_index);
pagination_model_.SetTotalPages(view_model_->view_size());
return page_index;
}
......@@ -231,12 +248,27 @@ gfx::Size ContentsView::GetPreferredSize() const {
}
void ContentsView::Layout() {
CalculateIdealBounds();
views::ViewModelUtils::SetViewBoundsToIdealBounds(*view_model_);
// 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(GetContentsBounds());
if (rect.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(active_page_)->OnKeyPressed(event);
return view_model_->view_at(GetActivePageIndex())->OnKeyPressed(event);
}
bool ContentsView::OnMouseWheel(const ui::MouseWheelEvent& event) {
......@@ -259,6 +291,19 @@ bool ContentsView::OnMouseWheel(const ui::MouseWheelEvent& event) {
return false;
}
void ContentsView::TotalPagesChanged() {
}
void ContentsView::SelectedPageChanged(int old_selected, int new_selected) {
}
void ContentsView::TransitionStarted() {
}
void ContentsView::TransitionChanged() {
UpdatePageBounds();
}
void ContentsView::OnGestureEvent(ui::GestureEvent* event) {
if (!IsNamedPageActive(NAMED_PAGE_APPS))
return;
......
......@@ -11,10 +11,11 @@
#include "base/compiler_specific.h"
#include "base/memory/scoped_ptr.h"
#include "ui/app_list/app_list_export.h"
#include "ui/app_list/pagination_model.h"
#include "ui/app_list/pagination_model_observer.h"
#include "ui/views/view.h"
namespace views {
class BoundsAnimator;
class ViewModel;
}
......@@ -36,7 +37,8 @@ class StartPageView;
// one of which can be active at a given time. ContentsView provides the user
// interface for switching between launcher pages, and animates the transition
// between them.
class APP_LIST_EXPORT ContentsView : public views::View {
class APP_LIST_EXPORT ContentsView : public views::View,
public PaginationModelObserver {
public:
// Values of this enum denote special launcher pages that require hard-coding.
// Launcher pages are not required to have a NamedPage enum value.
......@@ -67,7 +69,7 @@ class APP_LIST_EXPORT ContentsView : public views::View {
void SetActivePage(int page_index);
// The index of the currently active launcher page.
int active_page_index() const { return active_page_; }
int GetActivePageIndex() const;
// True if |named_page| is the current active laucher page.
bool IsNamedPageActive(NamedPage named_page) const;
......@@ -75,11 +77,17 @@ class APP_LIST_EXPORT ContentsView : public views::View {
// Gets the index of a launcher page in |view_model_|, by NamedPage.
int GetPageIndexForNamedPage(NamedPage named_page) const;
int NumLauncherPages() const;
void Prerender();
AppsContainerView* apps_container_view() { return apps_container_view_; }
StartPageView* start_page_view() { return start_page_view_; }
SearchResultListView* search_results_view() { return search_results_view_; }
views::View* GetPageView(int index);
// Adds a blank launcher page. For use in tests only.
void AddBlankPageForTesting();
// Overridden from views::View:
virtual gfx::Size GetPreferredSize() const OVERRIDE;
......@@ -87,6 +95,12 @@ class APP_LIST_EXPORT ContentsView : public views::View {
virtual bool OnKeyPressed(const ui::KeyEvent& event) OVERRIDE;
virtual bool OnMouseWheel(const ui::MouseWheelEvent& event) OVERRIDE;
// Overridden from PaginationModelObserver:
virtual void TotalPagesChanged() OVERRIDE;
virtual void SelectedPageChanged(int old_selected, int new_selected) OVERRIDE;
virtual void TransitionStarted() OVERRIDE;
virtual void TransitionChanged() OVERRIDE;
private:
// Sets the active launcher page, accounting for whether the change is for
// search results.
......@@ -95,8 +109,9 @@ class APP_LIST_EXPORT ContentsView : public views::View {
// Invoked when active view is changed.
void ActivePageChanged(bool show_search_results);
void CalculateIdealBounds();
void AnimateToIdealBounds();
// Calculates and sets the bounds for the subviews. If there is currently an
// animation, this positions the views as appropriate for the current frame.
void UpdatePageBounds();
// Adds |view| as a new page to the end of the list of launcher pages. The
// view is inserted as a child of the ContentsView. There is no name
......@@ -109,15 +124,14 @@ class APP_LIST_EXPORT ContentsView : public views::View {
int AddLauncherPage(views::View* view, NamedPage named_page);
// Gets the PaginationModel owned by the AppsGridView.
// Note: This is different to |pagination_model_|, which manages top-level
// launcher-page pagination.
PaginationModel* GetAppsPaginationModel();
// Overridden from ui::EventHandler:
virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
virtual void OnScrollEvent(ui::ScrollEvent* event) OVERRIDE;
// Index into |view_model_| of the currently active page.
int active_page_;
// Special sub views of the ContentsView. All owned by the views hierarchy.
AppsContainerView* apps_container_view_;
SearchResultListView* search_results_view_;
......@@ -128,7 +142,9 @@ class APP_LIST_EXPORT ContentsView : public views::View {
scoped_ptr<views::ViewModel> view_model_;
// Maps NamedPage onto |view_model_| indices.
std::map<NamedPage, int> named_page_to_view_;
scoped_ptr<views::BoundsAnimator> bounds_animator_;
// Manages the pagination for the launcher pages.
PaginationModel pagination_model_;
DISALLOW_COPY_AND_ASSIGN(ContentsView);
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment