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 "ui/app_list/app_list_constants.h" 11 #include "ui/app_list/pagination_model.h" 12 #include "ui/app_list/views/app_list_main_view.h" 13 #include "ui/app_list/views/apps_grid_view.h" 14 #include "ui/app_list/views/search_result_list_view.h" 15 #include "ui/base/events/event.h" 16 #include "ui/views/animation/bounds_animator.h" 17 #include "ui/views/view_model.h" 18 #include "ui/views/view_model_utils.h" 19 20 namespace app_list { 21 22 namespace { 23 24 const int kPreferredIconDimension = 48; 25 26 // Indexes of interesting views in ViewModel of ContentsView. 27 const int kIndexAppsGrid = 0; 28 const int kIndexSearchResults = 1; 29 30 const int kMinMouseWheelToSwitchPage = 20; 31 const int kMinScrollToSwitchPage = 20; 32 const int kMinHorizVelocityToSwitchPage = 800; 33 34 const double kFinishTransitionThreshold = 0.33; 35 36 // Helpers to get certain child view from |model|. 37 AppsGridView* GetAppsGridView(views::ViewModel* model) { 38 return static_cast<AppsGridView*>(model->view_at(kIndexAppsGrid)); 39 } 40 41 SearchResultListView* GetSearchResultListView(views::ViewModel* model) { 42 return static_cast<SearchResultListView*>( 43 model->view_at(kIndexSearchResults)); 44 } 45 46 } // namespace 47 48 ContentsView::ContentsView(AppListMainView* app_list_main_view, 49 PaginationModel* pagination_model, 50 AppListModel* model) 51 : show_state_(SHOW_APPS), 52 pagination_model_(pagination_model), 53 view_model_(new views::ViewModel), 54 bounds_animator_(new views::BoundsAnimator(this)) { 55 DCHECK(model); 56 pagination_model_->SetTransitionDurations( 57 kPageTransitionDurationInMs, 58 kOverscrollPageTransitionDurationMs); 59 60 apps_grid_view_ = new AppsGridView(app_list_main_view, pagination_model); 61 apps_grid_view_->SetLayout(kPreferredIconDimension, 62 kPreferredCols, 63 kPreferredRows); 64 AddChildView(apps_grid_view_); 65 view_model_->Add(apps_grid_view_, kIndexAppsGrid); 66 67 SearchResultListView* search_results_view = new SearchResultListView( 68 app_list_main_view); 69 AddChildView(search_results_view); 70 view_model_->Add(search_results_view, kIndexSearchResults); 71 72 GetAppsGridView(view_model_.get())->SetModel(model); 73 GetSearchResultListView(view_model_.get())->SetResults(model->results()); 74 } 75 76 ContentsView::~ContentsView() { 77 } 78 79 void ContentsView::SetDragAndDropHostOfCurrentAppList( 80 app_list::ApplicationDragAndDropHost* drag_and_drop_host) { 81 apps_grid_view_->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host); 82 } 83 84 void ContentsView::SetShowState(ShowState show_state) { 85 if (show_state_ == show_state) 86 return; 87 88 show_state_ = show_state; 89 ShowStateChanged(); 90 } 91 92 void ContentsView::ShowStateChanged() { 93 if (show_state_ == SHOW_SEARCH_RESULTS) { 94 // TODO(xiyuan): Highlight default match instead of the first. 95 SearchResultListView* results_view = 96 GetSearchResultListView(view_model_.get()); 97 if (results_view->visible()) 98 results_view->SetSelectedIndex(0); 99 } 100 101 AnimateToIdealBounds(); 102 } 103 104 void ContentsView::CalculateIdealBounds() { 105 gfx::Rect rect(GetContentsBounds()); 106 if (rect.IsEmpty()) 107 return; 108 109 gfx::Rect grid_frame(rect); 110 gfx::Rect results_frame(rect); 111 112 // Offsets apps grid and result list based on |show_state_|. 113 // SearchResultListView is on top of apps grid. Visible view is left in 114 // visible area and invisible ones is put out of the visible area. 115 int contents_area_height = rect.height(); 116 switch (show_state_) { 117 case SHOW_APPS: 118 results_frame.Offset(0, -contents_area_height); 119 break; 120 case SHOW_SEARCH_RESULTS: 121 grid_frame.Offset(0, contents_area_height); 122 break; 123 default: 124 NOTREACHED() << "Unknown show_state_ " << show_state_; 125 break; 126 } 127 128 view_model_->set_ideal_bounds(kIndexAppsGrid, grid_frame); 129 view_model_->set_ideal_bounds(kIndexSearchResults, results_frame); 130 } 131 132 void ContentsView::AnimateToIdealBounds() { 133 CalculateIdealBounds(); 134 for (int i = 0; i < view_model_->view_size(); ++i) { 135 bounds_animator_->AnimateViewTo(view_model_->view_at(i), 136 view_model_->ideal_bounds(i)); 137 } 138 } 139 140 void ContentsView::ShowSearchResults(bool show) { 141 SetShowState(show ? SHOW_SEARCH_RESULTS : SHOW_APPS); 142 } 143 144 void ContentsView::Prerender() { 145 const int selected_page = std::max(0, pagination_model_->selected_page()); 146 GetAppsGridView(view_model_.get())->Prerender(selected_page); 147 } 148 149 gfx::Size ContentsView::GetPreferredSize() { 150 const gfx::Size grid_size = 151 GetAppsGridView(view_model_.get())->GetPreferredSize(); 152 const gfx::Size results_size = 153 GetSearchResultListView(view_model_.get())->GetPreferredSize(); 154 155 int width = std::max(grid_size.width(), results_size.width()); 156 int height = std::max(grid_size.height(), results_size.height()); 157 return gfx::Size(width, height); 158 } 159 160 void ContentsView::Layout() { 161 CalculateIdealBounds(); 162 views::ViewModelUtils::SetViewBoundsToIdealBounds(*view_model_); 163 } 164 165 bool ContentsView::OnKeyPressed(const ui::KeyEvent& event) { 166 switch (show_state_) { 167 case SHOW_APPS: 168 return GetAppsGridView(view_model_.get())->OnKeyPressed(event); 169 case SHOW_SEARCH_RESULTS: 170 return GetSearchResultListView(view_model_.get())->OnKeyPressed(event); 171 default: 172 NOTREACHED() << "Unknown show state " << show_state_; 173 } 174 return false; 175 } 176 177 bool ContentsView::OnMouseWheel(const ui::MouseWheelEvent& event) { 178 if (show_state_ != SHOW_APPS) 179 return false; 180 181 int offset; 182 if (abs(event.x_offset()) > abs(event.y_offset())) 183 offset = event.x_offset(); 184 else 185 offset = event.y_offset(); 186 187 if (abs(offset) > kMinMouseWheelToSwitchPage) { 188 if (!pagination_model_->has_transition()) { 189 pagination_model_->SelectPageRelative( 190 offset > 0 ? -1 : 1, true); 191 } 192 return true; 193 } 194 195 return false; 196 } 197 198 void ContentsView::OnGestureEvent(ui::GestureEvent* event) { 199 if (show_state_ != SHOW_APPS) 200 return; 201 202 switch (event->type()) { 203 case ui::ET_GESTURE_SCROLL_BEGIN: 204 pagination_model_->StartScroll(); 205 event->SetHandled(); 206 return; 207 case ui::ET_GESTURE_SCROLL_UPDATE: 208 // event->details.scroll_x() > 0 means moving contents to right. That is, 209 // transitioning to previous page. 210 pagination_model_->UpdateScroll( 211 event->details().scroll_x() / GetContentsBounds().width()); 212 event->SetHandled(); 213 return; 214 case ui::ET_GESTURE_SCROLL_END: 215 pagination_model_->EndScroll(pagination_model_-> 216 transition().progress < kFinishTransitionThreshold); 217 event->SetHandled(); 218 return; 219 case ui::ET_SCROLL_FLING_START: { 220 pagination_model_->EndScroll(true); 221 if (fabs(event->details().velocity_x()) > kMinHorizVelocityToSwitchPage) { 222 pagination_model_->SelectPageRelative( 223 event->details().velocity_x() < 0 ? 1 : -1, 224 true); 225 } 226 event->SetHandled(); 227 return; 228 } 229 default: 230 break; 231 } 232 } 233 234 void ContentsView::OnScrollEvent(ui::ScrollEvent* event) { 235 if (show_state_ != SHOW_APPS || 236 event->type() == ui::ET_SCROLL_FLING_CANCEL) { 237 return; 238 } 239 240 float offset; 241 if (abs(event->x_offset()) > abs(event->y_offset())) 242 offset = event->x_offset(); 243 else 244 offset = event->y_offset(); 245 246 if (abs(offset) > kMinScrollToSwitchPage) { 247 if (!pagination_model_->has_transition()) { 248 pagination_model_->SelectPageRelative(offset > 0 ? -1 : 1, 249 true); 250 } 251 event->SetHandled(); 252 event->StopPropagation(); 253 } 254 } 255 256 } // namespace app_list 257