1// Copyright 2014 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 "athena/home/home_card_impl.h"
6
7#include <cmath>
8#include <limits>
9
10#include "athena/env/public/athena_env.h"
11#include "athena/home/app_list_view_delegate.h"
12#include "athena/home/athena_start_page_view.h"
13#include "athena/home/home_card_constants.h"
14#include "athena/home/minimized_home.h"
15#include "athena/home/public/app_model_builder.h"
16#include "athena/screen/public/screen_manager.h"
17#include "athena/util/container_priorities.h"
18#include "athena/wm/public/window_manager.h"
19#include "ui/app_list/search_provider.h"
20#include "ui/app_list/views/app_list_main_view.h"
21#include "ui/app_list/views/contents_view.h"
22#include "ui/aura/layout_manager.h"
23#include "ui/aura/window.h"
24#include "ui/compositor/layer.h"
25#include "ui/compositor/scoped_layer_animation_settings.h"
26#include "ui/gfx/animation/tween.h"
27#include "ui/views/layout/fill_layout.h"
28#include "ui/views/widget/widget.h"
29#include "ui/views/widget/widget_delegate.h"
30#include "ui/wm/core/shadow_types.h"
31#include "ui/wm/core/visibility_controller.h"
32#include "ui/wm/public/activation_client.h"
33
34namespace athena {
35namespace {
36
37HomeCard* instance = NULL;
38
39gfx::Rect GetBoundsForState(const gfx::Rect& screen_bounds,
40                            HomeCard::State state) {
41  switch (state) {
42    case HomeCard::HIDDEN:
43      break;
44
45    case HomeCard::VISIBLE_CENTERED:
46      return screen_bounds;
47
48    // Do not change the home_card's size, only changes the top position
49    // instead, because size change causes unnecessary re-layouts.
50    case HomeCard::VISIBLE_BOTTOM:
51      return gfx::Rect(0,
52                       screen_bounds.bottom() - kHomeCardHeight,
53                       screen_bounds.width(),
54                       screen_bounds.height());
55    case HomeCard::VISIBLE_MINIMIZED:
56      return gfx::Rect(0,
57                       screen_bounds.bottom() - kHomeCardMinimizedHeight,
58                       screen_bounds.width(),
59                       screen_bounds.height());
60  }
61
62  NOTREACHED();
63  return gfx::Rect();
64}
65
66}  // namespace
67
68// Makes sure the homecard is center-aligned horizontally and bottom-aligned
69// vertically.
70class HomeCardLayoutManager : public aura::LayoutManager {
71 public:
72  HomeCardLayoutManager()
73      : home_card_(NULL),
74        minimized_layer_(NULL) {}
75
76  virtual ~HomeCardLayoutManager() {}
77
78  void Layout(bool animate, gfx::Tween::Type tween_type) {
79    // |home_card| could be detached from the root window (e.g. when it is being
80    // destroyed).
81    if (!home_card_ || !home_card_->IsVisible() || !home_card_->GetRootWindow())
82      return;
83
84    scoped_ptr<ui::ScopedLayerAnimationSettings> settings;
85    if (animate) {
86      settings.reset(new ui::ScopedLayerAnimationSettings(
87          home_card_->layer()->GetAnimator()));
88      settings->SetTweenType(tween_type);
89    }
90    SetChildBoundsDirect(home_card_, GetBoundsForState(
91        home_card_->GetRootWindow()->bounds(), HomeCard::Get()->GetState()));
92  }
93
94  void SetMinimizedLayer(ui::Layer* minimized_layer) {
95    minimized_layer_ = minimized_layer;
96    UpdateMinimizedHomeBounds();
97  }
98
99 private:
100  void UpdateMinimizedHomeBounds() {
101    gfx::Rect minimized_bounds = minimized_layer_->parent()->bounds();
102    minimized_bounds.set_y(
103        minimized_bounds.bottom() - kHomeCardMinimizedHeight);
104    minimized_bounds.set_height(kHomeCardMinimizedHeight);
105    minimized_layer_->SetBounds(minimized_bounds);
106  }
107
108  // aura::LayoutManager:
109  virtual void OnWindowResized() OVERRIDE {
110    Layout(false, gfx::Tween::LINEAR);
111    UpdateMinimizedHomeBounds();
112  }
113  virtual void OnWindowAddedToLayout(aura::Window* child) OVERRIDE {
114    if (!home_card_) {
115      home_card_ = child;
116      Layout(false, gfx::Tween::LINEAR);
117    }
118  }
119  virtual void OnWillRemoveWindowFromLayout(aura::Window* child) OVERRIDE {
120    if (home_card_ == child)
121      home_card_ = NULL;
122  }
123  virtual void OnWindowRemovedFromLayout(aura::Window* child) OVERRIDE {
124  }
125  virtual void OnChildWindowVisibilityChanged(aura::Window* child,
126                                              bool visible) OVERRIDE {
127    if (home_card_ == child)
128      Layout(false, gfx::Tween::LINEAR);
129  }
130  virtual void SetChildBounds(aura::Window* child,
131                              const gfx::Rect& requested_bounds) OVERRIDE {
132    SetChildBoundsDirect(child, requested_bounds);
133  }
134
135  aura::Window* home_card_;
136  ui::Layer* minimized_layer_;
137
138  DISALLOW_COPY_AND_ASSIGN(HomeCardLayoutManager);
139};
140
141// The container view of home card contents of each state.
142class HomeCardView : public views::WidgetDelegateView {
143 public:
144  HomeCardView(app_list::AppListViewDelegate* view_delegate,
145               aura::Window* container,
146               HomeCardGestureManager::Delegate* gesture_delegate)
147      : gesture_delegate_(gesture_delegate) {
148    SetLayoutManager(new views::FillLayout());
149    // Ideally AppListMainView should be used here and have AthenaStartPageView
150    // as its child view, so that custom pages and apps grid are available in
151    // the home card.
152    // TODO(mukai): make it so after the detailed UI has been fixed.
153    main_view_ = new AthenaStartPageView(view_delegate);
154    AddChildView(main_view_);
155  }
156
157  void SetStateProgress(HomeCard::State from_state,
158                        HomeCard::State to_state,
159                        float progress) {
160    // TODO(mukai): not clear the focus, but simply close the virtual keyboard.
161    GetFocusManager()->ClearFocus();
162    if (from_state == HomeCard::VISIBLE_CENTERED)
163      main_view_->SetLayoutState(1.0f - progress);
164    else if (to_state == HomeCard::VISIBLE_CENTERED)
165      main_view_->SetLayoutState(progress);
166    UpdateShadow(true);
167  }
168
169  void SetStateWithAnimation(HomeCard::State state,
170                             gfx::Tween::Type tween_type) {
171    UpdateShadow(state != HomeCard::VISIBLE_MINIMIZED);
172    if (state == HomeCard::VISIBLE_CENTERED)
173      main_view_->RequestFocusOnSearchBox();
174    else
175      GetWidget()->GetFocusManager()->ClearFocus();
176
177    main_view_->SetLayoutStateWithAnimation(
178        (state == HomeCard::VISIBLE_CENTERED) ? 1.0f : 0.0f, tween_type);
179  }
180
181  void ClearGesture() {
182    gesture_manager_.reset();
183  }
184
185  // views::View:
186  virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE {
187    if (!gesture_manager_ &&
188        event->type() == ui::ET_GESTURE_SCROLL_BEGIN) {
189      gesture_manager_.reset(new HomeCardGestureManager(
190          gesture_delegate_,
191          GetWidget()->GetNativeWindow()->GetRootWindow()->bounds()));
192    }
193
194    if (gesture_manager_)
195      gesture_manager_->ProcessGestureEvent(event);
196  }
197  virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE {
198    if (HomeCard::Get()->GetState() == HomeCard::VISIBLE_MINIMIZED &&
199        event.IsLeftMouseButton() && event.GetClickCount() == 1) {
200      athena::WindowManager::Get()->ToggleOverview();
201      return true;
202    }
203    return false;
204  }
205
206 private:
207  void UpdateShadow(bool should_show) {
208    wm::SetShadowType(
209        GetWidget()->GetNativeWindow(),
210        should_show ? wm::SHADOW_TYPE_RECTANGULAR : wm::SHADOW_TYPE_NONE);
211  }
212
213  // views::WidgetDelegate:
214  virtual views::View* GetContentsView() OVERRIDE {
215    return this;
216  }
217
218  AthenaStartPageView* main_view_;
219  scoped_ptr<HomeCardGestureManager> gesture_manager_;
220  HomeCardGestureManager::Delegate* gesture_delegate_;
221
222  DISALLOW_COPY_AND_ASSIGN(HomeCardView);
223};
224
225HomeCardImpl::HomeCardImpl(AppModelBuilder* model_builder)
226    : model_builder_(model_builder),
227      state_(HIDDEN),
228      original_state_(VISIBLE_MINIMIZED),
229      home_card_widget_(NULL),
230      home_card_view_(NULL),
231      layout_manager_(NULL),
232      activation_client_(NULL) {
233  DCHECK(!instance);
234  instance = this;
235  WindowManager::Get()->AddObserver(this);
236}
237
238HomeCardImpl::~HomeCardImpl() {
239  DCHECK(instance);
240  WindowManager::Get()->RemoveObserver(this);
241  if (activation_client_)
242    activation_client_->RemoveObserver(this);
243  home_card_widget_->CloseNow();
244
245  // Reset the view delegate first as it access search provider during
246  // shutdown.
247  view_delegate_.reset();
248  search_provider_.reset();
249  instance = NULL;
250}
251
252void HomeCardImpl::Init() {
253  InstallAccelerators();
254  ScreenManager::ContainerParams params("HomeCardContainer", CP_HOME_CARD);
255  params.can_activate_children = true;
256  aura::Window* container = ScreenManager::Get()->CreateContainer(params);
257  layout_manager_ = new HomeCardLayoutManager();
258
259  container->SetLayoutManager(layout_manager_);
260  wm::SetChildWindowVisibilityChangesAnimated(container);
261
262  view_delegate_.reset(new AppListViewDelegate(model_builder_.get()));
263  if (search_provider_)
264    view_delegate_->RegisterSearchProvider(search_provider_.get());
265
266  home_card_view_ = new HomeCardView(view_delegate_.get(), container, this);
267  home_card_widget_ = new views::Widget();
268  views::Widget::InitParams widget_params(
269      views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
270  widget_params.parent = container;
271  widget_params.delegate = home_card_view_;
272  widget_params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
273  home_card_widget_->Init(widget_params);
274
275  minimized_home_ = CreateMinimizedHome();
276  container->layer()->Add(minimized_home_->layer());
277  container->layer()->StackAtTop(minimized_home_->layer());
278  layout_manager_->SetMinimizedLayer(minimized_home_->layer());
279
280  SetState(VISIBLE_MINIMIZED);
281  home_card_view_->Layout();
282
283  activation_client_ =
284      aura::client::GetActivationClient(container->GetRootWindow());
285  if (activation_client_)
286    activation_client_->AddObserver(this);
287
288  AthenaEnv::Get()->SetDisplayWorkAreaInsets(
289      gfx::Insets(0, 0, kHomeCardMinimizedHeight, 0));
290}
291
292aura::Window* HomeCardImpl::GetHomeCardWindowForTest() const {
293  return home_card_widget_ ? home_card_widget_->GetNativeWindow() : NULL;
294}
295
296void HomeCardImpl::InstallAccelerators() {
297  const AcceleratorData accelerator_data[] = {
298      {TRIGGER_ON_PRESS, ui::VKEY_L, ui::EF_CONTROL_DOWN,
299       COMMAND_SHOW_HOME_CARD, AF_NONE},
300  };
301  AcceleratorManager::Get()->RegisterAccelerators(
302      accelerator_data, arraysize(accelerator_data), this);
303}
304
305void HomeCardImpl::SetState(HomeCard::State state) {
306  if (state_ == state)
307    return;
308
309  // Update |state_| before changing the visibility of the widgets, so that
310  // LayoutManager callbacks get the correct state.
311  HomeCard::State old_state = state_;
312  state_ = state;
313  original_state_ = state;
314
315  if (old_state == VISIBLE_MINIMIZED ||
316      state_ == VISIBLE_MINIMIZED) {
317    minimized_home_->layer()->SetVisible(true);
318    {
319      ui::ScopedLayerAnimationSettings settings(
320          minimized_home_->layer()->GetAnimator());
321      minimized_home_->layer()->SetVisible(state_ == VISIBLE_MINIMIZED);
322      minimized_home_->layer()->SetOpacity(
323          state_ == VISIBLE_MINIMIZED ? 1.0f : 0.0f);
324    }
325  }
326  if (state_ == HIDDEN) {
327    home_card_widget_->Hide();
328  } else {
329    if (state_ == VISIBLE_MINIMIZED)
330      home_card_widget_->ShowInactive();
331    else
332      home_card_widget_->Show();
333    home_card_view_->SetStateWithAnimation(state, gfx::Tween::EASE_IN_OUT);
334    layout_manager_->Layout(true, gfx::Tween::EASE_IN_OUT);
335  }
336}
337
338HomeCard::State HomeCardImpl::GetState() {
339  return state_;
340}
341
342void HomeCardImpl::RegisterSearchProvider(
343    app_list::SearchProvider* search_provider) {
344  DCHECK(!search_provider_);
345  search_provider_.reset(search_provider);
346  view_delegate_->RegisterSearchProvider(search_provider_.get());
347}
348
349void HomeCardImpl::UpdateVirtualKeyboardBounds(
350    const gfx::Rect& bounds) {
351  if (state_ == VISIBLE_MINIMIZED && !bounds.IsEmpty()) {
352    SetState(HIDDEN);
353    original_state_ = VISIBLE_MINIMIZED;
354  } else if (state_ == VISIBLE_BOTTOM && !bounds.IsEmpty()) {
355    SetState(VISIBLE_CENTERED);
356    original_state_ = VISIBLE_BOTTOM;
357  } else if (state_ != original_state_ && bounds.IsEmpty()) {
358    SetState(original_state_);
359  }
360}
361
362bool HomeCardImpl::IsCommandEnabled(int command_id) const {
363  return true;
364}
365
366bool HomeCardImpl::OnAcceleratorFired(int command_id,
367                                      const ui::Accelerator& accelerator) {
368  DCHECK_EQ(COMMAND_SHOW_HOME_CARD, command_id);
369
370  if (state_ == VISIBLE_CENTERED && original_state_ != VISIBLE_BOTTOM)
371    SetState(VISIBLE_MINIMIZED);
372  else if (state_ == VISIBLE_MINIMIZED)
373    SetState(VISIBLE_CENTERED);
374  return true;
375}
376
377void HomeCardImpl::OnGestureEnded(State final_state, bool is_fling) {
378  home_card_view_->ClearGesture();
379  if (state_ != final_state &&
380      (state_ == VISIBLE_MINIMIZED || final_state == VISIBLE_MINIMIZED)) {
381    SetState(final_state);
382    WindowManager::Get()->ToggleOverview();
383  } else {
384    state_ = final_state;
385    // When the animation happens after a fling, EASE_IN_OUT would cause weird
386    // slow-down right after the finger release because of slow-in. Therefore
387    // EASE_OUT is better.
388    gfx::Tween::Type tween_type =
389        is_fling ? gfx::Tween::EASE_OUT : gfx::Tween::EASE_IN_OUT;
390    home_card_view_->SetStateWithAnimation(state_, tween_type);
391    layout_manager_->Layout(true, tween_type);
392  }
393}
394
395void HomeCardImpl::OnGestureProgressed(
396    State from_state, State to_state, float progress) {
397  if (from_state == VISIBLE_MINIMIZED || to_state == VISIBLE_MINIMIZED) {
398    minimized_home_->layer()->SetVisible(true);
399    float opacity =
400        (from_state == VISIBLE_MINIMIZED) ? 1.0f - progress : progress;
401    minimized_home_->layer()->SetOpacity(opacity);
402  }
403  gfx::Rect screen_bounds =
404      home_card_widget_->GetNativeWindow()->GetRootWindow()->bounds();
405  home_card_widget_->SetBounds(gfx::Tween::RectValueBetween(
406      progress,
407      GetBoundsForState(screen_bounds, from_state),
408      GetBoundsForState(screen_bounds, to_state)));
409
410  home_card_view_->SetStateProgress(from_state, to_state, progress);
411
412  // TODO(mukai): signals the update to the window manager so that it shows the
413  // intermediate visual state of overview mode.
414}
415
416void HomeCardImpl::OnOverviewModeEnter() {
417  if (state_ == HIDDEN || state_ == VISIBLE_MINIMIZED)
418    SetState(VISIBLE_BOTTOM);
419}
420
421void HomeCardImpl::OnOverviewModeExit() {
422  SetState(VISIBLE_MINIMIZED);
423}
424
425void HomeCardImpl::OnSplitViewModeEnter() {
426}
427
428void HomeCardImpl::OnSplitViewModeExit() {
429}
430
431void HomeCardImpl::OnWindowActivated(aura::Window* gained_active,
432                                     aura::Window* lost_active) {
433  if (state_ != HIDDEN &&
434      gained_active != home_card_widget_->GetNativeWindow()) {
435    SetState(VISIBLE_MINIMIZED);
436  }
437}
438
439// static
440HomeCard* HomeCard::Create(AppModelBuilder* model_builder) {
441  (new HomeCardImpl(model_builder))->Init();
442  DCHECK(instance);
443  return instance;
444}
445
446// static
447void HomeCard::Shutdown() {
448  DCHECK(instance);
449  delete instance;
450  instance = NULL;
451}
452
453// static
454HomeCard* HomeCard::Get() {
455  DCHECK(instance);
456  return instance;
457}
458
459}  // namespace athena
460