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.
5 #include "ui/app_list/pagination_model.h"
9 #include "ui/app_list/pagination_model_observer.h"
10 #include "ui/gfx/animation/slide_animation.h"
14 PaginationModel::PaginationModel()
18 pending_selected_page_(-1),
19 transition_duration_ms_(0),
20 overscroll_transition_duration_ms_(0),
21 last_overscroll_target_page_(0) {
24 PaginationModel::~PaginationModel() {
27 void PaginationModel::SetTotalPages(int total_pages
) {
28 if (total_pages
== total_pages_
)
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());
39 void PaginationModel::SelectPage(int page
, bool animate
) {
41 // -1 and |total_pages_| are valid target page for animation.
42 DCHECK(page
>= -1 && page
<= total_pages_
);
44 if (!transition_animation_
) {
45 if (page
== selected_page_
)
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();
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
)
60 last_overscroll_target_page_
= page
;
61 last_overscroll_animation_start_time_
= now
;
64 // Creates an animation if there is not one.
65 StartTransitionAnimation(Transition(page
, 0));
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_
;
72 if (from_page
== page
) {
74 transition_animation_
->Hide();
76 transition_animation_
->Show();
77 pending_selected_page_
= -1;
78 } else if (to_page
!= page
) {
79 pending_selected_page_
= page
;
81 pending_selected_page_
= -1;
85 DCHECK(total_pages_
== 0 || (page
>= 0 && page
< total_pages_
));
87 if (page
== selected_page_
)
90 ResetTransitionAnimation();
92 int old_selected
= selected_page_
;
93 selected_page_
= page
;
94 NotifySelectedPageChanged(old_selected
, selected_page_
);
98 void PaginationModel::SelectPageRelative(int delta
, bool animate
) {
99 SelectPage(CalculateTargetPage(delta
), animate
);
102 void PaginationModel::FinishAnimation() {
103 SelectPage(SelectedTargetPage(), false);
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);
113 if (transition_
.Equals(transition
))
116 transition_
= transition
;
117 NotifyTransitionChanged();
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
;
126 void PaginationModel::StartScroll() {
127 // Cancels current transition animation (if any).
128 transition_animation_
.reset();
131 void PaginationModel::UpdateScroll(double delta
) {
132 // Translates scroll delta to desired page change direction.
133 int page_change_dir
= delta
> 0 ? -1 : 1;
135 // Initializes a transition if there is none.
136 if (!has_transition())
137 transition_
.target_page
= CalculateTargetPage(page_change_dir
);
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
;
145 if (transition_
.progress
) {
146 transition_
.progress
= 0;
147 NotifyTransitionChanged();
150 } else if (progress
> 1) {
151 if (is_valid_page(transition_
.target_page
)) {
152 SelectPage(transition_
.target_page
, false);
156 transition_
.progress
= progress
;
157 NotifyTransitionChanged();
161 void PaginationModel::EndScroll(bool cancel
) {
162 if (!has_transition())
165 StartTransitionAnimation(transition_
);
168 transition_animation_
->Hide();
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();
176 void PaginationModel::AddObserver(PaginationModelObserver
* observer
) {
177 observers_
.AddObserver(observer
);
180 void PaginationModel::RemoveObserver(PaginationModelObserver
* observer
) {
181 observers_
.RemoveObserver(observer
);
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_
;
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_
;
194 // Just the target of the current animation.
195 return transition_
.target_page
;
198 void PaginationModel::NotifySelectedPageChanged(int old_selected
,
200 FOR_EACH_OBSERVER(PaginationModelObserver
,
202 SelectedPageChanged(old_selected
, new_selected
));
205 void PaginationModel::NotifyTransitionStarted() {
206 FOR_EACH_OBSERVER(PaginationModelObserver
, observers_
, TransitionStarted());
209 void PaginationModel::NotifyTransitionChanged() {
210 FOR_EACH_OBSERVER(PaginationModelObserver
, observers_
, TransitionChanged());
213 int PaginationModel::CalculateTargetPage(int delta
) const {
214 DCHECK_GT(total_pages_
, 0);
215 const int target_page
= SelectedTargetPage() + delta
;
218 int end_page
= total_pages_
- 1;
220 // Use invalid page when |selected_page_| is at ends.
221 if (target_page
< start_page
&& selected_page_
== start_page
)
223 else if (target_page
> end_page
&& selected_page_
== end_page
)
224 end_page
= total_pages_
;
226 return std::max(start_page
, std::min(end_page
, target_page
));
229 void PaginationModel::StartTransitionAnimation(const Transition
& transition
) {
230 DCHECK(selected_page_
!= transition
.target_page
);
232 NotifyTransitionStarted();
233 SetTransition(transition
);
235 transition_animation_
.reset(new gfx::SlideAnimation(this));
236 transition_animation_
->SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN
);
237 transition_animation_
->Reset(transition_
.progress
);
239 const int duration
= is_valid_page(transition_
.target_page
) ?
240 transition_duration_ms_
: overscroll_transition_duration_ms_
;
242 transition_animation_
->SetSlideDuration(duration
);
244 transition_animation_
->Show();
247 void PaginationModel::ResetTransitionAnimation() {
248 transition_animation_
.reset();
249 transition_
.target_page
= -1;
250 transition_
.progress
= 0;
251 pending_selected_page_
= -1;
254 void PaginationModel::AnimationProgressed(const gfx::Animation
* animation
) {
255 transition_
.progress
= transition_animation_
->GetCurrentValue();
256 NotifyTransitionChanged();
259 void PaginationModel::AnimationEnded(const gfx::Animation
* animation
) {
260 // Save |pending_selected_page_| because SelectPage resets it.
261 int next_target
= pending_selected_page_
;
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();
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();
279 if (next_target
>= 0)
280 SelectPage(next_target
, true);
283 } // namespace app_list