blob: 9361b3db67a08ff852241e509a00caa708389315 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/wm/pip/pip_positioner.h"
#include <algorithm>
#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 "ui/aura/window.h"
#include "ui/keyboard/keyboard_controller.h"
namespace ash {
namespace {
const int kPipWorkAreaInsetsDp = 8;
const float kPipDismissMovementProportion = 1.5f;
enum { GRAVITY_LEFT, GRAVITY_RIGHT, GRAVITY_TOP, GRAVITY_BOTTOM };
// Returns the result of adjusting |bounds| according to |gravity| inside
// |region|.
gfx::Rect GetAdjustedBoundsByGravity(const gfx::Rect& bounds,
const gfx::Rect& region,
int gravity) {
switch (gravity) {
case GRAVITY_LEFT:
return gfx::Rect(region.x(), bounds.y(), bounds.width(), bounds.height());
case GRAVITY_RIGHT:
return gfx::Rect(region.right() - bounds.width(), bounds.y(),
bounds.width(), bounds.height());
case GRAVITY_TOP:
return gfx::Rect(bounds.x(), region.y(), bounds.width(), bounds.height());
case GRAVITY_BOTTOM:
return gfx::Rect(bounds.x(), region.bottom() - bounds.height(),
bounds.width(), bounds.height());
default:
NOTREACHED();
}
return bounds;
}
// Returns the gravity that would make |bounds| fall to the closest edge of
// |region|. If |bounds| is outside of |region| then it will return the gravity
// as if |bounds| had fallen outside of |region|. See the below diagram for what
// the gravity regions look like for a point.
// \ TOP /
// \____/ R
// L |\ /| I
// E | \/ | G
// F | /\ | H
// T |/__\| T
// / \
// /BOTTOM
int GetGravityToClosestEdge(const gfx::Rect& bounds, const gfx::Rect& region) {
const int left_edge_dist = bounds.x() - region.x();
const int right_edge_dist = region.right() - bounds.right();
const int top_edge_dist = bounds.y() - region.y();
const int bottom_edge_dist = region.bottom() - bounds.bottom();
int minimum_edge_dist = std::min(left_edge_dist, right_edge_dist);
minimum_edge_dist = std::min(minimum_edge_dist, top_edge_dist);
minimum_edge_dist = std::min(minimum_edge_dist, bottom_edge_dist);
if (left_edge_dist == minimum_edge_dist) {
return GRAVITY_LEFT;
} else if (right_edge_dist == minimum_edge_dist) {
return GRAVITY_RIGHT;
} else if (top_edge_dist == minimum_edge_dist) {
return GRAVITY_TOP;
} else {
return GRAVITY_BOTTOM;
}
}
} // namespace
gfx::Rect PipPositioner::GetMovementArea(const display::Display& display) {
gfx::Rect work_area = display.work_area();
auto* keyboard_controller = keyboard::KeyboardController::Get();
// Include keyboard if it's not floating.
if (keyboard_controller->IsEnabled() &&
keyboard_controller->GetActiveContainerType() !=
keyboard::ContainerType::FLOATING) {
gfx::Rect keyboard_bounds = keyboard_controller->visual_bounds_in_screen();
work_area.Subtract(keyboard_bounds);
}
work_area.Inset(kPipWorkAreaInsetsDp, kPipWorkAreaInsetsDp);
return work_area;
}
gfx::Rect PipPositioner::GetBoundsForDrag(const display::Display& display,
const gfx::Rect& bounds) {
gfx::Rect drag_bounds = bounds;
drag_bounds.AdjustToFit(GetMovementArea(display));
return drag_bounds;
}
gfx::Rect PipPositioner::GetRestingPosition(const display::Display& display,
const gfx::Rect& bounds) {
gfx::Rect resting_bounds = bounds;
gfx::Rect area = GetMovementArea(display);
resting_bounds.AdjustToFit(area);
const int gravity = GetGravityToClosestEdge(resting_bounds, area);
return GetAdjustedBoundsByGravity(resting_bounds, area, gravity);
}
gfx::Rect PipPositioner::GetDismissedPosition(const display::Display& display,
const gfx::Rect& bounds) {
gfx::Rect work_area = GetMovementArea(display);
const int gravity = GetGravityToClosestEdge(bounds, work_area);
// Allow the bounds to move at most |kPipDismissMovementProportion| of the
// length of the bounds in the direction of movement.
gfx::Rect bounds_movement_area = bounds;
bounds_movement_area.Inset(-bounds.width() * kPipDismissMovementProportion,
-bounds.height() * kPipDismissMovementProportion);
gfx::Rect dismissed_bounds =
GetAdjustedBoundsByGravity(bounds, bounds_movement_area, gravity);
// If the PIP window isn't close enough to the edge of the screen, don't slide
// it out.
return work_area.Intersects(dismissed_bounds) ? bounds : dismissed_bounds;
}
gfx::Rect PipPositioner::GetPositionAfterMovementAreaChange(
wm::WindowState* window_state) {
// Restore to previous bounds if we have them. This lets us move the PIP
// window back to its original bounds after transient movement area changes,
// like the keyboard popping up and pushing the PIP window up.
const gfx::Rect bounds = window_state->HasRestoreBounds()
? window_state->GetRestoreBoundsInScreen()
: window_state->window()->GetBoundsInScreen();
return GetRestingPosition(window_state->GetDisplay(), bounds);
}
} // namespace ash