Wire ZoomEventManager for OTR profiles.
[chromium-blink-merge.git] / athena / wm / split_view_controller.cc
blob23813ad718624aead26961d7e2936d2c0ad2395d
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_manager.h"
11 #include "athena/wm/window_list_provider_impl.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 // Max distance from the scroll end position to the middle of the screen where
40 // we would go into the split view mode.
41 const float kMaxDistanceFromMiddle = 120.0f;
43 // The minimum x-velocity required for a fling to disengage split view mode
44 // when targeted to the drag handle.
45 const float kMinFlingVelocity = 800.0f;
47 enum WindowToActivate {
48 // Do not activate either of |left_window_| or |right_window_|.
49 WINDOW_NONE,
50 // Activate |left_window_|.
51 WINDOW_LEFT,
52 // Activate |right_window_|.
53 WINDOW_RIGHT
56 // Always returns the same target.
57 class StaticViewTargeterDelegate : public views::ViewTargeterDelegate {
58 public:
59 explicit StaticViewTargeterDelegate(views::View* target) : target_(target) {}
61 ~StaticViewTargeterDelegate() override {}
63 private:
64 // views::ViewTargeterDelegate:
65 virtual views::View* TargetForRect(views::View* root,
66 const gfx::Rect& rect) override {
67 return target_;
70 // Not owned.
71 views::View* target_;
73 DISALLOW_COPY_AND_ASSIGN(StaticViewTargeterDelegate);
76 // Expands the effective target area of the window of the widget containing the
77 // specified view. If the view is large enough to begin with, there should be
78 // no change from the default targeting behavior.
79 class PriorityWindowTargeter : public aura::WindowTargeter,
80 public aura::WindowObserver {
81 public:
82 explicit PriorityWindowTargeter(views::View* priority_view)
83 : priority_view_(priority_view) {
84 CHECK(priority_view->GetWidget());
85 window_ = priority_view->GetWidget()->GetNativeWindow();
86 CHECK(window_);
87 window_->AddObserver(this);
90 ~PriorityWindowTargeter() override { window_->RemoveObserver(this); }
92 private:
93 // aura::WindowTargeter:
94 ui::EventTarget* FindTargetForLocatedEvent(ui::EventTarget* root,
95 ui::LocatedEvent* event) override {
96 if (!window_ || (event->type() != ui::ET_TOUCH_PRESSED))
97 return WindowTargeter::FindTargetForLocatedEvent(root, event);
98 CHECK_EQ(window_, priority_view_->GetWidget()->GetNativeWindow());
100 // Bounds of the view in root window's coordinates.
101 gfx::Rect view_bounds = priority_view_->GetBoundsInScreen();
102 // If there is a transform on the window's layer - apply it.
103 gfx::Transform window_transform = window_->layer()->transform();
104 gfx::RectF transformed_bounds_f = view_bounds;
105 window_transform.TransformRect(&transformed_bounds_f);
106 gfx::Rect transformed_bounds = gfx::Rect(transformed_bounds_f.x(),
107 transformed_bounds_f.y(),
108 transformed_bounds_f.width(),
109 transformed_bounds_f.height());
110 // Now expand the bounds to be at least
111 // kMinTouchDimension x kMinTouchDimension and target the event to the
112 // window if it falls within the expanded bounds
113 gfx::Point center = transformed_bounds.CenterPoint();
114 gfx::Rect extension_rect = gfx::Rect(
115 center.x() - kMinTouchDimension / 2,
116 center.y() - kMinTouchDimension / 2,
117 kMinTouchDimension,
118 kMinTouchDimension);
119 gfx::Rect extended_bounds =
120 gfx::UnionRects(transformed_bounds, extension_rect);
121 if (extended_bounds.Contains(event->root_location())) {
122 root->ConvertEventToTarget(window_, event);
123 return window_;
126 return WindowTargeter::FindTargetForLocatedEvent(root, event);
129 // aura::WindowObserver:
130 void OnWindowDestroying(aura::Window* window) override {
131 DCHECK_EQ(window, window_);
132 window_->RemoveObserver(this);
133 window_ = nullptr;
136 // Minimum dimension of a target to be comfortably touchable.
137 // The effective touch target area of |priority_window_| gets expanded so
138 // that it's width and height is ayt least |kMinTouchDimension|.
139 int const kMinTouchDimension = 26;
141 aura::Window* window_;
142 views::View* priority_view_;
144 DISALLOW_COPY_AND_ASSIGN(PriorityWindowTargeter);
147 // Returns a target transform required to transform |from| to |to|.
148 gfx::Transform GetTransformForBounds(const gfx::Rect& from,
149 const gfx::Rect& to) {
150 gfx::Transform transform;
151 transform.Translate(to.x() - from.x(), to.y() - from.y());
152 transform.Scale(to.width() / static_cast<float>(from.width()),
153 to.height() / static_cast<float>(from.height()));
154 return transform;
157 bool IsLandscapeOrientation(gfx::Display::Rotation rotation) {
158 return rotation == gfx::Display::ROTATE_0 ||
159 rotation == gfx::Display::ROTATE_180;
162 } // namespace
164 SplitViewController::SplitViewController(
165 aura::Window* container,
166 WindowListProviderImpl* window_list_provider)
167 : state_(INACTIVE),
168 container_(container),
169 window_list_provider_(window_list_provider),
170 left_window_(nullptr),
171 right_window_(nullptr),
172 divider_position_(0),
173 divider_scroll_start_position_(0),
174 divider_widget_(nullptr),
175 drag_handle_(nullptr),
176 weak_factory_(this) {
177 window_list_provider_->AddObserver(this);
180 SplitViewController::~SplitViewController() {
181 window_list_provider_->RemoveObserver(this);
184 bool SplitViewController::CanActivateSplitViewMode() const {
185 // TODO(mfomitchev): return false in full screen.
186 return (!IsSplitViewModeActive() &&
187 window_list_provider_->GetWindowList().size() >= 2 &&
188 IsLandscapeOrientation(gfx::Screen::GetNativeScreen()->
189 GetDisplayNearestWindow(container_).rotation()));
192 bool SplitViewController::IsSplitViewModeActive() const {
193 return state_ == ACTIVE;
196 void SplitViewController::ActivateSplitMode(aura::Window* left,
197 aura::Window* right,
198 aura::Window* to_activate) {
199 const aura::Window::Windows& windows = window_list_provider_->GetWindowList();
200 aura::Window::Windows::const_reverse_iterator iter = windows.rbegin();
201 if (state_ == ACTIVE) {
202 if (!left && left_window_ != right)
203 left = left_window_;
204 if (!right && right_window_ != left)
205 right = right_window_;
208 if (!left && iter != windows.rend()) {
209 left = *iter;
210 iter++;
211 if (left == right && iter != windows.rend()) {
212 left = *iter;
213 iter++;
217 if (!right && iter != windows.rend()) {
218 right = *iter;
219 iter++;
220 if (right == left && iter != windows.rend()) {
221 right = *iter;
222 iter++;
226 to_hide_.clear();
227 if (left_window_ && left_window_ != left && left_window_ != right)
228 to_hide_.push_back(left_window_);
229 if (right_window_ && right_window_ != left && right_window_ != right)
230 to_hide_.push_back(right_window_);
232 left_window_ = left;
233 right_window_ = right;
235 divider_position_ = GetDefaultDividerPosition();
236 SetState(ACTIVE);
237 UpdateLayout(true);
239 aura::client::ActivationClient* activation_client =
240 aura::client::GetActivationClient(container_->GetRootWindow());
241 aura::Window* active_window = activation_client->GetActiveWindow();
242 if (to_activate) {
243 CHECK(to_activate == left_window_ || to_activate == right_window_);
244 wm::ActivateWindow(to_activate);
245 } else if (active_window != left_window_ &&
246 active_window != right_window_) {
247 // A window which does not belong to an activity could be active.
248 wm::ActivateWindow(left_window_);
250 active_window = activation_client->GetActiveWindow();
252 if (active_window == left_window_)
253 window_list_provider_->StackWindowBehindTo(right_window_, left_window_);
254 else
255 window_list_provider_->StackWindowBehindTo(left_window_, right_window_);
258 void SplitViewController::ReplaceWindow(aura::Window* window,
259 aura::Window* replace_with) {
260 CHECK(IsSplitViewModeActive());
261 CHECK(replace_with);
262 CHECK(window == left_window_ || window == right_window_);
263 CHECK(replace_with != left_window_ && replace_with != right_window_);
264 DCHECK(window_list_provider_->IsValidWindow(replace_with));
266 aura::Window* not_replaced = nullptr;
267 if (window == left_window_) {
268 left_window_ = replace_with;
269 not_replaced = right_window_;
270 } else {
271 right_window_ = replace_with;
272 not_replaced = left_window_;
274 UpdateLayout(false);
276 wm::ActivateWindow(replace_with);
277 window_list_provider_->StackWindowBehindTo(not_replaced, replace_with);
279 window->SetTransform(gfx::Transform());
280 window->Hide();
283 void SplitViewController::DeactivateSplitMode() {
284 CHECK_EQ(ACTIVE, state_);
285 SetState(INACTIVE);
286 UpdateLayout(false);
287 left_window_ = right_window_ = nullptr;
290 void SplitViewController::InitializeDivider() {
291 CHECK(!divider_widget_);
292 CHECK(!drag_handle_);
294 drag_handle_ = CreateDragHandleView(DRAG_HANDLE_HORIZONTAL,
295 this,
296 kDragHandleWidth,
297 kDragHandleHeight);
298 views::View* content_view = new views::View;
299 content_view->set_background(
300 views::Background::CreateSolidBackground(SK_ColorBLACK));
301 views::BoxLayout* layout =
302 new views::BoxLayout(views::BoxLayout::kHorizontal,
303 kDragHandleMargin,
304 kDragHandleMargin,
306 layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
307 layout->set_cross_axis_alignment(
308 views::BoxLayout::CROSS_AXIS_ALIGNMENT_CENTER);
309 content_view->SetLayoutManager(layout);
310 content_view->AddChildView(drag_handle_);
312 divider_widget_ = new views::Widget();
313 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
314 params.parent = container_;
315 params.bounds = gfx::Rect(-kDividerWidth / 2,
317 kDividerWidth,
318 container_->bounds().height());
319 divider_widget_->Init(params);
320 divider_widget_->SetContentsView(content_view);
322 // Install a static view targeter on the root view which always targets
323 // divider_view.
324 // TODO(mfomitchev,tdanderson): This should not be needed:
325 // 1. crbug.com/414339 - divider_view is the only view and it completely
326 // overlaps the root view.
327 // 2. The logic in ViewTargeterDelegate::TargetForRect could be improved to
328 // work better for views that are narrow in one dimension and long in
329 // another dimension.
330 views::internal::RootView* root_view =
331 static_cast<views::internal::RootView*>(divider_widget_->GetRootView());
332 view_targeter_delegate_.reset(new StaticViewTargeterDelegate(drag_handle_));
333 views::ViewTargeter* targeter =
334 new views::RootViewTargeter(view_targeter_delegate_.get(), root_view);
335 divider_widget_->GetRootView()->SetEventTargeter(
336 scoped_ptr<views::ViewTargeter>(targeter));
339 void SplitViewController::HideDivider() {
340 divider_widget_->Hide();
341 window_targeter_.reset();
344 void SplitViewController::ShowDivider() {
345 divider_widget_->Show();
346 if (!window_targeter_) {
347 scoped_ptr<ui::EventTargeter> window_targeter =
348 scoped_ptr<ui::EventTargeter>(new PriorityWindowTargeter(drag_handle_));
349 window_targeter_.reset(
350 new aura::ScopedWindowTargeter(container_, window_targeter.Pass()));
354 gfx::Rect SplitViewController::GetLeftAreaBounds() {
355 gfx::Rect work_area =
356 gfx::Screen::GetNativeScreen()->GetPrimaryDisplay().work_area();
357 return gfx::Rect(
358 0, 0, divider_position_ - kDividerWidth / 2, work_area.height());
361 gfx::Rect SplitViewController::GetRightAreaBounds() {
362 gfx::Rect work_area =
363 gfx::Screen::GetNativeScreen()->GetPrimaryDisplay().work_area();
364 int container_width = container_->bounds().width();
365 return gfx::Rect(divider_position_ + kDividerWidth / 2,
367 container_width - divider_position_ - kDividerWidth / 2,
368 work_area.height());
371 void SplitViewController::OnWindowAddedToList(aura::Window* added_window) {
374 void SplitViewController::OnWindowRemovedFromList(aura::Window* removed_window,
375 int index) {
376 if (!IsSplitViewModeActive() ||
377 (removed_window != left_window_ && removed_window != right_window_)) {
378 return;
381 DCHECK(!window_list_provider_->IsWindowInList(removed_window));
383 const aura::Window::Windows windows = window_list_provider_->GetWindowList();
384 CHECK_GE(static_cast<int>(windows.size()), 1);
385 DCHECK_GE(index, static_cast<int>(windows.size() - 1));
386 DCHECK_LE(index, static_cast<int>(windows.size()));
388 if (windows.size() == 1) {
389 DeactivateSplitMode();
390 return;
393 aura::Window* next_window = *(windows.rbegin() + 1);
394 if (removed_window == left_window_) {
395 CHECK(right_window_ == windows.back());
396 left_window_ = next_window;
397 } else {
398 CHECK(left_window_ == windows.back());
399 CHECK(removed_window == right_window_);
400 right_window_ = next_window;
402 UpdateLayout(false);
405 void SplitViewController::SetState(SplitViewController::State state) {
406 if (state_ == state)
407 return;
409 if (divider_widget_ == nullptr)
410 InitializeDivider();
412 state_ = state;
414 ScreenManager::Get()->SetRotationLocked(state_ != INACTIVE);
415 if (state == INACTIVE)
416 HideDivider();
417 else
418 ShowDivider();
421 void SplitViewController::UpdateLayout(bool animate) {
422 CHECK(left_window_);
423 CHECK(right_window_);
424 // Splitview can be activated from SplitViewController::ActivateSplitMode or
425 // SplitViewController::ScrollEnd. Additionally we don't want to rotate the
426 // screen while engaging splitview (i.e. state_ == SCROLLING).
427 if (state_ == INACTIVE && !animate) {
428 gfx::Rect work_area =
429 gfx::Screen::GetNativeScreen()->GetPrimaryDisplay().work_area();
430 aura::Window* top_window = window_list_provider_->GetWindowList().back();
431 if (top_window != left_window_) {
432 // TODO(mfomitchev): Use to_hide_ instead
433 left_window_->Hide();
434 right_window_->SetBounds(gfx::Rect(work_area.size()));
436 if (top_window != right_window_) {
437 left_window_->SetBounds(gfx::Rect(work_area.size()));
438 // TODO(mfomitchev): Use to_hide_ instead
439 right_window_->Hide();
441 SetWindowTransforms(
442 gfx::Transform(), gfx::Transform(), gfx::Transform(), false);
443 return;
446 left_window_->Show();
447 right_window_->Show();
449 gfx::Transform divider_transform;
450 divider_transform.Translate(divider_position_, 0);
451 if (state_ == ACTIVE) {
452 if (animate) {
453 gfx::Transform left_transform =
454 GetTransformForBounds(left_window_->bounds(), GetLeftAreaBounds());
455 gfx::Transform right_transform =
456 GetTransformForBounds(right_window_->bounds(), GetRightAreaBounds());
457 SetWindowTransforms(
458 left_transform, right_transform, divider_transform, true);
459 } else {
460 left_window_->SetBounds(GetLeftAreaBounds());
461 right_window_->SetBounds(GetRightAreaBounds());
462 SetWindowTransforms(
463 gfx::Transform(), gfx::Transform(), divider_transform, false);
465 } else {
466 gfx::Transform left_transform;
467 gfx::Transform right_transform;
468 gfx::Rect left_area_bounds = GetLeftAreaBounds();
469 gfx::Rect right_area_bounds = GetRightAreaBounds();
470 // If the width of the window is greater than the width of the area which it
471 // is supposed to occupy - translate the window. Otherwise scale the window
472 // up to fill the target area.
473 if (left_window_->bounds().width() >= left_area_bounds.width()) {
474 left_transform.Translate(
475 left_area_bounds.right() - left_window_->bounds().right(), 0);
476 } else {
477 left_transform =
478 GetTransformForBounds(left_window_->bounds(), left_area_bounds);
480 if (right_window_->bounds().width() >= right_area_bounds.width()) {
481 right_transform.Translate(
482 right_area_bounds.x() - right_window_->bounds().x(), 0);
483 } else {
484 right_transform =
485 GetTransformForBounds(right_window_->bounds(), right_area_bounds);
487 SetWindowTransforms(
488 left_transform, right_transform, divider_transform, animate);
490 // Note: |left_window_| and |right_window_| may be nullptr if calling
491 // SetWindowTransforms():
492 // - caused the in-progress animation to abort.
493 // - started a zero duration animation.
496 void SplitViewController::SetWindowTransforms(
497 const gfx::Transform& left_transform,
498 const gfx::Transform& right_transform,
499 const gfx::Transform& divider_transform,
500 bool animate) {
501 if (animate) {
502 ui::ScopedLayerAnimationSettings left_settings(
503 left_window_->layer()->GetAnimator());
504 left_settings.SetPreemptionStrategy(
505 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
506 left_window_->SetTransform(left_transform);
508 ui::ScopedLayerAnimationSettings divider_widget_settings(
509 divider_widget_->GetNativeWindow()->layer()->GetAnimator());
510 divider_widget_settings.SetPreemptionStrategy(
511 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
512 divider_widget_->GetNativeWindow()->SetTransform(divider_transform);
514 ui::ScopedLayerAnimationSettings right_settings(
515 right_window_->layer()->GetAnimator());
516 right_settings.SetPreemptionStrategy(
517 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
518 right_settings.AddObserver(new ui::ClosureAnimationObserver(
519 base::Bind(&SplitViewController::OnAnimationCompleted,
520 weak_factory_.GetWeakPtr())));
521 right_window_->SetTransform(right_transform);
522 } else {
523 left_window_->SetTransform(left_transform);
524 divider_widget_->GetNativeWindow()->SetTransform(divider_transform);
525 right_window_->SetTransform(right_transform);
529 void SplitViewController::OnAnimationCompleted() {
530 // Animation can be cancelled when deactivated.
531 if (left_window_ == nullptr)
532 return;
533 UpdateLayout(false);
535 for (size_t i = 0; i < to_hide_.size(); ++i)
536 to_hide_[i]->Hide();
537 to_hide_.clear();
539 if (state_ == INACTIVE) {
540 left_window_ = nullptr;
541 right_window_ = nullptr;
545 int SplitViewController::GetDefaultDividerPosition() {
546 return container_->GetBoundsInScreen().width() / 2;
549 float SplitViewController::GetMaxDistanceFromMiddleForTest() const {
550 return kMaxDistanceFromMiddle;
553 float SplitViewController::GetMinFlingVelocityForTest() const {
554 return kMinFlingVelocity;
557 ///////////////////////////////////////////////////////////////////////////////
558 // DragHandleScrollDelegate:
560 void SplitViewController::HandleScrollBegin(float delta) {
561 CHECK(state_ == ACTIVE);
562 state_ = SCROLLING;
563 divider_scroll_start_position_ = GetDefaultDividerPosition();
564 divider_position_ = divider_scroll_start_position_ + delta;
565 UpdateLayout(false);
568 void SplitViewController::HandleScrollEnd(float velocity) {
569 if (state_ != SCROLLING)
570 return;
572 int delta = GetDefaultDividerPosition() - divider_position_;
573 WindowToActivate window = WINDOW_NONE;
574 if (std::abs(velocity) > kMinFlingVelocity)
575 window = velocity > 0 ? WINDOW_LEFT : WINDOW_RIGHT;
576 else if (std::abs(delta) > kMaxDistanceFromMiddle)
577 window = delta > 0 ? WINDOW_RIGHT : WINDOW_LEFT;
579 switch (window) {
580 case WINDOW_NONE:
581 divider_position_ = GetDefaultDividerPosition();
582 SetState(ACTIVE);
583 break;
584 case WINDOW_LEFT:
585 divider_position_ = container_->GetBoundsInScreen().width();
586 SetState(INACTIVE);
587 wm::ActivateWindow(left_window_);
588 break;
589 case WINDOW_RIGHT:
590 divider_position_ = 0;
591 SetState(INACTIVE);
592 wm::ActivateWindow(right_window_);
593 break;
596 UpdateLayout(true);
599 void SplitViewController::HandleScrollUpdate(float delta) {
600 if (state_ != SCROLLING)
601 return;
602 divider_position_ = divider_scroll_start_position_ + delta;
603 UpdateLayout(false);
606 ///////////////////////////////////////////////////////////////////////////////
607 // WindowManagerObserver:
609 void SplitViewController::OnOverviewModeEnter() {
610 if (divider_widget_)
611 HideDivider();
614 void SplitViewController::OnOverviewModeExit() {
615 if (state_ != INACTIVE)
616 ShowDivider();
619 void SplitViewController::OnSplitViewModeEnter() {
622 void SplitViewController::OnSplitViewModeExit() {
625 } // namespace athena