Home | History | Annotate | Download | only in app_list
      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.
      4 
      5 #include "ui/app_list/pagination_model.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "ui/app_list/pagination_model_observer.h"
     10 #include "ui/gfx/animation/slide_animation.h"
     11 
     12 namespace app_list {
     13 
     14 PaginationModel::PaginationModel()
     15     : total_pages_(-1),
     16       selected_page_(-1),
     17       transition_(-1, 0),
     18       pending_selected_page_(-1),
     19       transition_duration_ms_(0),
     20       overscroll_transition_duration_ms_(0),
     21       last_overscroll_target_page_(0) {
     22 }
     23 
     24 PaginationModel::~PaginationModel() {
     25 }
     26 
     27 void PaginationModel::SetTotalPages(int total_pages) {
     28   if (total_pages == total_pages_)
     29     return;
     30 
     31   total_pages_ = total_pages;
     32   if (selected_page_ < 0)
     33     SelectPage(0, false /* animate */);
     34   if (selected_page_ >= total_pages_)
     35     SelectPage(std::max(total_pages_ - 1, 0), false /* animate */);
     36   FOR_EACH_OBSERVER(PaginationModelObserver, observers_, TotalPagesChanged());
     37 }
     38 
     39 void PaginationModel::SelectPage(int page, bool animate) {
     40   if (animate) {
     41     // -1 and |total_pages_| are valid target page for animation.
     42     DCHECK(page >= -1 && page <= total_pages_);
     43 
     44     if (!transition_animation_) {
     45       if (page == selected_page_)
     46         return;
     47 
     48       // Suppress over scroll animation if the same one happens too fast.
     49       if (!is_valid_page(page)) {
     50         const base::TimeTicks now = base::TimeTicks::Now();
     51 
     52         if (page == last_overscroll_target_page_) {
     53           const int kMinOverScrollTimeGapInMs = 500;
     54           const base::TimeDelta time_elapsed =
     55                now - last_overscroll_animation_start_time_;
     56           if (time_elapsed.InMilliseconds() < kMinOverScrollTimeGapInMs)
     57             return;
     58         }
     59 
     60         last_overscroll_target_page_ = page;
     61         last_overscroll_animation_start_time_ = now;
     62       }
     63 
     64       // Creates an animation if there is not one.
     65       StartTransitionAnimation(Transition(page, 0));
     66       return;
     67     } else {
     68       const bool showing = transition_animation_->IsShowing();
     69       const int from_page = showing ? selected_page_ : transition_.target_page;
     70       const int to_page = showing ? transition_.target_page : selected_page_;
     71 
     72       if (from_page == page) {
     73         if (showing)
     74           transition_animation_->Hide();
     75         else
     76           transition_animation_->Show();
     77         pending_selected_page_ = -1;
     78       } else if (to_page != page) {
     79         pending_selected_page_ = page;
     80       } else {
     81         pending_selected_page_ = -1;
     82       }
     83     }
     84   } else {
     85     DCHECK(total_pages_ == 0 || (page >= 0 && page < total_pages_));
     86 
     87     if (page == selected_page_)
     88       return;
     89 
     90     ResetTransitionAnimation();
     91 
     92     int old_selected = selected_page_;
     93     selected_page_ = page;
     94     NotifySelectedPageChanged(old_selected, selected_page_);
     95   }
     96 }
     97 
     98 void PaginationModel::SelectPageRelative(int delta, bool animate) {
     99   SelectPage(CalculateTargetPage(delta), animate);
    100 }
    101 
    102 void PaginationModel::SetTransition(const Transition& transition) {
    103   // -1 and |total_pages_| is a valid target page, which means user is at
    104   // the end and there is no target page for this scroll.
    105   DCHECK(transition.target_page >= -1 &&
    106          transition.target_page <= total_pages_);
    107   DCHECK(transition.progress >= 0 && transition.progress <= 1);
    108 
    109   if (transition_.Equals(transition))
    110     return;
    111 
    112   transition_ = transition;
    113   NotifyTransitionChanged();
    114 }
    115 
    116 void PaginationModel::SetTransitionDurations(int duration_ms,
    117                                              int overscroll_duration_ms) {
    118   transition_duration_ms_ = duration_ms;
    119   overscroll_transition_duration_ms_ = overscroll_duration_ms;
    120 }
    121 
    122 void PaginationModel::StartScroll() {
    123   // Cancels current transition animation (if any).
    124   transition_animation_.reset();
    125 }
    126 
    127 void PaginationModel::UpdateScroll(double delta) {
    128   // Translates scroll delta to desired page change direction.
    129   int page_change_dir = delta > 0 ? -1 : 1;
    130 
    131   // Initializes a transition if there is none.
    132   if (!has_transition())
    133     transition_.target_page = CalculateTargetPage(page_change_dir);
    134 
    135   // Updates transition progress.
    136   int transition_dir = transition_.target_page > selected_page_ ? 1 : -1;
    137   double progress = transition_.progress +
    138       fabs(delta) * page_change_dir * transition_dir;
    139 
    140   if (progress < 0) {
    141     if (transition_.progress) {
    142       transition_.progress = 0;
    143       NotifyTransitionChanged();
    144     }
    145     clear_transition();
    146   } else if (progress > 1) {
    147     if (is_valid_page(transition_.target_page)) {
    148       SelectPage(transition_.target_page, false);
    149       clear_transition();
    150     }
    151   } else {
    152     transition_.progress = progress;
    153     NotifyTransitionChanged();
    154   }
    155 }
    156 
    157 void PaginationModel::EndScroll(bool cancel) {
    158   if (!has_transition())
    159     return;
    160 
    161   StartTransitionAnimation(transition_);
    162 
    163   if (cancel)
    164     transition_animation_->Hide();
    165 }
    166 
    167 bool PaginationModel::IsRevertingCurrentTransition() const {
    168   // Use !IsShowing() so that we return true at the end of hide animation.
    169   return transition_animation_ && !transition_animation_->IsShowing();
    170 }
    171 
    172 void PaginationModel::AddObserver(PaginationModelObserver* observer) {
    173   observers_.AddObserver(observer);
    174 }
    175 
    176 void PaginationModel::RemoveObserver(PaginationModelObserver* observer) {
    177   observers_.RemoveObserver(observer);
    178 }
    179 
    180 void PaginationModel::NotifySelectedPageChanged(int old_selected,
    181                                                 int new_selected) {
    182   FOR_EACH_OBSERVER(PaginationModelObserver,
    183                     observers_,
    184                     SelectedPageChanged(old_selected, new_selected));
    185 }
    186 
    187 void PaginationModel::NotifyTransitionStarted() {
    188   FOR_EACH_OBSERVER(PaginationModelObserver, observers_, TransitionStarted());
    189 }
    190 
    191 void PaginationModel::NotifyTransitionChanged() {
    192   FOR_EACH_OBSERVER(PaginationModelObserver, observers_, TransitionChanged());
    193 }
    194 
    195 int PaginationModel::CalculateTargetPage(int delta) const {
    196   DCHECK_GT(total_pages_, 0);
    197 
    198   int current_page = selected_page_;
    199   if (transition_animation_ && transition_animation_->IsShowing()) {
    200     current_page = pending_selected_page_ >= 0 ?
    201         pending_selected_page_ : transition_.target_page;
    202   }
    203 
    204   const int target_page = current_page + delta;
    205 
    206   int start_page = 0;
    207   int end_page = total_pages_ - 1;
    208 
    209   // Use invalid page when |selected_page_| is at ends.
    210   if (target_page < start_page && selected_page_ == start_page)
    211     start_page = -1;
    212   else if (target_page > end_page && selected_page_ == end_page)
    213     end_page = total_pages_;
    214 
    215   return std::max(start_page, std::min(end_page, target_page));
    216 }
    217 
    218 void PaginationModel::StartTransitionAnimation(const Transition& transition) {
    219   DCHECK(selected_page_ != transition.target_page);
    220 
    221   NotifyTransitionStarted();
    222   SetTransition(transition);
    223 
    224   transition_animation_.reset(new gfx::SlideAnimation(this));
    225   transition_animation_->SetTweenType(gfx::Tween::LINEAR);
    226   transition_animation_->Reset(transition_.progress);
    227 
    228   const int duration = is_valid_page(transition_.target_page) ?
    229       transition_duration_ms_ : overscroll_transition_duration_ms_;
    230   if (duration)
    231     transition_animation_->SetSlideDuration(duration);
    232 
    233   transition_animation_->Show();
    234 }
    235 
    236 void PaginationModel::ResetTransitionAnimation() {
    237   transition_animation_.reset();
    238   transition_.target_page = -1;
    239   transition_.progress = 0;
    240   pending_selected_page_ = -1;
    241 }
    242 
    243 void PaginationModel::AnimationProgressed(const gfx::Animation* animation) {
    244   transition_.progress = transition_animation_->GetCurrentValue();
    245   NotifyTransitionChanged();
    246 }
    247 
    248 void PaginationModel::AnimationEnded(const gfx::Animation* animation) {
    249   // Save |pending_selected_page_| because SelectPage resets it.
    250   int next_target = pending_selected_page_;
    251 
    252   if (transition_animation_->GetCurrentValue() == 1) {
    253     // Showing animation ends.
    254     if (!is_valid_page(transition_.target_page)) {
    255       // If target page is not in valid range, reverse the animation.
    256       transition_animation_->Hide();
    257       return;
    258     }
    259 
    260     // Otherwise, change page and finish the transition.
    261     DCHECK(selected_page_ != transition_.target_page);
    262     SelectPage(transition_.target_page, false /* animate */);
    263   } else if (transition_animation_->GetCurrentValue() == 0) {
    264     // Hiding animation ends. No page change should happen.
    265     ResetTransitionAnimation();
    266   }
    267 
    268   if (next_target >= 0)
    269     SelectPage(next_target, true);
    270 }
    271 
    272 }  // namespace app_list
    273