Mac: Fix performance issues with remote CoreAnimation
[chromium-blink-merge.git] / ui / app_list / pagination_model.cc
blob0e568d81535021426122c7f075359c8f6848b6eb
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"
7 #include <algorithm>
9 #include "ui/app_list/pagination_model_observer.h"
10 #include "ui/gfx/animation/slide_animation.h"
12 namespace app_list {
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) {
24 PaginationModel::~PaginationModel() {
27 void PaginationModel::SetTotalPages(int total_pages) {
28 if (total_pages == total_pages_)
29 return;
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) {
40 if (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_)
46 return;
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)
57 return;
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));
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_;
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;
84 } else {
85 DCHECK(total_pages_ == 0 || (page >= 0 && page < total_pages_));
87 if (page == selected_page_)
88 return;
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))
114 return;
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;
144 if (progress < 0) {
145 if (transition_.progress) {
146 transition_.progress = 0;
147 NotifyTransitionChanged();
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();
155 } else {
156 transition_.progress = progress;
157 NotifyTransitionChanged();
161 void PaginationModel::EndScroll(bool cancel) {
162 if (!has_transition())
163 return;
165 StartTransitionAnimation(transition_);
167 if (cancel)
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,
199 int new_selected) {
200 FOR_EACH_OBSERVER(PaginationModelObserver,
201 observers_,
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;
217 int start_page = 0;
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)
222 start_page = -1;
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_;
241 if (duration)
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();
268 return;
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