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/base/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(total_pages_
- 1, 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(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::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);
109 if (transition_
.Equals(transition
))
112 transition_
= transition
;
113 NotifyTransitionChanged();
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
;
122 void PaginationModel::StartScroll() {
123 // Cancels current transition animation (if any).
124 transition_animation_
.reset();
127 void PaginationModel::UpdateScroll(double delta
) {
128 // Translates scroll delta to desired page change direction.
129 int page_change_dir
= delta
> 0 ? -1 : 1;
131 // Initializes a transition if there is none.
132 if (!has_transition())
133 transition_
.target_page
= CalculateTargetPage(page_change_dir
);
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
;
141 if (transition_
.progress
) {
142 transition_
.progress
= 0;
143 NotifyTransitionChanged();
146 } else if (progress
> 1) {
147 if (is_valid_page(transition_
.target_page
)) {
148 SelectPage(transition_
.target_page
, false);
152 transition_
.progress
= progress
;
153 NotifyTransitionChanged();
157 void PaginationModel::EndScroll(bool cancel
) {
158 if (!has_transition())
161 StartTransitionAnimation(transition_
);
164 transition_animation_
->Hide();
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();
172 void PaginationModel::AddObserver(PaginationModelObserver
* observer
) {
173 observers_
.AddObserver(observer
);
176 void PaginationModel::RemoveObserver(PaginationModelObserver
* observer
) {
177 observers_
.RemoveObserver(observer
);
180 void PaginationModel::NotifySelectedPageChanged(int old_selected
,
182 FOR_EACH_OBSERVER(PaginationModelObserver
,
184 SelectedPageChanged(old_selected
, new_selected
));
187 void PaginationModel::NotifyTransitionChanged() {
188 FOR_EACH_OBSERVER(PaginationModelObserver
, observers_
, TransitionChanged());
191 int PaginationModel::CalculateTargetPage(int delta
) const {
192 DCHECK_GT(total_pages_
, 0);
194 int current_page
= selected_page_
;
195 if (transition_animation_
&& transition_animation_
->IsShowing()) {
196 current_page
= pending_selected_page_
>= 0 ?
197 pending_selected_page_
: transition_
.target_page
;
200 const int target_page
= current_page
+ delta
;
203 int end_page
= total_pages_
- 1;
205 // Use invalid page when |selected_page_| is at ends.
206 if (target_page
< start_page
&& selected_page_
== start_page
)
208 else if (target_page
> end_page
&& selected_page_
== end_page
)
209 end_page
= total_pages_
;
211 return std::max(start_page
, std::min(end_page
, target_page
));
214 void PaginationModel::StartTransitionAnimation(const Transition
& transition
) {
215 DCHECK(selected_page_
!= transition
.target_page
);
217 SetTransition(transition
);
219 transition_animation_
.reset(new ui::SlideAnimation(this));
220 transition_animation_
->SetTweenType(ui::Tween::LINEAR
);
221 transition_animation_
->Reset(transition_
.progress
);
223 const int duration
= is_valid_page(transition_
.target_page
) ?
224 transition_duration_ms_
: overscroll_transition_duration_ms_
;
226 transition_animation_
->SetSlideDuration(duration
);
228 transition_animation_
->Show();
231 void PaginationModel::ResetTransitionAnimation() {
232 transition_animation_
.reset();
233 transition_
.target_page
= -1;
234 transition_
.progress
= 0;
235 pending_selected_page_
= -1;
238 void PaginationModel::AnimationProgressed(const ui::Animation
* animation
) {
239 transition_
.progress
= transition_animation_
->GetCurrentValue();
240 NotifyTransitionChanged();
243 void PaginationModel::AnimationEnded(const ui::Animation
* animation
) {
244 // Save |pending_selected_page_| because SelectPage resets it.
245 int next_target
= pending_selected_page_
;
247 if (transition_animation_
->GetCurrentValue() == 1) {
248 // Showing animation ends.
249 if (!is_valid_page(transition_
.target_page
)) {
250 // If target page is not in valid range, reverse the animation.
251 transition_animation_
->Hide();
255 // Otherwise, change page and finish the transition.
256 DCHECK(selected_page_
!= transition_
.target_page
);
257 SelectPage(transition_
.target_page
, false /* animate */);
258 } else if (transition_animation_
->GetCurrentValue() == 0) {
259 // Hiding animation ends. No page change should happen.
260 ResetTransitionAnimation();
263 if (next_target
>= 0)
264 SelectPage(next_target
, true);
267 } // namespace app_list