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