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::FinishAnimation() {
    103   SelectPage(SelectedTargetPage(), false);
    104 }
    105 
    106 void PaginationModel::SetTransition(const Transition& transition) {
    107   // -1 and |total_pages_| is a valid target page, which means user is at
    108   // the end and there is no target page for this scroll.
    109   DCHECK(transition.target_page >= -1 &&
    110          transition.target_page <= total_pages_);
    111   DCHECK(transition.progress >= 0 && transition.progress <= 1);
    112 
    113   if (transition_.Equals(transition))
    114     return;
    115 
    116   transition_ = transition;
    117   NotifyTransitionChanged();
    118 }
    119 
    120 void PaginationModel::SetTransitionDurations(int duration_ms,
    121                                              int overscroll_duration_ms) {
    122   transition_duration_ms_ = duration_ms;
    123   overscroll_transition_duration_ms_ = overscroll_duration_ms;
    124 }
    125 
    126 void PaginationModel::StartScroll() {
    127   // Cancels current transition animation (if any).
    128   transition_animation_.reset();
    129 }
    130 
    131 void PaginationModel::UpdateScroll(double delta) {
    132   // Translates scroll delta to desired page change direction.
    133   int page_change_dir = delta > 0 ? -1 : 1;
    134 
    135   // Initializes a transition if there is none.
    136   if (!has_transition())
    137     transition_.target_page = CalculateTargetPage(page_change_dir);
    138 
    139   // Updates transition progress.
    140   int transition_dir = transition_.target_page > selected_page_ ? 1 : -1;
    141   double progress = transition_.progress +
    142       fabs(delta) * page_change_dir * transition_dir;
    143 
    144   if (progress < 0) {
    145     if (transition_.progress) {
    146       transition_.progress = 0;
    147       NotifyTransitionChanged();
    148     }
    149     clear_transition();
    150   } else if (progress > 1) {
    151     if (is_valid_page(transition_.target_page)) {
    152       SelectPage(transition_.target_page, false);
    153       clear_transition();
    154     }
    155   } else {
    156     transition_.progress = progress;
    157     NotifyTransitionChanged();
    158   }
    159 }
    160 
    161 void PaginationModel::EndScroll(bool cancel) {
    162   if (!has_transition())
    163     return;
    164 
    165   StartTransitionAnimation(transition_);
    166 
    167   if (cancel)
    168     transition_animation_->Hide();
    169 }
    170 
    171 bool PaginationModel::IsRevertingCurrentTransition() const {
    172   // Use !IsShowing() so that we return true at the end of hide animation.
    173   return transition_animation_ && !transition_animation_->IsShowing();
    174 }
    175 
    176 void PaginationModel::AddObserver(PaginationModelObserver* observer) {
    177   observers_.AddObserver(observer);
    178 }
    179 
    180 void PaginationModel::RemoveObserver(PaginationModelObserver* observer) {
    181   observers_.RemoveObserver(observer);
    182 }
    183 
    184 int PaginationModel::SelectedTargetPage() const {
    185   // If no animation, or animation is in reverse, just the selected page.
    186   if (!transition_animation_ || !transition_animation_->IsShowing())
    187     return selected_page_;
    188 
    189   // If, at the end of the current animation, we will animate to another page,
    190   // return that eventual page.
    191   if (pending_selected_page_ >= 0)
    192     return pending_selected_page_;
    193 
    194   // Just the target of the current animation.
    195   return transition_.target_page;
    196 }
    197 
    198 void PaginationModel::NotifySelectedPageChanged(int old_selected,
    199                                                 int new_selected) {
    200   FOR_EACH_OBSERVER(PaginationModelObserver,
    201                     observers_,
    202                     SelectedPageChanged(old_selected, new_selected));
    203 }
    204 
    205 void PaginationModel::NotifyTransitionStarted() {
    206   FOR_EACH_OBSERVER(PaginationModelObserver, observers_, TransitionStarted());
    207 }
    208 
    209 void PaginationModel::NotifyTransitionChanged() {
    210   FOR_EACH_OBSERVER(PaginationModelObserver, observers_, TransitionChanged());
    211 }
    212 
    213 int PaginationModel::CalculateTargetPage(int delta) const {
    214   DCHECK_GT(total_pages_, 0);
    215   const int target_page = SelectedTargetPage() + delta;
    216 
    217   int start_page = 0;
    218   int end_page = total_pages_ - 1;
    219 
    220   // Use invalid page when |selected_page_| is at ends.
    221   if (target_page < start_page && selected_page_ == start_page)
    222     start_page = -1;
    223   else if (target_page > end_page && selected_page_ == end_page)
    224     end_page = total_pages_;
    225 
    226   return std::max(start_page, std::min(end_page, target_page));
    227 }
    228 
    229 void PaginationModel::StartTransitionAnimation(const Transition& transition) {
    230   DCHECK(selected_page_ != transition.target_page);
    231 
    232   NotifyTransitionStarted();
    233   SetTransition(transition);
    234 
    235   transition_animation_.reset(new gfx::SlideAnimation(this));
    236   transition_animation_->SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
    237   transition_animation_->Reset(transition_.progress);
    238 
    239   const int duration = is_valid_page(transition_.target_page) ?
    240       transition_duration_ms_ : overscroll_transition_duration_ms_;
    241   if (duration)
    242     transition_animation_->SetSlideDuration(duration);
    243 
    244   transition_animation_->Show();
    245 }
    246 
    247 void PaginationModel::ResetTransitionAnimation() {
    248   transition_animation_.reset();
    249   transition_.target_page = -1;
    250   transition_.progress = 0;
    251   pending_selected_page_ = -1;
    252 }
    253 
    254 void PaginationModel::AnimationProgressed(const gfx::Animation* animation) {
    255   transition_.progress = transition_animation_->GetCurrentValue();
    256   NotifyTransitionChanged();
    257 }
    258 
    259 void PaginationModel::AnimationEnded(const gfx::Animation* animation) {
    260   // Save |pending_selected_page_| because SelectPage resets it.
    261   int next_target = pending_selected_page_;
    262 
    263   if (transition_animation_->GetCurrentValue() == 1) {
    264     // Showing animation ends.
    265     if (!is_valid_page(transition_.target_page)) {
    266       // If target page is not in valid range, reverse the animation.
    267       transition_animation_->Hide();
    268       return;
    269     }
    270 
    271     // Otherwise, change page and finish the transition.
    272     DCHECK(selected_page_ != transition_.target_page);
    273     SelectPage(transition_.target_page, false /* animate */);
    274   } else if (transition_animation_->GetCurrentValue() == 0) {
    275     // Hiding animation ends. No page change should happen.
    276     ResetTransitionAnimation();
    277   }
    278 
    279   if (next_target >= 0)
    280     SelectPage(next_target, true);
    281 }
    282 
    283 }  // namespace app_list
    284