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::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::NotifyTransitionStarted() {
188 FOR_EACH_OBSERVER(PaginationModelObserver
, observers_
, TransitionStarted());
191 void PaginationModel::NotifyTransitionChanged() {
192 FOR_EACH_OBSERVER(PaginationModelObserver
, observers_
, TransitionChanged());
195 int PaginationModel::CalculateTargetPage(int delta
) const {
196 DCHECK_GT(total_pages_
, 0);
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
;
204 const int target_page
= current_page
+ delta
;
207 int end_page
= total_pages_
- 1;
209 // Use invalid page when |selected_page_| is at ends.
210 if (target_page
< start_page
&& selected_page_
== start_page
)
212 else if (target_page
> end_page
&& selected_page_
== end_page
)
213 end_page
= total_pages_
;
215 return std::max(start_page
, std::min(end_page
, target_page
));
218 void PaginationModel::StartTransitionAnimation(const Transition
& transition
) {
219 DCHECK(selected_page_
!= transition
.target_page
);
221 NotifyTransitionStarted();
222 SetTransition(transition
);
224 transition_animation_
.reset(new gfx::SlideAnimation(this));
225 transition_animation_
->SetTweenType(gfx::Tween::LINEAR
);
226 transition_animation_
->Reset(transition_
.progress
);
228 const int duration
= is_valid_page(transition_
.target_page
) ?
229 transition_duration_ms_
: overscroll_transition_duration_ms_
;
231 transition_animation_
->SetSlideDuration(duration
);
233 transition_animation_
->Show();
236 void PaginationModel::ResetTransitionAnimation() {
237 transition_animation_
.reset();
238 transition_
.target_page
= -1;
239 transition_
.progress
= 0;
240 pending_selected_page_
= -1;
243 void PaginationModel::AnimationProgressed(const gfx::Animation
* animation
) {
244 transition_
.progress
= transition_animation_
->GetCurrentValue();
245 NotifyTransitionChanged();
248 void PaginationModel::AnimationEnded(const gfx::Animation
* animation
) {
249 // Save |pending_selected_page_| because SelectPage resets it.
250 int next_target
= pending_selected_page_
;
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();
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();
268 if (next_target
>= 0)
269 SelectPage(next_target
, true);
272 } // namespace app_list