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 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 24 PaginationModel::~PaginationModel() { 25 } 26 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 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 98 void PaginationModel::SelectPageRelative(int delta, bool animate) { 99 SelectPage(CalculateTargetPage(delta), animate); 100 } 101 102 void PaginationModel::FinishAnimation() { 103 SelectPage(SelectedTargetPage(), false); 104 } 105 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 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 126 void PaginationModel::StartScroll() { 127 // Cancels current transition animation (if any). 128 transition_animation_.reset(); 129 } 130 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 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 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(); 174 } 175 176 void PaginationModel::AddObserver(PaginationModelObserver* observer) { 177 observers_.AddObserver(observer); 178 } 179 180 void PaginationModel::RemoveObserver(PaginationModelObserver* observer) { 181 observers_.RemoveObserver(observer); 182 } 183 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_; 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 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 205 void PaginationModel::NotifyTransitionStarted() { 206 FOR_EACH_OBSERVER(PaginationModelObserver, observers_, TransitionStarted()); 207 } 208 209 void PaginationModel::NotifyTransitionChanged() { 210 FOR_EACH_OBSERVER(PaginationModelObserver, observers_, TransitionChanged()); 211 } 212 213 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 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::LINEAR); 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 247 void PaginationModel::ResetTransitionAnimation() { 248 transition_animation_.reset(); 249 transition_.target_page = -1; 250 transition_.progress = 0; 251 pending_selected_page_ = -1; 252 } 253 254 void PaginationModel::AnimationProgressed(const gfx::Animation* animation) { 255 transition_.progress = transition_animation_->GetCurrentValue(); 256 NotifyTransitionChanged(); 257 } 258 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