Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / athena / wm / split_view_controller.cc
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/wm/split_view_controller.h"
6
7 #include <cmath>
8
9 #include "athena/screen/public/screen_manager.h"
10 #include "athena/wm/public/window_list_provider.h"
11 #include "athena/wm/public/window_manager.h"
12 #include "base/bind.h"
13 #include "ui/aura/scoped_window_targeter.h"
14 #include "ui/aura/window.h"
15 #include "ui/aura/window_targeter.h"
16 #include "ui/compositor/closure_animation_observer.h"
17 #include "ui/compositor/layer.h"
18 #include "ui/compositor/scoped_layer_animation_settings.h"
19 #include "ui/events/event_handler.h"
20 #include "ui/gfx/display.h"
21 #include "ui/gfx/screen.h"
22 #include "ui/views/background.h"
23 #include "ui/views/layout/box_layout.h"
24 #include "ui/views/widget/root_view.h"
25 #include "ui/views/widget/root_view_targeter.h"
26 #include "ui/views/widget/widget.h"
27 #include "ui/wm/core/window_util.h"
28 #include "ui/wm/public/activation_client.h"
29
30 namespace athena {
31
32 namespace {
33
34 const int kDragHandleWidth = 4;
35 const int kDragHandleHeight = 80;
36 const int kDragHandleMargin = 1;
37 const int kDividerWidth = kDragHandleWidth + 2 * kDragHandleMargin;
38
39 // Always returns the same target.
40 class StaticViewTargeterDelegate : public views::ViewTargeterDelegate {
41  public:
42   explicit StaticViewTargeterDelegate(views::View* target) : target_(target) {}
43
44   virtual ~StaticViewTargeterDelegate() {}
45
46  private:
47   // views::ViewTargeterDelegate:
48   virtual views::View* TargetForRect(views::View* root,
49                                      const gfx::Rect& rect) OVERRIDE {
50     return target_;
51   }
52
53   // Not owned.
54   views::View* target_;
55
56   DISALLOW_COPY_AND_ASSIGN(StaticViewTargeterDelegate);
57 };
58
59 // Expands the effective target area of the window of the widget containing the
60 // specified view. If the view is large enough to begin with, there should be
61 // no change from the default targeting behavior.
62 class PriorityWindowTargeter : public aura::WindowTargeter,
63                                public aura::WindowObserver {
64  public:
65   explicit PriorityWindowTargeter(views::View* priority_view)
66       : priority_view_(priority_view) {
67     CHECK(priority_view->GetWidget());
68     window_ = priority_view->GetWidget()->GetNativeWindow();
69     CHECK(window_);
70     window_->AddObserver(this);
71   }
72
73   virtual ~PriorityWindowTargeter() {
74     window_->RemoveObserver(this);
75   }
76
77  private:
78   // aura::WindowTargeter:
79   virtual ui::EventTarget* FindTargetForLocatedEvent(
80       ui::EventTarget* root,
81       ui::LocatedEvent* event) OVERRIDE {
82     if (!window_ || (event->type() != ui::ET_TOUCH_PRESSED))
83       return WindowTargeter::FindTargetForLocatedEvent(root, event);
84     CHECK_EQ(window_, priority_view_->GetWidget()->GetNativeWindow());
85
86     // Bounds of the view in root window's coordinates.
87     gfx::Rect view_bounds = priority_view_->GetBoundsInScreen();
88     // If there is a transform on the window's layer - apply it.
89     gfx::Transform window_transform = window_->layer()->transform();
90     gfx::RectF transformed_bounds_f = view_bounds;
91     window_transform.TransformRect(&transformed_bounds_f);
92     gfx::Rect transformed_bounds = gfx::Rect(transformed_bounds_f.x(),
93                                              transformed_bounds_f.y(),
94                                              transformed_bounds_f.width(),
95                                              transformed_bounds_f.height());
96     // Now expand the bounds to be at least
97     // kMinTouchDimension x kMinTouchDimension and target the event to the
98     // window if it falls within the expanded bounds
99     gfx::Point center = transformed_bounds.CenterPoint();
100     gfx::Rect extension_rect = gfx::Rect(
101         center.x() - kMinTouchDimension / 2,
102         center.y() - kMinTouchDimension / 2,
103         kMinTouchDimension,
104         kMinTouchDimension);
105     gfx::Rect extended_bounds =
106         gfx::UnionRects(transformed_bounds, extension_rect);
107     if (extended_bounds.Contains(event->root_location())) {
108       root->ConvertEventToTarget(window_, event);
109       return window_;
110     }
111
112     return WindowTargeter::FindTargetForLocatedEvent(root, event);
113   }
114
115   // aura::WindowObserver:
116   virtual void OnWindowDestroying(aura::Window* window) OVERRIDE {
117     DCHECK_EQ(window, window_);
118     window_->RemoveObserver(this);
119     window_ = NULL;
120   }
121
122   // Minimum dimension of a target to be comfortably touchable.
123   // The effective touch target area of |priority_window_| gets expanded so
124   // that it's width and height is ayt least |kMinTouchDimension|.
125   int const kMinTouchDimension = 26;
126
127   aura::Window* window_;
128   views::View* priority_view_;
129
130   DISALLOW_COPY_AND_ASSIGN(PriorityWindowTargeter);
131 };
132
133 // Returns a target transform required to transform |from| to |to|.
134 gfx::Transform GetTransformForBounds(const gfx::Rect& from,
135                                      const gfx::Rect& to) {
136   gfx::Transform transform;
137   transform.Translate(to.x() - from.x(), to.y() - from.y());
138   transform.Scale(to.width() / static_cast<float>(from.width()),
139                   to.height() / static_cast<float>(from.height()));
140   return transform;
141 }
142
143 bool IsLandscapeOrientation(gfx::Display::Rotation rotation) {
144   return rotation == gfx::Display::ROTATE_0 ||
145          rotation == gfx::Display::ROTATE_180;
146 }
147
148 }  // namespace
149
150 SplitViewController::SplitViewController(
151     aura::Window* container,
152     WindowListProvider* window_list_provider)
153     : state_(INACTIVE),
154       container_(container),
155       window_list_provider_(window_list_provider),
156       left_window_(NULL),
157       right_window_(NULL),
158       divider_position_(0),
159       divider_scroll_start_position_(0),
160       divider_widget_(NULL),
161       drag_handle_(NULL),
162       weak_factory_(this) {
163 }
164
165 SplitViewController::~SplitViewController() {
166 }
167
168 bool SplitViewController::CanActivateSplitViewMode() const {
169   // TODO(mfomitchev): return false in full screen.
170   return (!IsSplitViewModeActive() &&
171               window_list_provider_->GetWindowList().size() >= 2 &&
172               IsLandscapeOrientation(gfx::Screen::GetNativeScreen()->
173                   GetDisplayNearestWindow(container_).rotation()));
174 }
175
176 bool SplitViewController::IsSplitViewModeActive() const {
177   return state_ == ACTIVE;
178 }
179
180 void SplitViewController::ActivateSplitMode(aura::Window* left,
181                                             aura::Window* right,
182                                             aura::Window* to_activate) {
183   const aura::Window::Windows& windows = window_list_provider_->GetWindowList();
184   aura::Window::Windows::const_reverse_iterator iter = windows.rbegin();
185   if (state_ == ACTIVE) {
186     if (!left && left_window_ != right)
187       left = left_window_;
188     if (!right && right_window_ != left)
189       right = right_window_;
190   }
191
192   if (!left && iter != windows.rend()) {
193     left = *iter;
194     iter++;
195     if (left == right && iter != windows.rend()) {
196       left = *iter;
197       iter++;
198     }
199   }
200
201   if (!right && iter != windows.rend()) {
202     right = *iter;
203     iter++;
204     if (right == left && iter != windows.rend()) {
205       right = *iter;
206       iter++;
207     }
208   }
209
210   to_hide_.clear();
211   if (left_window_ && left_window_ != left && left_window_ != right)
212     to_hide_.push_back(left_window_);
213   if (right_window_ && right_window_ != left && right_window_ != right)
214     to_hide_.push_back(right_window_);
215
216   left_window_ = left;
217   right_window_ = right;
218
219   divider_position_ = GetDefaultDividerPosition();
220   SetState(ACTIVE);
221   UpdateLayout(true);
222
223   aura::client::ActivationClient* activation_client =
224       aura::client::GetActivationClient(container_->GetRootWindow());
225   aura::Window* active_window = activation_client->GetActiveWindow();
226   if (to_activate) {
227     CHECK(to_activate == left_window_ || to_activate == right_window_);
228     wm::ActivateWindow(to_activate);
229   } else if (active_window != left_window_ &&
230              active_window != right_window_) {
231     // A window which does not belong to an activity could be active.
232     wm::ActivateWindow(left_window_);
233   }
234   active_window = activation_client->GetActiveWindow();
235
236   if (active_window == left_window_)
237     window_list_provider_->StackWindowBehindTo(right_window_, left_window_);
238   else
239     window_list_provider_->StackWindowBehindTo(left_window_, right_window_);
240 }
241
242 void SplitViewController::ReplaceWindow(aura::Window* window,
243                                         aura::Window* replace_with) {
244   CHECK(IsSplitViewModeActive());
245   CHECK(replace_with);
246   CHECK(window == left_window_ || window == right_window_);
247   CHECK(replace_with != left_window_ && replace_with != right_window_);
248   DCHECK(window_list_provider_->IsWindowInList(replace_with));
249
250   aura::Window* not_replaced = NULL;
251   if (window == left_window_) {
252     left_window_ = replace_with;
253     not_replaced = right_window_;
254   } else {
255     right_window_ = replace_with;
256     not_replaced = left_window_;
257   }
258   UpdateLayout(false);
259
260   wm::ActivateWindow(replace_with);
261   window_list_provider_->StackWindowBehindTo(not_replaced, replace_with);
262
263   window->SetTransform(gfx::Transform());
264   window->Hide();
265 }
266
267 void SplitViewController::DeactivateSplitMode() {
268   CHECK_EQ(ACTIVE, state_);
269   SetState(INACTIVE);
270   UpdateLayout(false);
271   left_window_ = right_window_ = NULL;
272 }
273
274 void SplitViewController::InitializeDivider() {
275   CHECK(!divider_widget_);
276   CHECK(!drag_handle_);
277
278   drag_handle_ = CreateDragHandleView(DRAG_HANDLE_HORIZONTAL,
279                                       this,
280                                       kDragHandleWidth,
281                                       kDragHandleHeight);
282   views::View* content_view = new views::View;
283   content_view->set_background(
284       views::Background::CreateSolidBackground(SK_ColorBLACK));
285   views::BoxLayout* layout =
286       new views::BoxLayout(views::BoxLayout::kHorizontal,
287                            kDragHandleMargin,
288                            kDragHandleMargin,
289                            0);
290   layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
291   layout->set_cross_axis_alignment(
292       views::BoxLayout::CROSS_AXIS_ALIGNMENT_CENTER);
293   content_view->SetLayoutManager(layout);
294   content_view->AddChildView(drag_handle_);
295
296   divider_widget_ = new views::Widget();
297   views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
298   params.parent = container_;
299   params.bounds = gfx::Rect(-kDividerWidth / 2,
300                             0,
301                             kDividerWidth,
302                             container_->bounds().height());
303   divider_widget_->Init(params);
304   divider_widget_->SetContentsView(content_view);
305
306   // Install a static view targeter on the root view which always targets
307   // divider_view.
308   // TODO(mfomitchev,tdanderson): This should not be needed:
309   // 1. crbug.com/414339 - divider_view is the only view and it completely
310   //    overlaps the root view.
311   // 2. The logic in ViewTargeterDelegate::TargetForRect could be improved to
312   //    work better for views that are narrow in one dimension and long in
313   //    another dimension.
314   views::internal::RootView* root_view =
315       static_cast<views::internal::RootView*>(divider_widget_->GetRootView());
316   view_targeter_delegate_.reset(new StaticViewTargeterDelegate(drag_handle_));
317   views::ViewTargeter* targeter =
318       new views::RootViewTargeter(view_targeter_delegate_.get(), root_view);
319   divider_widget_->GetRootView()->SetEventTargeter(
320       scoped_ptr<views::ViewTargeter>(targeter));
321 }
322
323 void SplitViewController::HideDivider() {
324   divider_widget_->Hide();
325   window_targeter_.reset();
326 }
327
328 void SplitViewController::ShowDivider() {
329   divider_widget_->Show();
330   if (!window_targeter_) {
331     scoped_ptr<ui::EventTargeter> window_targeter =
332         scoped_ptr<ui::EventTargeter>(new PriorityWindowTargeter(drag_handle_));
333     window_targeter_.reset(
334         new aura::ScopedWindowTargeter(container_, window_targeter.Pass()));
335   }
336 }
337
338 gfx::Rect SplitViewController::GetLeftAreaBounds() {
339   gfx::Rect work_area =
340       gfx::Screen::GetNativeScreen()->GetPrimaryDisplay().work_area();
341   return gfx::Rect(
342       0, 0, divider_position_ - kDividerWidth / 2, work_area.height());
343 }
344
345 gfx::Rect SplitViewController::GetRightAreaBounds() {
346   gfx::Rect work_area =
347       gfx::Screen::GetNativeScreen()->GetPrimaryDisplay().work_area();
348   int container_width = container_->bounds().width();
349   return gfx::Rect(divider_position_ + kDividerWidth / 2,
350                    0,
351                    container_width - divider_position_ - kDividerWidth / 2,
352                    work_area.height());
353 }
354
355 void SplitViewController::SetState(SplitViewController::State state) {
356   if (state_ == state)
357     return;
358
359   if (divider_widget_ == NULL)
360     InitializeDivider();
361
362   state_ = state;
363
364   ScreenManager::Get()->SetRotationLocked(state_ != INACTIVE);
365   if (state == INACTIVE)
366     HideDivider();
367   else
368     ShowDivider();
369 }
370
371 void SplitViewController::UpdateLayout(bool animate) {
372   CHECK(left_window_);
373   CHECK(right_window_);
374   // Splitview can be activated from SplitViewController::ActivateSplitMode or
375   // SplitViewController::ScrollEnd. Additionally we don't want to rotate the
376   // screen while engaging splitview (i.e. state_ == SCROLLING).
377   if (state_ == INACTIVE && !animate) {
378     gfx::Rect work_area =
379         gfx::Screen::GetNativeScreen()->GetPrimaryDisplay().work_area();
380     aura::Window* top_window = window_list_provider_->GetWindowList().back();
381     if (top_window != left_window_) {
382       // TODO(mfomitchev): Use to_hide_ instead
383       left_window_->Hide();
384       right_window_->SetBounds(gfx::Rect(work_area.size()));
385     }
386     if (top_window != right_window_) {
387       left_window_->SetBounds(gfx::Rect(work_area.size()));
388       // TODO(mfomitchev): Use to_hide_ instead
389       right_window_->Hide();
390     }
391     SetWindowTransforms(
392         gfx::Transform(), gfx::Transform(), gfx::Transform(), false);
393     return;
394   }
395
396   left_window_->Show();
397   right_window_->Show();
398
399   gfx::Transform divider_transform;
400   divider_transform.Translate(divider_position_, 0);
401   if (state_ == ACTIVE) {
402     if (animate) {
403       gfx::Transform left_transform =
404           GetTransformForBounds(left_window_->bounds(), GetLeftAreaBounds());
405       gfx::Transform right_transform =
406           GetTransformForBounds(right_window_->bounds(), GetRightAreaBounds());
407       SetWindowTransforms(
408           left_transform, right_transform, divider_transform, true);
409     } else {
410       left_window_->SetBounds(GetLeftAreaBounds());
411       right_window_->SetBounds(GetRightAreaBounds());
412       SetWindowTransforms(
413           gfx::Transform(), gfx::Transform(), divider_transform, false);
414     }
415   } else {
416     gfx::Transform left_transform;
417     gfx::Transform right_transform;
418     gfx::Rect left_area_bounds = GetLeftAreaBounds();
419     gfx::Rect right_area_bounds = GetRightAreaBounds();
420     // If the width of the window is greater than the width of the area which it
421     // is supposed to occupy - translate the window. Otherwise scale the window
422     // up to fill the target area.
423     if (left_window_->bounds().width() >= left_area_bounds.width()) {
424       left_transform.Translate(
425           left_area_bounds.right() - left_window_->bounds().right(), 0);
426     } else {
427       left_transform =
428           GetTransformForBounds(left_window_->bounds(), left_area_bounds);
429     }
430     if (right_window_->bounds().width() >= right_area_bounds.width()) {
431       right_transform.Translate(
432           right_area_bounds.x() - right_window_->bounds().x(), 0);
433     } else {
434       right_transform =
435           GetTransformForBounds(right_window_->bounds(), right_area_bounds);
436     }
437     SetWindowTransforms(
438         left_transform, right_transform, divider_transform, animate);
439   }
440   // Note: |left_window_| and |right_window_| may be NULL if calling
441   // SetWindowTransforms():
442   // - caused the in-progress animation to abort.
443   // - started a zero duration animation.
444 }
445
446 void SplitViewController::SetWindowTransforms(
447     const gfx::Transform& left_transform,
448     const gfx::Transform& right_transform,
449     const gfx::Transform& divider_transform,
450     bool animate) {
451   if (animate) {
452     ui::ScopedLayerAnimationSettings left_settings(
453         left_window_->layer()->GetAnimator());
454     left_settings.SetPreemptionStrategy(
455         ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
456     left_window_->SetTransform(left_transform);
457
458     ui::ScopedLayerAnimationSettings divider_widget_settings(
459         divider_widget_->GetNativeWindow()->layer()->GetAnimator());
460     divider_widget_settings.SetPreemptionStrategy(
461         ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
462     divider_widget_->GetNativeWindow()->SetTransform(divider_transform);
463
464     ui::ScopedLayerAnimationSettings right_settings(
465         right_window_->layer()->GetAnimator());
466     right_settings.SetPreemptionStrategy(
467         ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
468     right_settings.AddObserver(new ui::ClosureAnimationObserver(
469         base::Bind(&SplitViewController::OnAnimationCompleted,
470                    weak_factory_.GetWeakPtr())));
471     right_window_->SetTransform(right_transform);
472   } else {
473     left_window_->SetTransform(left_transform);
474     divider_widget_->GetNativeWindow()->SetTransform(divider_transform);
475     right_window_->SetTransform(right_transform);
476   }
477 }
478
479 void SplitViewController::OnAnimationCompleted() {
480   // Animation can be cancelled when deactivated.
481   if (left_window_ == NULL)
482     return;
483   UpdateLayout(false);
484
485   for (size_t i = 0; i < to_hide_.size(); ++i)
486     to_hide_[i]->Hide();
487   to_hide_.clear();
488
489   if (state_ == INACTIVE) {
490     left_window_ = NULL;
491     right_window_ = NULL;
492   }
493 }
494
495 int SplitViewController::GetDefaultDividerPosition() {
496   return container_->GetBoundsInScreen().width() / 2;
497 }
498
499 ///////////////////////////////////////////////////////////////////////////////
500 // BezelController::ScrollDelegate:
501
502 void SplitViewController::BezelScrollBegin(BezelController::Bezel bezel,
503                                            float delta) {
504   if (!BezelCanScroll())
505     return;
506
507   SetState(SCROLLING);
508
509   const aura::Window::Windows& windows = window_list_provider_->GetWindowList();
510   CHECK(windows.size() >= 2);
511   aura::Window::Windows::const_reverse_iterator iter = windows.rbegin();
512   aura::Window* current_window = *(iter);
513
514   if (delta > 0) {
515     right_window_ = current_window;
516     left_window_ = *(iter + 1);
517   } else {
518     left_window_ = current_window;
519     right_window_ = *(iter + 1);
520   }
521
522   CHECK(left_window_);
523   CHECK(right_window_);
524
525   // Calculate divider_scroll_start_position_
526   gfx::Screen* screen = gfx::Screen::GetScreenFor(container_);
527   const gfx::Rect& display_bounds =
528       screen->GetDisplayNearestWindow(container_).bounds();
529   gfx::Rect container_bounds = container_->GetBoundsInScreen();
530   divider_scroll_start_position_ =
531       delta > 0 ? display_bounds.x() - container_bounds.x()
532                 : display_bounds.right() - container_bounds.x();
533
534   divider_position_ = divider_scroll_start_position_ + delta;
535   UpdateLayout(false);
536 }
537
538 void SplitViewController::BezelScrollEnd() {
539   if (state_ != SCROLLING)
540     return;
541
542   // Max distance from the scroll end position to the middle of the screen where
543   // we would go into the split view mode.
544   const int kMaxDistanceFromMiddle = 120;
545   const int default_divider_position = GetDefaultDividerPosition();
546   if (std::abs(default_divider_position - divider_position_) <=
547       kMaxDistanceFromMiddle) {
548     divider_position_ = default_divider_position;
549     SetState(ACTIVE);
550   } else if (divider_position_ < default_divider_position) {
551     divider_position_ = 0;
552     SetState(INACTIVE);
553     wm::ActivateWindow(right_window_);
554   } else {
555     divider_position_ = container_->GetBoundsInScreen().width();
556     SetState(INACTIVE);
557     wm::ActivateWindow(left_window_);
558   }
559   UpdateLayout(true);
560 }
561
562 void SplitViewController::BezelScrollUpdate(float delta) {
563   if (state_ != SCROLLING)
564     return;
565   divider_position_ = divider_scroll_start_position_ + delta;
566   UpdateLayout(false);
567 }
568
569 bool SplitViewController::BezelCanScroll() {
570   return CanActivateSplitViewMode();
571 }
572
573 ///////////////////////////////////////////////////////////////////////////////
574 // DragHandleScrollDelegate:
575
576 void SplitViewController::HandleScrollBegin(float delta) {
577   CHECK(state_ == ACTIVE);
578   state_ = SCROLLING;
579   divider_scroll_start_position_ = GetDefaultDividerPosition();
580   divider_position_ = divider_scroll_start_position_ + delta;
581   UpdateLayout(false);
582 }
583
584 void SplitViewController::HandleScrollEnd() {
585   BezelScrollEnd();
586 }
587
588 void SplitViewController::HandleScrollUpdate(float delta) {
589   BezelScrollUpdate(delta);
590 }
591
592 ///////////////////////////////////////////////////////////////////////////////
593 // WindowManagerObserver:
594
595 void SplitViewController::OnOverviewModeEnter() {
596   if (divider_widget_)
597     HideDivider();
598 }
599
600 void SplitViewController::OnOverviewModeExit() {
601   if (state_ != INACTIVE)
602     ShowDivider();
603 }
604
605 void SplitViewController::OnSplitViewModeEnter() {
606 }
607
608 void SplitViewController::OnSplitViewModeExit() {
609 }
610
611 }  // namespace athena