1 // Copyright 2018 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 "ash/wm/pip/pip_positioner.h"
6
7 #include <algorithm>
8
9 #include "ash/keyboard/ui/keyboard_ui_controller.h"
10 #include "ash/public/cpp/shell_window_ids.h"
11 #include "ash/public/cpp/window_properties.h"
12 #include "ash/root_window_controller.h"
13 #include "ash/shelf/shelf.h"
14 #include "ash/shell.h"
15 #include "ash/wm/collision_detection/collision_detection_utils.h"
16 #include "ash/wm/window_state.h"
17 #include "ash/wm/window_util.h"
18 #include "ash/wm/work_area_insets.h"
19 #include "base/numerics/safe_conversions.h"
20 #include "ui/aura/window.h"
21 #include "ui/gfx/geometry/insets.h"
22 #include "ui/wm/core/coordinate_conversion.h"
23
24 namespace ash {
25
26 namespace {
27 const float kPipDismissMovementProportion = 1.5f;
28 } // namespace
29
GetBoundsForDrag(const display::Display & display,const gfx::Rect & bounds_in_screen)30 gfx::Rect PipPositioner::GetBoundsForDrag(const display::Display& display,
31 const gfx::Rect& bounds_in_screen) {
32 gfx::Rect drag_bounds = bounds_in_screen;
33 drag_bounds.AdjustToFit(CollisionDetectionUtils::GetMovementArea(display));
34 drag_bounds = CollisionDetectionUtils::AvoidObstacles(
35 display, drag_bounds,
36 CollisionDetectionUtils::RelativePriority::kPictureInPicture);
37 return drag_bounds;
38 }
39
GetDismissedPosition(const display::Display & display,const gfx::Rect & bounds_in_screen)40 gfx::Rect PipPositioner::GetDismissedPosition(
41 const display::Display& display,
42 const gfx::Rect& bounds_in_screen) {
43 gfx::Rect work_area = CollisionDetectionUtils::GetMovementArea(display);
44 const CollisionDetectionUtils::Gravity gravity =
45 CollisionDetectionUtils::GetGravityToClosestEdge(bounds_in_screen,
46 work_area);
47 // Allow the bounds to move at most |kPipDismissMovementProportion| of the
48 // length of the bounds in the direction of movement.
49 gfx::Rect bounds_movement_area = bounds_in_screen;
50 bounds_movement_area.Inset(
51 -bounds_in_screen.width() * kPipDismissMovementProportion,
52 -bounds_in_screen.height() * kPipDismissMovementProportion);
53 gfx::Rect dismissed_bounds =
54 CollisionDetectionUtils::GetAdjustedBoundsByGravity(
55 bounds_in_screen, bounds_movement_area, gravity);
56
57 // If the PIP window isn't close enough to the edge of the screen, don't slide
58 // it out.
59 return work_area.Intersects(dismissed_bounds) ? bounds_in_screen
60 : dismissed_bounds;
61 }
62
GetPositionAfterMovementAreaChange(WindowState * window_state)63 gfx::Rect PipPositioner::GetPositionAfterMovementAreaChange(
64 WindowState* window_state) {
65 // Restore to previous bounds if we have them. This lets us move the PIP
66 // window back to its original bounds after transient movement area changes,
67 // like the keyboard popping up and pushing the PIP window up.
68 gfx::Rect bounds_in_screen = window_state->window()->GetBoundsInScreen();
69 float* snap_fraction =
70 window_state->window()->GetProperty(ash::kPipSnapFractionKey);
71 if (snap_fraction)
72 bounds_in_screen = GetSnapFractionAppliedBounds(window_state);
73 return CollisionDetectionUtils::GetRestingPosition(
74 window_state->GetDisplay(), bounds_in_screen,
75 CollisionDetectionUtils::RelativePriority::kPictureInPicture);
76 }
77
GetSnapFractionAppliedBounds(WindowState * window_state)78 gfx::Rect PipPositioner::GetSnapFractionAppliedBounds(
79 WindowState* window_state) {
80 gfx::Rect bounds = window_state->window()->GetBoundsInScreen();
81 gfx::Rect movement_area =
82 CollisionDetectionUtils::GetMovementArea(window_state->GetDisplay());
83 if (!HasSnapFraction(window_state))
84 return bounds;
85 float snap_fraction =
86 *(window_state->window()->GetProperty(ash::kPipSnapFractionKey));
87
88 if (snap_fraction < 1.) {
89 int offset = movement_area.x() +
90 base::ClampRound(snap_fraction *
91 (movement_area.width() - bounds.width()));
92 return gfx::Rect(offset, movement_area.y(), bounds.width(),
93 bounds.height());
94 } else if (snap_fraction < 2.) {
95 snap_fraction -= 1.;
96 int offset = movement_area.y() +
97 base::ClampRound(snap_fraction *
98 (movement_area.height() - bounds.height()));
99 return gfx::Rect(movement_area.right() - bounds.width(), offset,
100 bounds.width(), bounds.height());
101 } else if (snap_fraction < 3.) {
102 snap_fraction -= 2.;
103 int offset = movement_area.x() +
104 base::ClampRound((1. - snap_fraction) *
105 (movement_area.width() - bounds.width()));
106 return gfx::Rect(offset, movement_area.bottom() - bounds.height(),
107 bounds.width(), bounds.height());
108 } else {
109 snap_fraction -= 3.;
110 int offset = movement_area.y() +
111 base::ClampRound((1. - snap_fraction) *
112 (movement_area.height() - bounds.height()));
113 return gfx::Rect(movement_area.x(), offset, bounds.width(),
114 bounds.height());
115 }
116 }
117
ClearSnapFraction(WindowState * window_state)118 void PipPositioner::ClearSnapFraction(WindowState* window_state) {
119 return window_state->window()->ClearProperty(ash::kPipSnapFractionKey);
120 }
121
HasSnapFraction(WindowState * window_state)122 bool PipPositioner::HasSnapFraction(WindowState* window_state) {
123 return window_state->window()->GetProperty(ash::kPipSnapFractionKey) !=
124 nullptr;
125 }
126
SaveSnapFraction(WindowState * window_state,const gfx::Rect & bounds)127 void PipPositioner::SaveSnapFraction(WindowState* window_state,
128 const gfx::Rect& bounds) {
129 // Ensure that |bounds| is along one of the edges of the movement area.
130 // If the PIP window is drag-moved onto some system UI, it's possible that
131 // the PIP window is detached from any of them.
132 gfx::Rect snapped_bounds =
133 ash::CollisionDetectionUtils::AdjustToFitMovementAreaByGravity(
134 window_state->GetDisplay(), bounds);
135 gfx::Rect movement_area =
136 CollisionDetectionUtils::GetMovementArea(window_state->GetDisplay());
137 float width_fraction = (float)(snapped_bounds.x() - movement_area.x()) /
138 (movement_area.width() - snapped_bounds.width());
139 float height_fraction = (float)(snapped_bounds.y() - movement_area.y()) /
140 (movement_area.height() - snapped_bounds.height());
141 float snap_fraction;
142 if (snapped_bounds.y() == movement_area.y()) {
143 snap_fraction = width_fraction;
144 } else if (snapped_bounds.right() == movement_area.right()) {
145 snap_fraction = 1. + height_fraction;
146 } else if (snapped_bounds.bottom() == movement_area.bottom()) {
147 snap_fraction = 2. + (1. - width_fraction);
148 } else {
149 snap_fraction = 3. + (1. - height_fraction);
150 }
151 window_state->window()->SetProperty(ash::kPipSnapFractionKey,
152 new float(snap_fraction));
153 }
154
155 } // namespace ash
156