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/views/contents_view.h" 6 7#include <algorithm> 8 9#include "base/logging.h" 10#include "grit/ui_resources.h" 11#include "ui/app_list/app_list_constants.h" 12#include "ui/app_list/app_list_switches.h" 13#include "ui/app_list/app_list_view_delegate.h" 14#include "ui/app_list/views/app_list_folder_view.h" 15#include "ui/app_list/views/app_list_main_view.h" 16#include "ui/app_list/views/apps_container_view.h" 17#include "ui/app_list/views/apps_grid_view.h" 18#include "ui/app_list/views/contents_switcher_view.h" 19#include "ui/app_list/views/search_result_list_view.h" 20#include "ui/app_list/views/start_page_view.h" 21#include "ui/base/resource/resource_bundle.h" 22#include "ui/events/event.h" 23#include "ui/views/view_model.h" 24#include "ui/views/view_model_utils.h" 25 26namespace app_list { 27 28namespace { 29 30const int kMinMouseWheelToSwitchPage = 20; 31const int kMinScrollToSwitchPage = 20; 32const int kMinHorizVelocityToSwitchPage = 800; 33 34const double kFinishTransitionThreshold = 0.33; 35 36} // namespace 37 38ContentsView::ContentsView(AppListMainView* app_list_main_view) 39 : search_results_view_(NULL), 40 start_page_view_(NULL), 41 app_list_main_view_(app_list_main_view), 42 contents_switcher_view_(NULL), 43 view_model_(new views::ViewModel) { 44 pagination_model_.AddObserver(this); 45} 46 47ContentsView::~ContentsView() { 48 pagination_model_.RemoveObserver(this); 49} 50 51void ContentsView::InitNamedPages(AppListModel* model, 52 AppListViewDelegate* view_delegate) { 53 DCHECK(model); 54 55 if (app_list::switches::IsExperimentalAppListEnabled()) { 56 start_page_view_ = new StartPageView(app_list_main_view_, view_delegate); 57 AddLauncherPage( 58 start_page_view_, IDR_APP_LIST_SEARCH_ICON, NAMED_PAGE_START); 59 } else { 60 search_results_view_ = 61 new SearchResultListView(app_list_main_view_, view_delegate); 62 AddLauncherPage(search_results_view_, 0, NAMED_PAGE_SEARCH_RESULTS); 63 search_results_view_->SetResults(model->results()); 64 } 65 66 apps_container_view_ = new AppsContainerView(app_list_main_view_, model); 67 int apps_page_index = AddLauncherPage( 68 apps_container_view_, IDR_APP_LIST_APPS_ICON, NAMED_PAGE_APPS); 69 70 pagination_model_.SelectPage(apps_page_index, false); 71} 72 73void ContentsView::CancelDrag() { 74 if (apps_container_view_->apps_grid_view()->has_dragged_view()) 75 apps_container_view_->apps_grid_view()->EndDrag(true); 76 if (apps_container_view_->app_list_folder_view() 77 ->items_grid_view() 78 ->has_dragged_view()) { 79 apps_container_view_->app_list_folder_view()->items_grid_view()->EndDrag( 80 true); 81 } 82} 83 84void ContentsView::SetDragAndDropHostOfCurrentAppList( 85 ApplicationDragAndDropHost* drag_and_drop_host) { 86 apps_container_view_->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host); 87} 88 89void ContentsView::SetActivePage(int page_index) { 90 if (GetActivePageIndex() == page_index) 91 return; 92 93 SetActivePageInternal(page_index, false); 94} 95 96int ContentsView::GetActivePageIndex() const { 97 // The active page is changed at the beginning of an animation, not the end. 98 return pagination_model_.SelectedTargetPage(); 99} 100 101bool ContentsView::IsNamedPageActive(NamedPage named_page) const { 102 std::map<NamedPage, int>::const_iterator it = 103 named_page_to_view_.find(named_page); 104 if (it == named_page_to_view_.end()) 105 return false; 106 return it->second == GetActivePageIndex(); 107} 108 109int ContentsView::GetPageIndexForNamedPage(NamedPage named_page) const { 110 // Find the index of the view corresponding to the given named_page. 111 std::map<NamedPage, int>::const_iterator it = 112 named_page_to_view_.find(named_page); 113 // GetPageIndexForNamedPage should never be called on a named_page that does 114 // not have a corresponding view. 115 DCHECK(it != named_page_to_view_.end()); 116 return it->second; 117} 118 119int ContentsView::NumLauncherPages() const { 120 return pagination_model_.total_pages(); 121} 122 123void ContentsView::SetActivePageInternal(int page_index, 124 bool show_search_results) { 125 // Start animating to the new page. 126 pagination_model_.SelectPage(page_index, true); 127 ActivePageChanged(show_search_results); 128} 129 130void ContentsView::ActivePageChanged(bool show_search_results) { 131 // TODO(xiyuan): Highlight default match instead of the first. 132 if (IsNamedPageActive(NAMED_PAGE_SEARCH_RESULTS) && 133 search_results_view_->visible()) { 134 search_results_view_->SetSelectedIndex(0); 135 } 136 if (search_results_view_) 137 search_results_view_->UpdateAutoLaunchState(); 138 139 if (IsNamedPageActive(NAMED_PAGE_START)) { 140 if (show_search_results) 141 start_page_view_->ShowSearchResults(); 142 else 143 start_page_view_->Reset(); 144 } 145 146 // Notify parent AppListMainView of the page change. 147 app_list_main_view_->UpdateSearchBoxVisibility(); 148} 149 150void ContentsView::ShowSearchResults(bool show) { 151 NamedPage new_named_page = show ? NAMED_PAGE_SEARCH_RESULTS : NAMED_PAGE_APPS; 152 if (app_list::switches::IsExperimentalAppListEnabled()) 153 new_named_page = NAMED_PAGE_START; 154 155 SetActivePageInternal(GetPageIndexForNamedPage(new_named_page), show); 156} 157 158bool ContentsView::IsShowingSearchResults() const { 159 return app_list::switches::IsExperimentalAppListEnabled() 160 ? IsNamedPageActive(NAMED_PAGE_START) && 161 start_page_view_->IsShowingSearchResults() 162 : IsNamedPageActive(NAMED_PAGE_SEARCH_RESULTS); 163} 164 165void ContentsView::UpdatePageBounds() { 166 gfx::Rect rect(GetContentsBounds()); 167 if (rect.IsEmpty()) 168 return; 169 170 // The bounds calculations will potentially be mid-transition (depending on 171 // the state of the PaginationModel). 172 int current_page = std::max(0, pagination_model_.selected_page()); 173 int target_page = current_page; 174 double progress = 1; 175 if (pagination_model_.has_transition()) { 176 const PaginationModel::Transition& transition = 177 pagination_model_.transition(); 178 if (pagination_model_.is_valid_page(transition.target_page)) { 179 target_page = transition.target_page; 180 progress = transition.progress; 181 } 182 } 183 184 gfx::Rect incoming_target(rect); 185 gfx::Rect outgoing_target(rect); 186 int dir = target_page > current_page ? -1 : 1; 187 188 if (app_list::switches::IsExperimentalAppListEnabled()) { 189 // The experimental app list transitions horizontally. 190 int page_width = rect.width(); 191 int transition_offset = progress * page_width * dir; 192 193 outgoing_target.set_x(transition_offset); 194 incoming_target.set_x(dir < 0 ? transition_offset + page_width 195 : transition_offset - page_width); 196 } else { 197 // The normal app list transitions vertically. 198 int page_height = rect.height(); 199 int transition_offset = progress * page_height * dir; 200 201 outgoing_target.set_y(transition_offset); 202 incoming_target.set_y(dir < 0 ? transition_offset + page_height 203 : transition_offset - page_height); 204 } 205 206 view_model_->view_at(current_page)->SetBoundsRect(outgoing_target); 207 view_model_->view_at(target_page)->SetBoundsRect(incoming_target); 208} 209 210PaginationModel* ContentsView::GetAppsPaginationModel() { 211 return apps_container_view_->apps_grid_view()->pagination_model(); 212} 213 214void ContentsView::ShowFolderContent(AppListFolderItem* item) { 215 apps_container_view_->ShowActiveFolder(item); 216} 217 218void ContentsView::Prerender() { 219 const int selected_page = 220 std::max(0, GetAppsPaginationModel()->selected_page()); 221 apps_container_view_->apps_grid_view()->Prerender(selected_page); 222} 223 224views::View* ContentsView::GetPageView(int index) { 225 return view_model_->view_at(index); 226} 227 228void ContentsView::AddBlankPageForTesting() { 229 AddLauncherPage(new views::View, 0); 230} 231 232int ContentsView::AddLauncherPage(views::View* view, int resource_id) { 233 int page_index = view_model_->view_size(); 234 AddChildView(view); 235 view_model_->Add(view, page_index); 236 pagination_model_.SetTotalPages(view_model_->view_size()); 237 if (contents_switcher_view_) 238 contents_switcher_view_->AddSwitcherButton(resource_id, page_index); 239 return page_index; 240} 241 242int ContentsView::AddLauncherPage(views::View* view, 243 int resource_id, 244 NamedPage named_page) { 245 int page_index = AddLauncherPage(view, resource_id); 246 named_page_to_view_.insert(std::pair<NamedPage, int>(named_page, page_index)); 247 return page_index; 248} 249 250gfx::Size ContentsView::GetPreferredSize() const { 251 const gfx::Size container_size = 252 apps_container_view_->apps_grid_view()->GetPreferredSize(); 253 const gfx::Size results_size = search_results_view_ 254 ? search_results_view_->GetPreferredSize() 255 : gfx::Size(); 256 257 int width = std::max(container_size.width(), results_size.width()); 258 int height = std::max(container_size.height(), results_size.height()); 259 return gfx::Size(width, height); 260} 261 262void ContentsView::Layout() { 263 // Immediately finish all current animations. 264 pagination_model_.FinishAnimation(); 265 266 // Move the current view onto the screen, and all other views off screen to 267 // the left. (Since we are not animating, we don't need to be careful about 268 // which side we place the off-screen views onto.) 269 gfx::Rect rect(GetContentsBounds()); 270 if (rect.IsEmpty()) 271 return; 272 273 gfx::Rect offscreen_target(rect); 274 offscreen_target.set_x(-rect.width()); 275 276 for (int i = 0; i < view_model_->view_size(); ++i) { 277 view_model_->view_at(i)->SetBoundsRect( 278 i == pagination_model_.SelectedTargetPage() ? rect : offscreen_target); 279 } 280} 281 282bool ContentsView::OnKeyPressed(const ui::KeyEvent& event) { 283 return view_model_->view_at(GetActivePageIndex())->OnKeyPressed(event); 284} 285 286bool ContentsView::OnMouseWheel(const ui::MouseWheelEvent& event) { 287 if (!IsNamedPageActive(NAMED_PAGE_APPS)) 288 return false; 289 290 int offset; 291 if (abs(event.x_offset()) > abs(event.y_offset())) 292 offset = event.x_offset(); 293 else 294 offset = event.y_offset(); 295 296 if (abs(offset) > kMinMouseWheelToSwitchPage) { 297 if (!GetAppsPaginationModel()->has_transition()) { 298 GetAppsPaginationModel()->SelectPageRelative(offset > 0 ? -1 : 1, true); 299 } 300 return true; 301 } 302 303 return false; 304} 305 306void ContentsView::TotalPagesChanged() { 307} 308 309void ContentsView::SelectedPageChanged(int old_selected, int new_selected) { 310} 311 312void ContentsView::TransitionStarted() { 313} 314 315void ContentsView::TransitionChanged() { 316 UpdatePageBounds(); 317} 318 319void ContentsView::OnGestureEvent(ui::GestureEvent* event) { 320 if (!IsNamedPageActive(NAMED_PAGE_APPS)) 321 return; 322 323 switch (event->type()) { 324 case ui::ET_GESTURE_SCROLL_BEGIN: 325 GetAppsPaginationModel()->StartScroll(); 326 event->SetHandled(); 327 return; 328 case ui::ET_GESTURE_SCROLL_UPDATE: 329 // event->details.scroll_x() > 0 means moving contents to right. That is, 330 // transitioning to previous page. 331 GetAppsPaginationModel()->UpdateScroll(event->details().scroll_x() / 332 GetContentsBounds().width()); 333 event->SetHandled(); 334 return; 335 case ui::ET_GESTURE_SCROLL_END: 336 GetAppsPaginationModel()->EndScroll( 337 GetAppsPaginationModel()->transition().progress < 338 kFinishTransitionThreshold); 339 event->SetHandled(); 340 return; 341 case ui::ET_SCROLL_FLING_START: { 342 GetAppsPaginationModel()->EndScroll(true); 343 if (fabs(event->details().velocity_x()) > kMinHorizVelocityToSwitchPage) { 344 GetAppsPaginationModel()->SelectPageRelative( 345 event->details().velocity_x() < 0 ? 1 : -1, true); 346 } 347 event->SetHandled(); 348 return; 349 } 350 default: 351 break; 352 } 353} 354 355void ContentsView::OnScrollEvent(ui::ScrollEvent* event) { 356 if (!IsNamedPageActive(NAMED_PAGE_APPS) || 357 event->type() == ui::ET_SCROLL_FLING_CANCEL) { 358 return; 359 } 360 361 float offset; 362 if (std::abs(event->x_offset()) > std::abs(event->y_offset())) 363 offset = event->x_offset(); 364 else 365 offset = event->y_offset(); 366 367 if (std::abs(offset) > kMinScrollToSwitchPage) { 368 if (!GetAppsPaginationModel()->has_transition()) { 369 GetAppsPaginationModel()->SelectPageRelative(offset > 0 ? -1 : 1, true); 370 } 371 event->SetHandled(); 372 event->StopPropagation(); 373 } 374} 375 376} // namespace app_list 377