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