Commit 05cec5c6 authored by Eliot Courtney's avatar Eliot Courtney Committed by Commit Bot

Move the PIP window out of the way of the floating keyboard and tray.

We are looking for the minimum displacement of the PIP window that makes
it not intersect any system trays or the floating keyboard.

There are four cases for how the PIP window could move.
Case #1: Touches 0 obstacles. This corresponds to not moving.
Case #2: Touches 1 obstacle.
  The PIP window will be touching one edge of the obstacle.
Case #3: Touches 2 obstacles.
  The PIP window will be touching one horizontal and one vertical edge
  from two different obstacles.
Case #4: Touches more than 2 obstacles. This is handled in case #3.

To handle all these cases, we just need to check a few candidate points.
If the PIP window is displaced by one obstacle, the shortest
displacement is to move along the horizontal or vertical axis to be
directly adjacent to one of the edges of that obstacle.

If it is displaced by two obstacles, the shortest displacement is to
move to be directly adjacent to a horizontal and vertical edge - one
from each obstacle.

keyboard and unified system tray update, and the PIP window moved out of
the way of both the floating virtual keyboard and unified system tray.

Bug: 883118
Bug: 841886
Bug: b/115291749
Test: Added unittest
Test: Patched in code calling GetPositionAfterMovementAreaChange on
Change-Id: I2b500d5ba4a67fe2e309b809ec667248a19518ca
Reviewed-on: https://chromium-review.googlesource.com/c/1221427
Commit-Queue: Eliot Courtney <edcourtney@chromium.org>
Reviewed-by: default avatarMitsuru Oshima <oshima@chromium.org>
Cr-Commit-Position: refs/heads/master@{#604523}
parent d2469282
......@@ -6,13 +6,13 @@
#include <algorithm>
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/root_window_controller.h"
#include "ash/shelf/shelf.h"
#include "ash/shell.h"
#include "ash/system/status_area_widget.h"
#include "ash/system/unified/unified_system_tray.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "base/logging.h"
#include "ui/aura/window.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/keyboard/keyboard_controller.h"
......@@ -76,6 +76,112 @@ int GetGravityToClosestEdge(const gfx::Rect& bounds, const gfx::Rect& region) {
}
}
std::vector<gfx::Rect> CollectCollisionRects(const display::Display& display) {
std::vector<gfx::Rect> rects;
auto* root_window = Shell::GetRootWindowForDisplayId(display.id());
if (root_window) {
auto* settings_bubble_container =
root_window->GetChildById(kShellWindowId_SettingBubbleContainer);
for (auto* window : settings_bubble_container->children()) {
if (!window->IsVisible() && !window->GetTargetBounds().IsEmpty())
continue;
// Use the target bounds in case an animation is in progress.
rects.push_back(window->GetTargetBounds());
rects.back().Inset(-kPipWorkAreaInsetsDp, -kPipWorkAreaInsetsDp);
}
}
auto* keyboard_controller = keyboard::KeyboardController::Get();
if (keyboard_controller->IsEnabled() &&
keyboard_controller->GetActiveContainerType() ==
keyboard::ContainerType::FLOATING &&
keyboard_controller->GetRootWindow() == root_window &&
!keyboard_controller->visual_bounds_in_screen().IsEmpty()) {
rects.push_back(keyboard_controller->visual_bounds_in_screen());
rects.back().Inset(-kPipWorkAreaInsetsDp, -kPipWorkAreaInsetsDp);
}
return rects;
}
// Finds the candidate points |center| could be moved to. One of these points
// is guaranteed to be the minimum distance to move |center| to make it not
// intersect any of the rectangles in |collision_rects|.
std::vector<gfx::Point> CollectCandidatePoints(
const gfx::Point& center,
const std::vector<gfx::Rect>& collision_rects) {
std::vector<gfx::Point> candidate_points;
candidate_points.reserve(4 * collision_rects.size() * collision_rects.size() +
4 * collision_rects.size() + 1);
// There are four cases for how the PIP window will move.
// Case #1: Touches 0 obstacles. This corresponds to not moving.
// Case #2: Touches 1 obstacle.
// The PIP window will be touching one edge of the obstacle.
// Case #3: Touches 2 obstacles.
// The PIP window will be touching one horizontal and one vertical edge
// from two different obstacles.
// Case #4: Touches more than 2 obstacles. This is handled in case #3.
// Case #2.
// Rects include the left and top edges, so subtract 1 for those.
// Prioritize horizontal movement before vertical movement.
bool intersects = false;
for (const auto& rectA : collision_rects) {
intersects = intersects || rectA.Contains(center);
candidate_points.emplace_back(rectA.x() - 1, center.y());
candidate_points.emplace_back(rectA.right(), center.y());
candidate_points.emplace_back(center.x(), rectA.y() - 1);
candidate_points.emplace_back(center.x(), rectA.bottom());
}
// Case #1: Touching zero obstacles, so don't move the PIP window.
if (!intersects)
return {};
// Case #3: Add candidate points corresponding to each pair of horizontal
// and vertical edges.
for (const auto& rectA : collision_rects) {
for (const auto& rectB : collision_rects) {
// Continuing early here isn't necessary but makes fewer candidate points.
if (&rectA == &rectB)
continue;
candidate_points.emplace_back(rectA.x() - 1, rectB.y() - 1);
candidate_points.emplace_back(rectA.x() - 1, rectB.bottom());
candidate_points.emplace_back(rectA.right(), rectB.y() - 1);
candidate_points.emplace_back(rectA.right(), rectB.bottom());
}
}
return candidate_points;
}
// Finds the candidate point with the shortest distance to |center| that is
// inside |work_area| and does not intersect any gfx::Rect in |rects|.
gfx::Point ComputeBestCandidatePoint(const gfx::Point& center,
const gfx::Rect& work_area,
const std::vector<gfx::Rect>& rects) {
auto candidate_points = CollectCandidatePoints(center, rects);
int64_t best_dist = -1;
gfx::Point best_point = center;
for (const auto& point : candidate_points) {
if (!work_area.Contains(point))
continue;
bool viable = true;
for (const auto& rect : rects) {
if (rect.Contains(point)) {
viable = false;
break;
}
}
if (!viable)
continue;
int64_t dist = (point - center).LengthSquared();
if (dist < best_dist || best_dist == -1) {
best_dist = dist;
best_point = point;
}
}
return best_point;
}
} // namespace
gfx::Rect PipPositioner::GetMovementArea(const display::Display& display) {
......@@ -98,6 +204,7 @@ gfx::Rect PipPositioner::GetBoundsForDrag(const display::Display& display,
const gfx::Rect& bounds) {
gfx::Rect drag_bounds = bounds;
drag_bounds.AdjustToFit(GetMovementArea(display));
drag_bounds = AvoidObstacles(display, drag_bounds);
return drag_bounds;
}
......@@ -108,7 +215,8 @@ gfx::Rect PipPositioner::GetRestingPosition(const display::Display& display,
resting_bounds.AdjustToFit(area);
const int gravity = GetGravityToClosestEdge(resting_bounds, area);
return GetAdjustedBoundsByGravity(resting_bounds, area, gravity);
resting_bounds = GetAdjustedBoundsByGravity(resting_bounds, area, gravity);
return AvoidObstacles(display, resting_bounds);
}
gfx::Rect PipPositioner::GetDismissedPosition(const display::Display& display,
......@@ -139,4 +247,43 @@ gfx::Rect PipPositioner::GetPositionAfterMovementAreaChange(
return GetRestingPosition(window_state->GetDisplay(), bounds);
}
gfx::Rect PipPositioner::AvoidObstacles(const display::Display& display,
const gfx::Rect& bounds) {
gfx::Rect work_area = GetMovementArea(display);
auto rects = CollectCollisionRects(display);
// The worst case for this should be: floating keyboard + one system tray +
// the volume shifter, which is 3 windows.
DCHECK(rects.size() <= 15) << "PipPositioner::AvoidObstacles is N^3 and "
"should be optimized if there are a lot of "
"windows. Please see crrev.com/c/1221427 for a "
"description of an N^2 algorithm.";
return AvoidObstaclesInternal(work_area, rects, bounds);
}
gfx::Rect PipPositioner::AvoidObstaclesInternal(
const gfx::Rect& work_area,
const std::vector<gfx::Rect>& rects,
const gfx::Rect& bounds) {
gfx::Rect inset_work_area = work_area;
// For even sized bounds, there is no 'center' integer point, so we need
// to adjust the obstacles and work area to account for this.
inset_work_area.Inset(bounds.width() / 2, bounds.height() / 2,
(bounds.width() - 1) / 2, (bounds.height() - 1) / 2);
std::vector<gfx::Rect> inset_rects(rects);
for (auto& rect : inset_rects) {
// Reduce the collision resolution problem from rectangles-rectangle
// resolution to rectangles-point resolution, by expanding each obstacle
// by |bounds| size.
rect.Inset(-(bounds.width() - 1) / 2, -(bounds.height() - 1) / 2,
-bounds.width() / 2, -bounds.height() / 2);
}
gfx::Point moved_center = ComputeBestCandidatePoint(
bounds.CenterPoint(), inset_work_area, inset_rects);
gfx::Rect moved_bounds = bounds;
moved_bounds.Offset(moved_center - bounds.CenterPoint());
return moved_bounds;
}
} // namespace ash
......@@ -5,6 +5,8 @@
#ifndef ASH_WM_PIP_PIP_POSITIONER_H_
#define ASH_WM_PIP_PIP_POSITIONER_H_
#include <vector>
#include "ash/ash_export.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
......@@ -58,6 +60,20 @@ class ASH_EXPORT PipPositioner {
private:
friend class PipPositionerTest;
// Moves |bounds| such that it does not intersect with system ui areas, such
// as the unified system tray or the floating keyboard.
static gfx::Rect AvoidObstacles(const display::Display& display,
const gfx::Rect& bounds);
// Internal method for collision resolution. Returns a gfx::Rect with the
// same size as |bounds|. That rectangle will not intersect any of the
// rectangles in |rects| and will be completely inside |work_area|. If such a
// rectangle does not exist, returns |bounds|. Otherwise, it will be the
// closest such rectangle to |bounds|.
static gfx::Rect AvoidObstaclesInternal(const gfx::Rect& work_area,
const std::vector<gfx::Rect>& rects,
const gfx::Rect& bounds);
DISALLOW_COPY_AND_ASSIGN(PipPositioner);
};
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment