[Athena] Fix switching activities by swiping from the right bezel
[chromium-blink-merge.git] / athena / wm / split_view_controller.cc
blob40749c76eb2737105cfb7380a9f26b11a59a7d85
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.
5 #include "athena/wm/split_view_controller.h"
7 #include <cmath>
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"
30 namespace athena {
32 namespace {
34 const int kDragHandleWidth = 4;
35 const int kDragHandleHeight = 80;
36 const int kDragHandleMargin = 1;
37 const int kDividerWidth = kDragHandleWidth + 2 * kDragHandleMargin;
39 // Always returns the same target.
40 class StaticViewTargeterDelegate : public views::ViewTargeterDelegate {
41 public:
42 explicit StaticViewTargeterDelegate(views::View* target) : target_(target) {}
44 virtual ~StaticViewTargeterDelegate() {}
46 private:
47 // views::ViewTargeterDelegate:
48 virtual views::View* TargetForRect(views::View* root,
49 const gfx::Rect& rect) OVERRIDE {
50 return target_;
53 // Not owned.
54 views::View* target_;
56 DISALLOW_COPY_AND_ASSIGN(StaticViewTargeterDelegate);
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);
73 virtual ~PriorityWindowTargeter() {
74 window_->RemoveObserver(this);
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());
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_;
112 return WindowTargeter::FindTargetForLocatedEvent(root, event);
115 // aura::WindowObserver:
116 virtual void OnWindowDestroying(aura::Window* window) OVERRIDE {
117 DCHECK_EQ(window, window_);
118 window_->RemoveObserver(this);
119 window_ = NULL;
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;
127 aura::Window* window_;
128 views::View* priority_view_;
130 DISALLOW_COPY_AND_ASSIGN(PriorityWindowTargeter);
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;
143 bool IsLandscapeOrientation(gfx::Display::Rotation rotation) {
144 return rotation == gfx::Display::ROTATE_0 ||
145 rotation == gfx::Display::ROTATE_180;
148 } // namespace
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) {
165 SplitViewController::~SplitViewController() {
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()));
176 bool SplitViewController::IsSplitViewModeActive() const {
177 return state_ == ACTIVE;
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_;
192 if (!left && iter != windows.rend()) {
193 left = *iter;
194 iter++;
195 if (left == right && iter != windows.rend()) {
196 left = *iter;
197 iter++;
201 if (!right && iter != windows.rend()) {
202 right = *iter;
203 iter++;
204 if (right == left && iter != windows.rend()) {
205 right = *iter;
206 iter++;
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_);
216 left_window_ = left;
217 right_window_ = right;
219 divider_position_ = GetDefaultDividerPosition();
220 SetState(ACTIVE);
221 UpdateLayout(true);
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_);
234 active_window = activation_client->GetActiveWindow();
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_);
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));
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_;
258 UpdateLayout(false);
260 wm::ActivateWindow(replace_with);
261 window_list_provider_->StackWindowBehindTo(not_replaced, replace_with);
263 window->SetTransform(gfx::Transform());
264 window->Hide();
267 void SplitViewController::DeactivateSplitMode() {
268 CHECK_EQ(ACTIVE, state_);
269 SetState(INACTIVE);
270 UpdateLayout(false);
271 left_window_ = right_window_ = NULL;
274 void SplitViewController::InitializeDivider() {
275 CHECK(!divider_widget_);
276 CHECK(!drag_handle_);
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,
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_);
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,
301 kDividerWidth,
302 container_->bounds().height());
303 divider_widget_->Init(params);
304 divider_widget_->SetContentsView(content_view);
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));
323 void SplitViewController::HideDivider() {
324 divider_widget_->Hide();
325 window_targeter_.reset();
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()));
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());
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,
351 container_width - divider_position_ - kDividerWidth / 2,
352 work_area.height());
355 void SplitViewController::SetState(SplitViewController::State state) {
356 if (state_ == state)
357 return;
359 if (divider_widget_ == NULL)
360 InitializeDivider();
362 state_ = state;
364 ScreenManager::Get()->SetRotationLocked(state_ != INACTIVE);
365 if (state == INACTIVE)
366 HideDivider();
367 else
368 ShowDivider();
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()));
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();
391 SetWindowTransforms(
392 gfx::Transform(), gfx::Transform(), gfx::Transform(), false);
393 return;
396 left_window_->Show();
397 right_window_->Show();
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);
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);
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);
437 SetWindowTransforms(
438 left_transform, right_transform, divider_transform, animate);
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.
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);
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);
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);
479 void SplitViewController::OnAnimationCompleted() {
480 // Animation can be cancelled when deactivated.
481 if (left_window_ == NULL)
482 return;
483 UpdateLayout(false);
485 for (size_t i = 0; i < to_hide_.size(); ++i)
486 to_hide_[i]->Hide();
487 to_hide_.clear();
489 if (state_ == INACTIVE) {
490 left_window_ = NULL;
491 right_window_ = NULL;
495 int SplitViewController::GetDefaultDividerPosition() {
496 return container_->GetBoundsInScreen().width() / 2;
499 ///////////////////////////////////////////////////////////////////////////////
500 // BezelController::ScrollDelegate:
502 void SplitViewController::BezelScrollBegin(BezelController::Bezel bezel,
503 float delta) {
504 if (!BezelCanScroll())
505 return;
507 SetState(SCROLLING);
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);
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);
522 CHECK(left_window_);
523 CHECK(right_window_);
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();
534 divider_position_ = divider_scroll_start_position_ + delta;
535 UpdateLayout(false);
538 void SplitViewController::BezelScrollEnd() {
539 if (state_ != SCROLLING)
540 return;
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_);
559 UpdateLayout(true);
562 void SplitViewController::BezelScrollUpdate(float delta) {
563 if (state_ != SCROLLING)
564 return;
565 divider_position_ = divider_scroll_start_position_ + delta;
566 UpdateLayout(false);
569 bool SplitViewController::BezelCanScroll() {
570 return CanActivateSplitViewMode();
573 ///////////////////////////////////////////////////////////////////////////////
574 // DragHandleScrollDelegate:
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);
584 void SplitViewController::HandleScrollEnd() {
585 BezelScrollEnd();
588 void SplitViewController::HandleScrollUpdate(float delta) {
589 BezelScrollUpdate(delta);
592 ///////////////////////////////////////////////////////////////////////////////
593 // WindowManagerObserver:
595 void SplitViewController::OnOverviewModeEnter() {
596 if (divider_widget_)
597 HideDivider();
600 void SplitViewController::OnOverviewModeExit() {
601 if (state_ != INACTIVE)
602 ShowDivider();
605 void SplitViewController::OnSplitViewModeEnter() {
608 void SplitViewController::OnSplitViewModeExit() {
611 } // namespace athena