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"
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"
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
{
42 explicit StaticViewTargeterDelegate(views::View
* target
) : target_(target
) {}
44 virtual ~StaticViewTargeterDelegate() {}
47 // views::ViewTargeterDelegate:
48 virtual views::View
* TargetForRect(views::View
* root
,
49 const gfx::Rect
& rect
) OVERRIDE
{
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
{
65 explicit PriorityWindowTargeter(views::View
* priority_view
)
66 : priority_view_(priority_view
) {
67 CHECK(priority_view
->GetWidget());
68 window_
= priority_view
->GetWidget()->GetNativeWindow();
70 window_
->AddObserver(this);
73 virtual ~PriorityWindowTargeter() {
74 window_
->RemoveObserver(this);
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,
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
);
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);
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()));
143 bool IsLandscapeOrientation(gfx::Display::Rotation rotation
) {
144 return rotation
== gfx::Display::ROTATE_0
||
145 rotation
== gfx::Display::ROTATE_180
;
150 SplitViewController::SplitViewController(
151 aura::Window
* container
,
152 WindowListProvider
* window_list_provider
)
154 container_(container
),
155 window_list_provider_(window_list_provider
),
158 divider_position_(0),
159 divider_scroll_start_position_(0),
160 divider_widget_(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
,
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
)
188 if (!right
&& right_window_
!= left
)
189 right
= right_window_
;
192 if (!left
&& iter
!= windows
.rend()) {
195 if (left
== right
&& iter
!= windows
.rend()) {
201 if (!right
&& iter
!= windows
.rend()) {
204 if (right
== left
&& iter
!= windows
.rend()) {
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_
);
217 right_window_
= right
;
219 divider_position_
= GetDefaultDividerPosition();
223 aura::client::ActivationClient
* activation_client
=
224 aura::client::GetActivationClient(container_
->GetRootWindow());
225 aura::Window
* active_window
= activation_client
->GetActiveWindow();
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_
);
239 window_list_provider_
->StackWindowBehindTo(left_window_
, right_window_
);
242 void SplitViewController::ReplaceWindow(aura::Window
* window
,
243 aura::Window
* replace_with
) {
244 CHECK(IsSplitViewModeActive());
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_
;
255 right_window_
= replace_with
;
256 not_replaced
= left_window_
;
260 wm::ActivateWindow(replace_with
);
261 window_list_provider_
->StackWindowBehindTo(not_replaced
, replace_with
);
263 window
->SetTransform(gfx::Transform());
267 void SplitViewController::DeactivateSplitMode() {
268 CHECK_EQ(ACTIVE
, state_
);
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
,
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
,
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,
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
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();
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,
355 void SplitViewController::SetState(SplitViewController::State state
) {
359 if (divider_widget_
== NULL
)
364 ScreenManager::Get()->SetRotationLocked(state_
!= INACTIVE
);
365 if (state
== INACTIVE
)
371 void SplitViewController::UpdateLayout(bool animate
) {
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();
392 gfx::Transform(), gfx::Transform(), gfx::Transform(), false);
396 left_window_
->Show();
397 right_window_
->Show();
399 gfx::Transform divider_transform
;
400 divider_transform
.Translate(divider_position_
, 0);
401 if (state_
== ACTIVE
) {
403 gfx::Transform left_transform
=
404 GetTransformForBounds(left_window_
->bounds(), GetLeftAreaBounds());
405 gfx::Transform right_transform
=
406 GetTransformForBounds(right_window_
->bounds(), GetRightAreaBounds());
408 left_transform
, right_transform
, divider_transform
, true);
410 left_window_
->SetBounds(GetLeftAreaBounds());
411 right_window_
->SetBounds(GetRightAreaBounds());
413 gfx::Transform(), gfx::Transform(), divider_transform
, false);
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);
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);
435 GetTransformForBounds(right_window_
->bounds(), right_area_bounds
);
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
,
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
);
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
)
485 for (size_t i
= 0; i
< to_hide_
.size(); ++i
)
489 if (state_
== INACTIVE
) {
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
,
504 if (!BezelCanScroll())
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
);
515 right_window_
= current_window
;
516 left_window_
= *(iter
+ 1);
518 left_window_
= current_window
;
519 right_window_
= *(iter
+ 1);
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
;
538 void SplitViewController::BezelScrollEnd() {
539 if (state_
!= SCROLLING
)
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
;
550 } else if (divider_position_
< default_divider_position
) {
551 divider_position_
= 0;
553 wm::ActivateWindow(right_window_
);
555 divider_position_
= container_
->GetBoundsInScreen().width();
557 wm::ActivateWindow(left_window_
);
562 void SplitViewController::BezelScrollUpdate(float delta
) {
563 if (state_
!= SCROLLING
)
565 divider_position_
= divider_scroll_start_position_
+ delta
;
569 bool SplitViewController::BezelCanScroll() {
570 return CanActivateSplitViewMode();
573 ///////////////////////////////////////////////////////////////////////////////
574 // DragHandleScrollDelegate:
576 void SplitViewController::HandleScrollBegin(float delta
) {
577 CHECK(state_
== ACTIVE
);
579 divider_scroll_start_position_
= GetDefaultDividerPosition();
580 divider_position_
= divider_scroll_start_position_
+ delta
;
584 void SplitViewController::HandleScrollEnd() {
588 void SplitViewController::HandleScrollUpdate(float delta
) {
589 BezelScrollUpdate(delta
);
592 ///////////////////////////////////////////////////////////////////////////////
593 // WindowManagerObserver:
595 void SplitViewController::OnOverviewModeEnter() {
600 void SplitViewController::OnOverviewModeExit() {
601 if (state_
!= INACTIVE
)
605 void SplitViewController::OnSplitViewModeEnter() {
608 void SplitViewController::OnSplitViewModeExit() {
611 } // namespace athena