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 "ash/wm/app_list_controller.h"
6
7#include "ash/ash_switches.h"
8#include "ash/launcher/launcher.h"
9#include "ash/root_window_controller.h"
10#include "ash/shelf/shelf_layout_manager.h"
11#include "ash/shell.h"
12#include "ash/shell_delegate.h"
13#include "ash/shell_window_ids.h"
14#include "ash/wm/property_util.h"
15#include "base/command_line.h"
16#include "ui/app_list/app_list_constants.h"
17#include "ui/app_list/pagination_model.h"
18#include "ui/app_list/views/app_list_view.h"
19#include "ui/aura/client/focus_client.h"
20#include "ui/aura/root_window.h"
21#include "ui/aura/window.h"
22#include "ui/base/events/event.h"
23#include "ui/compositor/layer.h"
24#include "ui/compositor/scoped_layer_animation_settings.h"
25#include "ui/gfx/transform_util.h"
26#include "ui/views/widget/widget.h"
27
28namespace ash {
29namespace internal {
30
31namespace {
32
33// Duration for show/hide animation in milliseconds.
34const int kAnimationDurationMs = 200;
35
36// Offset in pixels to animation away/towards the launcher.
37const int kAnimationOffset = 8;
38
39// The maximum shift in pixels when over-scroll happens.
40const int kMaxOverScrollShift = 48;
41
42// The alternate shelf style adjusts the bubble to be flush with the shelf
43// when there is no bubble-tip. This is the tip height which needs to be
44// offsetted.
45const int kArrowTipHeight = 10;
46
47// The minimal anchor position offset to make sure that the bubble is still on
48// the screen with 8 pixels spacing on the left / right. This constant is a
49// result of minimal bubble arrow sizes and offsets.
50const int kMinimalAnchorPositionOffset = 57;
51
52ui::Layer* GetLayer(views::Widget* widget) {
53  return widget->GetNativeView()->layer();
54}
55
56// Gets arrow location based on shelf alignment.
57views::BubbleBorder::Arrow GetBubbleArrow(aura::Window* window) {
58  DCHECK(Shell::HasInstance());
59  return ShelfLayoutManager::ForLauncher(window)->
60      SelectValueForShelfAlignment(
61          views::BubbleBorder::BOTTOM_CENTER,
62          views::BubbleBorder::LEFT_CENTER,
63          views::BubbleBorder::RIGHT_CENTER,
64          views::BubbleBorder::TOP_CENTER);
65}
66
67// Offset given |rect| towards shelf.
68gfx::Rect OffsetTowardsShelf(const gfx::Rect& rect, views::Widget* widget) {
69  DCHECK(Shell::HasInstance());
70  ShelfAlignment shelf_alignment = Shell::GetInstance()->GetShelfAlignment(
71      widget->GetNativeView()->GetRootWindow());
72  gfx::Rect offseted(rect);
73  switch (shelf_alignment) {
74    case SHELF_ALIGNMENT_BOTTOM:
75      offseted.Offset(0, kAnimationOffset);
76      break;
77    case SHELF_ALIGNMENT_LEFT:
78      offseted.Offset(-kAnimationOffset, 0);
79      break;
80    case SHELF_ALIGNMENT_RIGHT:
81      offseted.Offset(kAnimationOffset, 0);
82      break;
83    case SHELF_ALIGNMENT_TOP:
84      offseted.Offset(0, -kAnimationOffset);
85      break;
86  }
87
88  return offseted;
89}
90
91// Using |button_bounds|, determine the anchor so that the bubble gets shown
92// above the shelf (used for the alternate shelf theme).
93gfx::Point GetAdjustAnchorPositionToShelf(
94    const gfx::Rect& button_bounds, views::Widget* widget) {
95  DCHECK(Shell::HasInstance());
96  ShelfAlignment shelf_alignment = Shell::GetInstance()->GetShelfAlignment(
97      widget->GetNativeView()->GetRootWindow());
98  gfx::Point anchor(button_bounds.CenterPoint());
99  switch (shelf_alignment) {
100    case SHELF_ALIGNMENT_TOP:
101    case SHELF_ALIGNMENT_BOTTOM:
102      {
103        if (base::i18n::IsRTL()) {
104          int screen_width = widget->GetWorkAreaBoundsInScreen().width();
105          anchor.set_x(std::min(screen_width - kMinimalAnchorPositionOffset,
106                                anchor.x()));
107        } else {
108          anchor.set_x(std::max(kMinimalAnchorPositionOffset, anchor.x()));
109        }
110        int offset = button_bounds.height() / 2 - kArrowTipHeight;
111        if (shelf_alignment == SHELF_ALIGNMENT_TOP)
112          offset = -offset;
113        anchor.set_y(anchor.y() - offset);
114      }
115      break;
116    case SHELF_ALIGNMENT_LEFT:
117      anchor.set_x(button_bounds.right() - kArrowTipHeight);
118      anchor.set_y(std::max(kMinimalAnchorPositionOffset, anchor.y()));
119      break;
120    case SHELF_ALIGNMENT_RIGHT:
121      anchor.set_x(button_bounds.x() + kArrowTipHeight);
122      anchor.set_y(std::max(kMinimalAnchorPositionOffset, anchor.y()));
123      break;
124  }
125
126  return anchor;
127}
128
129}  // namespace
130
131////////////////////////////////////////////////////////////////////////////////
132// AppListController, public:
133
134AppListController::AppListController()
135    : pagination_model_(new app_list::PaginationModel),
136      is_visible_(false),
137      view_(NULL),
138      should_snap_back_(false) {
139  Shell::GetInstance()->AddShellObserver(this);
140  pagination_model_->AddObserver(this);
141}
142
143AppListController::~AppListController() {
144  // Ensures app list view goes before the controller since pagination model
145  // lives in the controller and app list view would access it on destruction.
146  if (view_ && view_->GetWidget())
147    view_->GetWidget()->CloseNow();
148
149  Shell::GetInstance()->RemoveShellObserver(this);
150  pagination_model_->RemoveObserver(this);
151}
152
153void AppListController::SetVisible(bool visible, aura::Window* window) {
154  if (visible == is_visible_)
155    return;
156
157  is_visible_ = visible;
158
159  // App list needs to know the new shelf layout in order to calculate its
160  // UI layout when AppListView visibility changes.
161  Shell::GetPrimaryRootWindowController()->GetShelfLayoutManager()->
162      UpdateAutoHideState();
163
164  if (view_) {
165    ScheduleAnimation();
166  } else if (is_visible_) {
167    // AppListModel and AppListViewDelegate are owned by AppListView. They
168    // will be released with AppListView on close.
169    app_list::AppListView* view = new app_list::AppListView(
170        Shell::GetInstance()->delegate()->CreateAppListViewDelegate());
171    aura::Window* container = GetRootWindowController(window->GetRootWindow())->
172        GetContainer(kShellWindowId_AppListContainer);
173    if (ash::switches::UseAlternateShelfLayout()) {
174      gfx::Rect applist_button_bounds = Launcher::ForWindow(container)->
175          GetAppListButtonView()->GetBoundsInScreen();
176      view->InitAsBubble(
177          container,
178          pagination_model_.get(),
179          NULL,
180          GetAdjustAnchorPositionToShelf(applist_button_bounds,
181              Launcher::ForWindow(container)->GetAppListButtonView()->
182                  GetWidget()),
183          GetBubbleArrow(container),
184          true /* border_accepts_events */);
185      view->SetArrowPaintType(views::BubbleBorder::PAINT_NONE);
186    } else {
187      view->InitAsBubble(
188          container,
189          pagination_model_.get(),
190          Launcher::ForWindow(container)->GetAppListButtonView(),
191          gfx::Point(),
192          GetBubbleArrow(container),
193          true /* border_accepts_events */);
194    }
195    SetView(view);
196    // By setting us as DnD recipient, the app list knows that we can
197    // handle items.
198    if (!CommandLine::ForCurrentProcess()->HasSwitch(
199            ash::switches::kAshDisableDragAndDropAppListToLauncher)) {
200      SetDragAndDropHostOfCurrentAppList(
201          Launcher::ForWindow(window)->GetDragAndDropHostForAppList());
202    }
203  }
204}
205
206bool AppListController::IsVisible() const {
207  return view_ && view_->GetWidget()->IsVisible();
208}
209
210aura::Window* AppListController::GetWindow() {
211  return is_visible_ && view_ ? view_->GetWidget()->GetNativeWindow() : NULL;
212}
213
214////////////////////////////////////////////////////////////////////////////////
215// AppListController, private:
216
217void AppListController::SetDragAndDropHostOfCurrentAppList(
218    app_list::ApplicationDragAndDropHost* drag_and_drop_host) {
219  if (view_ && is_visible_)
220    view_->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host);
221}
222
223void AppListController::SetView(app_list::AppListView* view) {
224  DCHECK(view_ == NULL);
225  DCHECK(is_visible_);
226
227  view_ = view;
228  views::Widget* widget = view_->GetWidget();
229  widget->AddObserver(this);
230  Shell::GetInstance()->AddPreTargetHandler(this);
231  Launcher::ForWindow(widget->GetNativeWindow())->AddIconObserver(this);
232  widget->GetNativeView()->GetRootWindow()->AddObserver(this);
233  aura::client::GetFocusClient(widget->GetNativeView())->AddObserver(this);
234
235  view_->ShowWhenReady();
236}
237
238void AppListController::ResetView() {
239  if (!view_)
240    return;
241
242  views::Widget* widget = view_->GetWidget();
243  widget->RemoveObserver(this);
244  GetLayer(widget)->GetAnimator()->RemoveObserver(this);
245  Shell::GetInstance()->RemovePreTargetHandler(this);
246  Launcher::ForWindow(widget->GetNativeWindow())->RemoveIconObserver(this);
247  widget->GetNativeView()->GetRootWindow()->RemoveObserver(this);
248  aura::client::GetFocusClient(widget->GetNativeView())->RemoveObserver(this);
249  view_ = NULL;
250}
251
252void AppListController::ScheduleAnimation() {
253  // Stop observing previous animation.
254  StopObservingImplicitAnimations();
255
256  views::Widget* widget = view_->GetWidget();
257  ui::Layer* layer = GetLayer(widget);
258  layer->GetAnimator()->StopAnimating();
259
260  gfx::Rect target_bounds;
261  if (is_visible_) {
262    target_bounds = widget->GetWindowBoundsInScreen();
263    widget->SetBounds(OffsetTowardsShelf(target_bounds, widget));
264  } else {
265    target_bounds = OffsetTowardsShelf(widget->GetWindowBoundsInScreen(),
266                                       widget);
267  }
268
269  ui::ScopedLayerAnimationSettings animation(layer->GetAnimator());
270  animation.SetTransitionDuration(
271      base::TimeDelta::FromMilliseconds(
272          is_visible_ ? 0 : kAnimationDurationMs));
273  animation.AddObserver(this);
274
275  layer->SetOpacity(is_visible_ ? 1.0 : 0.0);
276  widget->SetBounds(target_bounds);
277}
278
279void AppListController::ProcessLocatedEvent(ui::LocatedEvent* event) {
280  // If the event happened on a menu, then the event should not close the app
281  // list.
282  aura::Window* target = static_cast<aura::Window*>(event->target());
283  if (target) {
284    RootWindowController* root_controller =
285        GetRootWindowController(target->GetRootWindow());
286    if (root_controller) {
287      aura::Window* menu_container = root_controller->GetContainer(
288          ash::internal::kShellWindowId_MenuContainer);
289      if (menu_container->Contains(target))
290        return;
291    }
292  }
293
294  if (view_ && is_visible_) {
295    aura::Window* window = view_->GetWidget()->GetNativeView();
296    gfx::Point window_local_point(event->root_location());
297    aura::Window::ConvertPointToTarget(window->GetRootWindow(),
298                                       window,
299                                       &window_local_point);
300    // Use HitTest to respect the hit test mask of the bubble.
301    if (!window->HitTest(window_local_point))
302      SetVisible(false, window);
303  }
304}
305
306void AppListController::UpdateBounds() {
307  if (view_ && is_visible_)
308    view_->UpdateBounds();
309}
310
311////////////////////////////////////////////////////////////////////////////////
312// AppListController, aura::EventFilter implementation:
313
314void AppListController::OnMouseEvent(ui::MouseEvent* event) {
315  if (event->type() == ui::ET_MOUSE_PRESSED)
316    ProcessLocatedEvent(event);
317}
318
319void AppListController::OnGestureEvent(ui::GestureEvent* event) {
320  if (event->type() == ui::ET_GESTURE_TAP_DOWN)
321    ProcessLocatedEvent(event);
322}
323
324////////////////////////////////////////////////////////////////////////////////
325// AppListController,  aura::FocusObserver implementation:
326
327void AppListController::OnWindowFocused(aura::Window* gained_focus,
328                                        aura::Window* lost_focus) {
329  if (gained_focus && view_ && is_visible_) {
330    aura::Window* applist_container =
331        GetRootWindowController(gained_focus->GetRootWindow())->GetContainer(
332            kShellWindowId_AppListContainer);
333    if (gained_focus->parent() != applist_container)
334      SetVisible(false, gained_focus);
335  }
336}
337
338////////////////////////////////////////////////////////////////////////////////
339// AppListController,  aura::WindowObserver implementation:
340void AppListController::OnWindowBoundsChanged(aura::Window* root,
341                                              const gfx::Rect& old_bounds,
342                                              const gfx::Rect& new_bounds) {
343  UpdateBounds();
344}
345
346////////////////////////////////////////////////////////////////////////////////
347// AppListController, ui::ImplicitAnimationObserver implementation:
348
349void AppListController::OnImplicitAnimationsCompleted() {
350  if (is_visible_ )
351    view_->GetWidget()->Activate();
352  else
353    view_->GetWidget()->Close();
354}
355
356////////////////////////////////////////////////////////////////////////////////
357// AppListController, views::WidgetObserver implementation:
358
359void AppListController::OnWidgetDestroying(views::Widget* widget) {
360  DCHECK(view_->GetWidget() == widget);
361  if (is_visible_)
362    SetVisible(false, widget->GetNativeView());
363  ResetView();
364}
365
366////////////////////////////////////////////////////////////////////////////////
367// AppListController, ShellObserver implementation:
368void AppListController::OnShelfAlignmentChanged(aura::RootWindow* root_window) {
369  if (view_)
370    view_->SetBubbleArrow(GetBubbleArrow(view_->GetWidget()->GetNativeView()));
371}
372
373////////////////////////////////////////////////////////////////////////////////
374// AppListController, LauncherIconObserver implementation:
375
376void AppListController::OnLauncherIconPositionsChanged() {
377  UpdateBounds();
378}
379
380////////////////////////////////////////////////////////////////////////////////
381// AppListController, PaginationModelObserver implementation:
382
383void AppListController::TotalPagesChanged() {
384}
385
386void AppListController::SelectedPageChanged(int old_selected,
387                                            int new_selected) {
388}
389
390void AppListController::TransitionStarted() {
391}
392
393void AppListController::TransitionChanged() {
394  // |view_| could be NULL when app list is closed with a running transition.
395  if (!view_)
396    return;
397
398  const app_list::PaginationModel::Transition& transition =
399      pagination_model_->transition();
400  if (pagination_model_->is_valid_page(transition.target_page))
401    return;
402
403  views::Widget* widget = view_->GetWidget();
404  ui::LayerAnimator* widget_animator = GetLayer(widget)->GetAnimator();
405  if (!pagination_model_->IsRevertingCurrentTransition()) {
406    // Update cached |view_bounds_| if it is the first over-scroll move and
407    // widget does not have running animations.
408    if (!should_snap_back_ && !widget_animator->is_animating())
409      view_bounds_ = widget->GetWindowBoundsInScreen();
410
411    const int current_page = pagination_model_->selected_page();
412    const int dir = transition.target_page > current_page ? -1 : 1;
413
414    const double progress = 1.0 - pow(1.0 - transition.progress, 4);
415    const int shift = kMaxOverScrollShift * progress * dir;
416
417    gfx::Rect shifted(view_bounds_);
418    shifted.set_x(shifted.x() + shift);
419    widget->SetBounds(shifted);
420    should_snap_back_ = true;
421  } else if (should_snap_back_) {
422    should_snap_back_ = false;
423    ui::ScopedLayerAnimationSettings animation(widget_animator);
424    animation.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
425        app_list::kOverscrollPageTransitionDurationMs));
426    widget->SetBounds(view_bounds_);
427  }
428}
429
430}  // namespace internal
431}  // namespace ash
432