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