// 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/pagination_model.h" #include #include "ui/app_list/pagination_model_observer.h" #include "ui/gfx/animation/slide_animation.h" namespace app_list { PaginationModel::PaginationModel() : total_pages_(-1), selected_page_(-1), transition_(-1, 0), pending_selected_page_(-1), transition_duration_ms_(0), overscroll_transition_duration_ms_(0), last_overscroll_target_page_(0) { } PaginationModel::~PaginationModel() { } void PaginationModel::SetTotalPages(int total_pages) { if (total_pages == total_pages_) return; total_pages_ = total_pages; if (selected_page_ < 0) SelectPage(0, false /* animate */); if (selected_page_ >= total_pages_) SelectPage(std::max(total_pages_ - 1, 0), false /* animate */); FOR_EACH_OBSERVER(PaginationModelObserver, observers_, TotalPagesChanged()); } void PaginationModel::SelectPage(int page, bool animate) { if (animate) { // -1 and |total_pages_| are valid target page for animation. DCHECK(page >= -1 && page <= total_pages_); if (!transition_animation_) { if (page == selected_page_) return; // Suppress over scroll animation if the same one happens too fast. if (!is_valid_page(page)) { const base::TimeTicks now = base::TimeTicks::Now(); if (page == last_overscroll_target_page_) { const int kMinOverScrollTimeGapInMs = 500; const base::TimeDelta time_elapsed = now - last_overscroll_animation_start_time_; if (time_elapsed.InMilliseconds() < kMinOverScrollTimeGapInMs) return; } last_overscroll_target_page_ = page; last_overscroll_animation_start_time_ = now; } // Creates an animation if there is not one. StartTransitionAnimation(Transition(page, 0)); return; } else { const bool showing = transition_animation_->IsShowing(); const int from_page = showing ? selected_page_ : transition_.target_page; const int to_page = showing ? transition_.target_page : selected_page_; if (from_page == page) { if (showing) transition_animation_->Hide(); else transition_animation_->Show(); pending_selected_page_ = -1; } else if (to_page != page) { pending_selected_page_ = page; } else { pending_selected_page_ = -1; } } } else { DCHECK(total_pages_ == 0 || (page >= 0 && page < total_pages_)); if (page == selected_page_) return; ResetTransitionAnimation(); int old_selected = selected_page_; selected_page_ = page; NotifySelectedPageChanged(old_selected, selected_page_); } } 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. DCHECK(transition.target_page >= -1 && transition.target_page <= total_pages_); DCHECK(transition.progress >= 0 && transition.progress <= 1); if (transition_.Equals(transition)) return; transition_ = transition; NotifyTransitionChanged(); } void PaginationModel::SetTransitionDurations(int duration_ms, int overscroll_duration_ms) { transition_duration_ms_ = duration_ms; overscroll_transition_duration_ms_ = overscroll_duration_ms; } void PaginationModel::StartScroll() { // Cancels current transition animation (if any). transition_animation_.reset(); } void PaginationModel::UpdateScroll(double delta) { // Translates scroll delta to desired page change direction. int page_change_dir = delta > 0 ? -1 : 1; // Initializes a transition if there is none. if (!has_transition()) transition_.target_page = CalculateTargetPage(page_change_dir); // Updates transition progress. int transition_dir = transition_.target_page > selected_page_ ? 1 : -1; double progress = transition_.progress + fabs(delta) * page_change_dir * transition_dir; if (progress < 0) { if (transition_.progress) { transition_.progress = 0; NotifyTransitionChanged(); } clear_transition(); } else if (progress > 1) { if (is_valid_page(transition_.target_page)) { SelectPage(transition_.target_page, false); clear_transition(); } } else { transition_.progress = progress; NotifyTransitionChanged(); } } void PaginationModel::EndScroll(bool cancel) { if (!has_transition()) return; StartTransitionAnimation(transition_); if (cancel) transition_animation_->Hide(); } bool PaginationModel::IsRevertingCurrentTransition() const { // Use !IsShowing() so that we return true at the end of hide animation. return transition_animation_ && !transition_animation_->IsShowing(); } void PaginationModel::AddObserver(PaginationModelObserver* observer) { observers_.AddObserver(observer); } 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, observers_, SelectedPageChanged(old_selected, new_selected)); } void PaginationModel::NotifyTransitionStarted() { FOR_EACH_OBSERVER(PaginationModelObserver, observers_, TransitionStarted()); } void PaginationModel::NotifyTransitionChanged() { FOR_EACH_OBSERVER(PaginationModelObserver, observers_, TransitionChanged()); } int PaginationModel::CalculateTargetPage(int delta) const { DCHECK_GT(total_pages_, 0); const int target_page = SelectedTargetPage() + delta; int start_page = 0; int end_page = total_pages_ - 1; // Use invalid page when |selected_page_| is at ends. if (target_page < start_page && selected_page_ == start_page) start_page = -1; else if (target_page > end_page && selected_page_ == end_page) end_page = total_pages_; return std::max(start_page, std::min(end_page, target_page)); } void PaginationModel::StartTransitionAnimation(const Transition& transition) { DCHECK(selected_page_ != transition.target_page); NotifyTransitionStarted(); SetTransition(transition); transition_animation_.reset(new gfx::SlideAnimation(this)); transition_animation_->SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN); transition_animation_->Reset(transition_.progress); const int duration = is_valid_page(transition_.target_page) ? transition_duration_ms_ : overscroll_transition_duration_ms_; if (duration) transition_animation_->SetSlideDuration(duration); transition_animation_->Show(); } void PaginationModel::ResetTransitionAnimation() { transition_animation_.reset(); transition_.target_page = -1; transition_.progress = 0; pending_selected_page_ = -1; } void PaginationModel::AnimationProgressed(const gfx::Animation* animation) { transition_.progress = transition_animation_->GetCurrentValue(); NotifyTransitionChanged(); } void PaginationModel::AnimationEnded(const gfx::Animation* animation) { // Save |pending_selected_page_| because SelectPage resets it. int next_target = pending_selected_page_; if (transition_animation_->GetCurrentValue() == 1) { // Showing animation ends. if (!is_valid_page(transition_.target_page)) { // If target page is not in valid range, reverse the animation. transition_animation_->Hide(); return; } // Otherwise, change page and finish the transition. DCHECK(selected_page_ != transition_.target_page); SelectPage(transition_.target_page, false /* animate */); } else if (transition_animation_->GetCurrentValue() == 0) { // Hiding animation ends. No page change should happen. ResetTransitionAnimation(); } if (next_target >= 0) SelectPage(next_target, true); } } // namespace app_list