• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
PaginationModel()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 
~PaginationModel()24 PaginationModel::~PaginationModel() {
25 }
26 
SetTotalPages(int total_pages)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 
SelectPage(int page,bool animate)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 
SelectPageRelative(int delta,bool animate)98 void PaginationModel::SelectPageRelative(int delta, bool animate) {
99   SelectPage(CalculateTargetPage(delta), animate);
100 }
101 
FinishAnimation()102 void PaginationModel::FinishAnimation() {
103   SelectPage(SelectedTargetPage(), false);
104 }
105 
SetTransition(const Transition & transition)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 
SetTransitionDurations(int duration_ms,int overscroll_duration_ms)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 
StartScroll()126 void PaginationModel::StartScroll() {
127   // Cancels current transition animation (if any).
128   transition_animation_.reset();
129 }
130 
UpdateScroll(double delta)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 
EndScroll(bool cancel)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 
IsRevertingCurrentTransition() const171 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 
AddObserver(PaginationModelObserver * observer)176 void PaginationModel::AddObserver(PaginationModelObserver* observer) {
177   observers_.AddObserver(observer);
178 }
179 
RemoveObserver(PaginationModelObserver * observer)180 void PaginationModel::RemoveObserver(PaginationModelObserver* observer) {
181   observers_.RemoveObserver(observer);
182 }
183 
SelectedTargetPage() const184 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 
NotifySelectedPageChanged(int old_selected,int new_selected)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 
NotifyTransitionStarted()205 void PaginationModel::NotifyTransitionStarted() {
206   FOR_EACH_OBSERVER(PaginationModelObserver, observers_, TransitionStarted());
207 }
208 
NotifyTransitionChanged()209 void PaginationModel::NotifyTransitionChanged() {
210   FOR_EACH_OBSERVER(PaginationModelObserver, observers_, TransitionChanged());
211 }
212 
CalculateTargetPage(int delta) const213 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 
StartTransitionAnimation(const Transition & transition)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 
ResetTransitionAnimation()247 void PaginationModel::ResetTransitionAnimation() {
248   transition_animation_.reset();
249   transition_.target_page = -1;
250   transition_.progress = 0;
251   pending_selected_page_ = -1;
252 }
253 
AnimationProgressed(const gfx::Animation * animation)254 void PaginationModel::AnimationProgressed(const gfx::Animation* animation) {
255   transition_.progress = transition_animation_->GetCurrentValue();
256   NotifyTransitionChanged();
257 }
258 
AnimationEnded(const gfx::Animation * animation)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