// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/wm/workspace/multi_window_resize_controller.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/root_window_controller.h"
#include "ash/shell.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/resize_shadow_controller.h"
#include "ash/wm/splitview/split_view_utils.h"
#include "ash/wm/window_util.h"
#include "ash/wm/wm_metrics.h"
#include "ash/wm/workspace/workspace_window_resizer.h"
#include "base/containers/adapters.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/user_metrics.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window_delegate.h"
#include "ui/base/hit_test.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/display/screen.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/background.h"
#include "ui/wm/core/compound_event_filter.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/core/window_animations.h"
namespace ash {
namespace {
// Delay before hiding the `resize_widget_`.
constexpr base::TimeDelta kHideDelay = base::Milliseconds(500);
// Padding from the bottom/right edge the resize widget is shown at.
const int kResizeWidgetPadding = 15;
// The size of the resize widget.
constexpr int kLongSide = 64;
constexpr int kShortSide = 52;
// Returns the widget init params needed to create the resize widget.
views::Widget::InitParams CreateWidgetParams(aura::Window* parent_window,
const std::string& widget_name) {
views::Widget::InitParams params(
views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
views::Widget::InitParams::TYPE_POPUP);
params.parent = parent_window;
params.name = widget_name;
params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
return params;
}
gfx::PointF ConvertPointFromScreen(aura::Window* window,
const gfx::PointF& point) {
gfx::PointF result(point);
::wm::ConvertPointFromScreen(window, &result);
return result;
}
gfx::Point ConvertPointToTarget(aura::Window* source,
aura::Window* target,
const gfx::Point& point) {
gfx::Point result(point);
aura::Window::ConvertPointToTarget(source, target, &result);
return result;
}
gfx::Rect ConvertRectToScreen(aura::Window* source, const gfx::Rect& rect) {
gfx::Rect result(rect);
::wm::ConvertRectToScreen(source, &result);
return result;
}
bool ContainsX(aura::Window* window, int x) {
return x >= 0 && x <= window->bounds().width();
}
bool ContainsScreenX(aura::Window* window, int x_in_screen) {
gfx::PointF window_loc =
ConvertPointFromScreen(window, gfx::PointF(x_in_screen, 0));
return ContainsX(window, window_loc.x());
}
bool ContainsY(aura::Window* window, int y) {
return y >= 0 && y <= window->bounds().height();
}
bool ContainsScreenY(aura::Window* window, int y_in_screen) {
gfx::PointF window_loc =
ConvertPointFromScreen(window, gfx::PointF(0, y_in_screen));
return ContainsY(window, window_loc.y());
}
// Returns true if `p` is on the edge `edge_want` of `window`.
bool PointOnWindowEdge(aura::Window* window,
int edge_want,
const gfx::Point& p) {
switch (edge_want) {
case HTLEFT:
return ContainsY(window, p.y()) && p.x() == 0;
case HTRIGHT:
return ContainsY(window, p.y()) && p.x() == window->bounds().width();
case HTTOP:
return ContainsX(window, p.x()) && p.y() == 0;
case HTBOTTOM:
return ContainsX(window, p.x()) && p.y() == window->bounds().height();
default:
NOTREACHED();
}
}
bool Intersects(int x1, int max_1, int x2, int max_2) {
return x2 <= max_1 && max_2 > x1;
}
} // namespace
// -----------------------------------------------------------------------------
// ResizeView:
// View contained in the widget. Passes along mouse events to the
// MultiWindowResizeController so that it can start/stop the resize loop.
class MultiWindowResizeController::ResizeView : public views::View {
public:
ResizeView(MultiWindowResizeController* controller, Direction direction)
: controller_(controller), direction_(direction) {}
ResizeView(const ResizeView&) = delete;
ResizeView& operator=(const ResizeView&) = delete;
~ResizeView() override = default;
// views::View:
gfx::Size CalculatePreferredSize(
const views::SizeBounds& available_size) const override {
const bool vert = direction_ == Direction::kLeftRight;
return gfx::Size(vert ? kLongSide : kShortSide,
vert ? kShortSide : kLongSide);
}
void OnPaint(gfx::Canvas* canvas) override {
cc::PaintFlags flags;
flags.setColor(
GetColorProvider()->GetColor(cros_tokens::kCrosSysSystemBaseElevated));
flags.setStyle(cc::PaintFlags::kFill_Style);
flags.setAntiAlias(true);
canvas->DrawPath(GeneratePath(GetLocalBounds()), flags);
// Paint the chevron icons.
constexpr int kIconSize = 20;
constexpr int kHalfLong = kLongSide / 2;
// Paint the left / up chevron icon.
canvas->Save();
int long_offset = (kHalfLong - kIconSize) / 2;
int short_offset = (kShortSide - kIconSize) / 2;
canvas->Translate(direction_ == Direction::kLeftRight
? gfx::Vector2d(long_offset, short_offset)
: gfx::Vector2d(short_offset, long_offset));
gfx::PaintVectorIcon(
canvas,
direction_ == Direction::kLeftRight ? kOverflowShelfLeftIcon
: kChevronUpSmallIcon,
kIconSize,
GetColorProvider()->GetColor(cros_tokens::kCrosSysOnSurface));
canvas->Restore();
// Paint the right / down chevron icon.
canvas->Save();
long_offset = kHalfLong + (kHalfLong - kIconSize) / 2;
canvas->Translate(direction_ == Direction::kLeftRight
? gfx::Vector2d(long_offset, short_offset)
: gfx::Vector2d(short_offset, long_offset));
gfx::PaintVectorIcon(
canvas,
direction_ == Direction::kLeftRight ? kOverflowShelfRightIcon
: kChevronDownSmallIcon,
kIconSize,
GetColorProvider()->GetColor(cros_tokens::kCrosSysOnSurface));
canvas->Restore();
}
bool OnMousePressed(const ui::MouseEvent& event) override {
gfx::Point location(event.location());
views::View::ConvertPointToScreen(this, &location);
controller_->StartResize(gfx::PointF(location));
return true;
}
bool OnMouseDragged(const ui::MouseEvent& event) override {
gfx::Point location(event.location());
views::View::ConvertPointToScreen(this, &location);
controller_->Resize(gfx::PointF(location), event.flags());
return true;
}
void OnMouseReleased(const ui::MouseEvent& event) override {
controller_->CompleteResize();
}
void OnMouseCaptureLost() override { controller_->CancelResize(); }
gfx::NativeCursor GetCursor(const ui::MouseEvent& event) override {
int component = (direction_ == Direction::kLeftRight) ? HTRIGHT : HTBOTTOM;
return ::wm::CompoundEventFilter::CursorForWindowComponent(component);
}
private:
raw_ptr<MultiWindowResizeController> controller_;
const Direction direction_;
SkPath GeneratePath(const gfx::Rect& bounds) {
// /\
// ---- ----
// / \
// \ /
// ---- ----
// \/
//
// Generate the path for the shape above when `direction_` is
// `Direction::kLeftRight`. If the `direction_` is `Direction::kTopBottom`,
// generate the path for the shape above with 90 degree rotated.
static constexpr int kLargeCurveRadius = 16;
static constexpr int kSmallCurveRadius = 10;
// The resize shape is symmetric horizontally and vertically, hence only
// need to manually generate the path for the quarter and then flip twice;
const gfx::RectF quarter_bounds(bounds.x(), bounds.y(), bounds.width() / 2,
bounds.height() / 2);
SkPath path;
if (direction_ == Direction::kLeftRight) {
// /|
// ---- |
// / ____ |
// Generate the path for the quarter of the resize shape which looks like
// the shape above, starting from left bottom to the right top and then
// back to the left bottom.
path.moveTo(quarter_bounds.x(), quarter_bounds.bottom());
path.arcTo(
quarter_bounds.x(), quarter_bounds.bottom() - kLargeCurveRadius,
quarter_bounds.x() + kLargeCurveRadius,
quarter_bounds.bottom() - kLargeCurveRadius, kLargeCurveRadius);
path.lineTo(quarter_bounds.right() - kSmallCurveRadius,
quarter_bounds.bottom() - kLargeCurveRadius);
path.arcTo(quarter_bounds.right(),
quarter_bounds.bottom() - kLargeCurveRadius,
quarter_bounds.right(), quarter_bounds.y(), kSmallCurveRadius);
path.lineTo(quarter_bounds.right(), quarter_bounds.bottom());
} else {
// Similar to the way when `direction_` is `Direction::kLeftRight`,
// starting from the right top to the left bottom and then back to the
// right top.
path.moveTo(quarter_bounds.right(), quarter_bounds.y());
path.arcTo(quarter_bounds.right() - kLargeCurveRadius, quarter_bounds.y(),
quarter_bounds.right() - kLargeCurveRadius,
quarter_bounds.y() + kLargeCurveRadius, kLargeCurveRadius);
path.lineTo(quarter_bounds.right() - kLargeCurveRadius,
quarter_bounds.bottom() - kSmallCurveRadius);
path.arcTo(quarter_bounds.right() - kLargeCurveRadius,
quarter_bounds.bottom(), quarter_bounds.x(),
quarter_bounds.bottom(), kSmallCurveRadius);
path.lineTo(quarter_bounds.right(), quarter_bounds.bottom());
}
path.close();
// Flip vertically and horizontally and vertically to get the full path.
SkMatrix flip;
flip.setScale(1, -1, quarter_bounds.width(), quarter_bounds.height());
path.addPath(path, flip);
flip.setScale(-1, 1, quarter_bounds.width(), quarter_bounds.height());
path.addPath(path, flip);
return path;
}
};
// -----------------------------------------------------------------------------
// ResizeMouseWatcherHost:
// MouseWatcherHost implementation for MultiWindowResizeController. Forwards
// Contains() to MultiWindowResizeController.
class MultiWindowResizeController::ResizeMouseWatcherHost
: public views::MouseWatcherHost {
public:
explicit ResizeMouseWatcherHost(MultiWindowResizeController* host)
: host_(host) {}
ResizeMouseWatcherHost(const ResizeMouseWatcherHost&) = delete;
ResizeMouseWatcherHost& operator=(const ResizeMouseWatcherHost&) = delete;
~ResizeMouseWatcherHost() override = default;
// views::MouseWatcherHost:
bool Contains(const gfx::Point& point_in_screen, EventType type) override {
return (type == EventType::kPress)
? host_->IsOverResizeWidget(point_in_screen)
: host_->IsOverWindows(point_in_screen);
}
private:
raw_ptr<MultiWindowResizeController> host_;
};
MultiWindowResizeController::ResizeWindows::ResizeWindows()
: direction(Direction::kTopBottom) {}
MultiWindowResizeController::ResizeWindows::ResizeWindows(
const ResizeWindows& other) = default;
MultiWindowResizeController::ResizeWindows::~ResizeWindows() = default;
bool MultiWindowResizeController::ResizeWindows::Equals(
const ResizeWindows& other) const {
return window1 == other.window1 && window2 == other.window2 &&
direction == other.direction;
}
// -----------------------------------------------------------------------------
// MultiWindowResizeController:
MultiWindowResizeController::MultiWindowResizeController() {
Shell::Get()->overview_controller()->AddObserver(this);
}
MultiWindowResizeController::~MultiWindowResizeController() {
if (Shell::Get()->overview_controller()) {
Shell::Get()->overview_controller()->RemoveObserver(this);
}
ResetResizer();
}
void MultiWindowResizeController::Show(aura::Window* window,
int component,
const gfx::Point& point_in_window) {
// When the resize widget is showing we ignore Show() requests. Instead we
// only care about mouse movements from MouseWatcher. This is necessary as
// WorkspaceEventHandler only sees mouse movements over the windows, not all
// windows or over the desktop.
if (resize_widget_)
return;
ResizeWindows windows(DetermineWindows(window, component, point_in_window));
if (IsShowing() && windows_.Equals(windows))
return;
Hide();
if (!windows.is_valid()) {
windows_ = ResizeWindows();
return;
}
windows_ = windows;
StartObserving(windows_.window1);
StartObserving(windows_.window2);
show_location_in_parent_ =
ConvertPointToTarget(window, window->parent(), point_in_window);
show_timer_.Start(FROM_HERE, kShowDelay, this,
&MultiWindowResizeController::ShowIfValidMouseLocation);
}
void MultiWindowResizeController::MouseMovedOutOfHost() {
Hide();
}
void MultiWindowResizeController::OnWindowPropertyChanged(aura::Window* window,
const void* key,
intptr_t old) {
// If the window is now non-resizeable, make sure the resizer is not showing.
if ((window->GetProperty(aura::client::kResizeBehaviorKey) &
aura::client::kResizeBehaviorCanResize) == 0)
ResetResizer();
}
void MultiWindowResizeController::OnWindowVisibilityChanged(
aura::Window* window,
bool visible) {
// OnWindowVisibilityChanged() is fired not only for observed windows but
// also its descendants (and ancestors), but multi-window resizing should keep
// running even if the resized window’s child window gets hidden. So here, we
// only handles events for windows being resized (i.e. observed windows).
if (!IsObserving(window))
return;
if (!visible)
ResetResizer();
}
void MultiWindowResizeController::OnWindowDestroying(aura::Window* window) {
ResetResizer();
}
void MultiWindowResizeController::OnPostWindowStateTypeChange(
WindowState* window_state,
chromeos::WindowStateType old_type) {
if (window_state->IsMaximized() || window_state->IsFullscreen() ||
window_state->IsMinimized()) {
ResetResizer();
}
}
void MultiWindowResizeController::OnOverviewModeStarting() {
// Hide resizing UI when entering overview.
Shell::Get()->resize_shadow_controller()->HideAllShadows();
ResetResizer();
}
void MultiWindowResizeController::OnOverviewModeEndingAnimationComplete(
bool canceled) {
if (canceled) {
return;
}
// Show shadow for the resizer after exiting overview.
Shell::Get()->resize_shadow_controller()->TryShowAllShadows();
}
MultiWindowResizeController::ResizeWindows
MultiWindowResizeController::DetermineWindowsFromScreenPoint(
aura::Window* window) const {
gfx::Point mouse_location(
display::Screen::GetScreen()->GetCursorScreenPoint());
wm::ConvertPointFromScreen(window, &mouse_location);
const int component =
window_util::GetNonClientComponent(window, mouse_location);
return DetermineWindows(window, component, mouse_location);
}
void MultiWindowResizeController::CreateMouseWatcher() {
mouse_watcher_ = std::make_unique<views::MouseWatcher>(
std::make_unique<ResizeMouseWatcherHost>(this), this);
mouse_watcher_->set_notify_on_exit_time(kHideDelay);
DCHECK(resize_widget_);
mouse_watcher_->Start(resize_widget_->GetNativeWindow());
}
MultiWindowResizeController::ResizeWindows
MultiWindowResizeController::DetermineWindows(aura::Window* window,
int window_component,
const gfx::Point& point) const {
ResizeWindows result;
// Check if the window is non-resizeable.
if ((window->GetProperty(aura::client::kResizeBehaviorKey) &
aura::client::kResizeBehaviorCanResize) == 0) {
return result;
}
gfx::Point point_in_parent =
ConvertPointToTarget(window, window->parent(), point);
switch (window_component) {
case HTRIGHT:
result.direction = Direction::kLeftRight;
result.window1 = window;
result.window2 = FindWindowByEdge(
window, HTLEFT, window->bounds().right(), point_in_parent.y());
break;
case HTLEFT:
result.direction = Direction::kLeftRight;
result.window1 = FindWindowByEdge(window, HTRIGHT, window->bounds().x(),
point_in_parent.y());
result.window2 = window;
break;
case HTTOP:
result.direction = Direction::kTopBottom;
result.window1 = FindWindowByEdge(window, HTBOTTOM, point_in_parent.x(),
window->bounds().y());
result.window2 = window;
break;
case HTBOTTOM:
result.direction = Direction::kTopBottom;
result.window1 = window;
result.window2 = FindWindowByEdge(window, HTTOP, point_in_parent.x(),
window->bounds().bottom());
break;
default:
break;
}
return result;
}
aura::Window* MultiWindowResizeController::FindWindowByEdge(
aura::Window* window_to_ignore,
int edge_want,
int x_in_parent,
int y_in_parent) const {
aura::Window* parent = window_to_ignore->parent();
const aura::Window::Windows& windows = parent->children();
for (aura::Window* window : base::Reversed(windows)) {
if (window == window_to_ignore || !window->IsVisible())
continue;
// Ignore windows without a non-client area.
if (!window->delegate())
continue;
// Return the window if it is resizeable and the wanted edge has the point.
if ((window->GetProperty(aura::client::kResizeBehaviorKey) &
aura::client::kResizeBehaviorCanResize) != 0 &&
PointOnWindowEdge(
window, edge_want,
ConvertPointToTarget(parent, window,
gfx::Point(x_in_parent, y_in_parent)))) {
return window;
}
// Having determined that the window is not a suitable return value, if it
// contains the point, then it is obscuring that point on any remaining
// window that also contains the point.
if (window->bounds().Contains(x_in_parent, y_in_parent))
return nullptr;
}
return nullptr;
}
aura::Window* MultiWindowResizeController::FindWindowTouching(
aura::Window* window,
Direction direction) const {
int right = window->bounds().right();
int bottom = window->bounds().bottom();
aura::Window* parent = window->parent();
const aura::Window::Windows& windows = parent->children();
for (aura::Window* other : base::Reversed(windows)) {
if (other == window || !other->IsVisible())
continue;
switch (direction) {
case Direction::kTopBottom:
if (other->bounds().y() == bottom &&
Intersects(other->bounds().x(), other->bounds().right(),
window->bounds().x(), window->bounds().right())) {
return other;
}
break;
case Direction::kLeftRight:
if (other->bounds().x() == right &&
Intersects(other->bounds().y(), other->bounds().bottom(),
window->bounds().y(), window->bounds().bottom())) {
return other;
}
break;
default:
NOTREACHED();
}
}
return nullptr;
}
void MultiWindowResizeController::FindWindowsTouching(
aura::Window* start,
Direction direction,
aura::Window::Windows* others) const {
while (start) {
start = FindWindowTouching(start, direction);
if (start)
others->push_back(start);
}
}
void MultiWindowResizeController::StartObserving(aura::Window* window) {
window_observations_.AddObservation(window);
window_state_observations_.AddObservation(WindowState::Get(window));
}
void MultiWindowResizeController::StopObserving(aura::Window* window) {
window_observations_.RemoveObservation(window);
window_state_observations_.RemoveObservation(WindowState::Get(window));
}
bool MultiWindowResizeController::IsObserving(aura::Window* window) const {
return window_observations_.IsObservingSource(window);
}
void MultiWindowResizeController::ShowIfValidMouseLocation() {
if (DetermineWindowsFromScreenPoint(windows_.window1).Equals(windows_) ||
DetermineWindowsFromScreenPoint(windows_.window2).Equals(windows_)) {
ShowNow();
} else {
Hide();
}
}
void MultiWindowResizeController::ShowNow() {
DCHECK(!resize_widget_.get());
DCHECK(windows_.is_valid());
show_timer_.Stop();
aura::Window* window1 = windows_.window1;
aura::Window* window2 = windows_.window2;
resize_widget_ = std::make_unique<views::Widget>();
resize_widget_->set_focus_on_creation(false);
aura::Window* parent_window = window1->GetRootWindow()->GetChildById(
kShellWindowId_AlwaysOnTopContainer);
resize_widget_->Init(CreateWidgetParams(
parent_window, /*widget_name=*/"MultiWindowResizeController"));
::wm::SetWindowVisibilityAnimationType(
resize_widget_->GetNativeWindow(),
::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE);
resize_widget_->SetContentsView(
std::make_unique<ResizeView>(this, windows_.direction));
gfx::Rect resize_widget_bounds =
CalculateResizeWidgetBounds(gfx::PointF(show_location_in_parent_));
resize_widget_show_bounds_in_screen_ =
ConvertRectToScreen(window1->parent(), resize_widget_bounds);
resize_widget_->SetBounds(resize_widget_show_bounds_in_screen_);
resize_widget_->Show();
base::RecordAction(base::UserMetricsAction(kMultiWindowResizerShow));
base::UmaHistogramBoolean(kMultiWindowResizerShowHistogramName, true);
if (WindowState::Get(window1)->IsSnapped() &&
WindowState::Get(window2)->IsSnapped()) {
base::RecordAction(
base::UserMetricsAction(kMultiWindowResizerShowTwoWindowsSnapped));
base::UmaHistogramBoolean(
kMultiWindowResizerShowTwoWindowsSnappedHistogramName, true);
}
CreateMouseWatcher();
}
bool MultiWindowResizeController::IsShowing() const {
return resize_widget_.get() || show_timer_.IsRunning();
}
void MultiWindowResizeController::Hide() {
// Ignore `Hide` while actively resizing.
if (window_resizer_) {
return;
}
if (windows_.window1) {
StopObserving(windows_.window1);
windows_.window1 = nullptr;
}
if (windows_.window2) {
StopObserving(windows_.window2);
windows_.window2 = nullptr;
}
show_timer_.Stop();
if (!resize_widget_) {
return;
}
for (aura::Window* window : windows_.other_windows) {
StopObserving(window);
}
mouse_watcher_.reset();
resize_widget_.reset();
windows_ = ResizeWindows();
}
void MultiWindowResizeController::ResetResizer() {
// Have to explicitly reset the WindowResizer, otherwise Hide() does nothing.
window_resizer_.reset();
Hide();
}
void MultiWindowResizeController::StartResize(
const gfx::PointF& location_in_screen) {
DCHECK(!window_resizer_.get());
DCHECK(windows_.is_valid());
gfx::PointF location_in_parent =
ConvertPointFromScreen(windows_.window2->parent(), location_in_screen);
aura::Window::Windows windows;
windows.push_back(windows_.window2.get());
DCHECK(windows_.other_windows.empty());
FindWindowsTouching(windows_.window2, windows_.direction,
&windows_.other_windows);
for (aura::Window* other_window : windows_.other_windows) {
StartObserving(other_window);
windows.push_back(other_window);
}
int component =
windows_.direction == Direction::kLeftRight ? HTRIGHT : HTBOTTOM;
WindowState* window_state = WindowState::Get(windows_.window1);
window_state->CreateDragDetails(location_in_parent, component,
::wm::WINDOW_MOVE_SOURCE_MOUSE);
window_resizer_ = WorkspaceWindowResizer::Create(window_state, windows);
// Do not hide the resize widget while a drag is active.
mouse_watcher_.reset();
base::RecordAction(base::UserMetricsAction(kMultiWindowResizerClick));
base::UmaHistogramBoolean(kMultiWindowResizerClickHistogramName, true);
if (WindowState::Get(windows_.window1)->IsSnapped() &&
WindowState::Get(windows_.window2)->IsSnapped()) {
base::RecordAction(
base::UserMetricsAction(kMultiWindowResizerClickTwoWindowsSnapped));
base::UmaHistogramBoolean(
kMultiWindowResizerClickTwoWindowsSnappedHistogramName, true);
}
}
void MultiWindowResizeController::Resize(const gfx::PointF& location_in_screen,
int event_flags) {
gfx::PointF location_in_parent =
ConvertPointFromScreen(windows_.window1->parent(), location_in_screen);
window_resizer_->Drag(location_in_parent, event_flags);
gfx::Rect bounds =
ConvertRectToScreen(windows_.window1->parent(),
CalculateResizeWidgetBounds(location_in_parent));
if (windows_.direction == Direction::kLeftRight) {
bounds.set_y(resize_widget_show_bounds_in_screen_.y());
} else {
bounds.set_x(resize_widget_show_bounds_in_screen_.x());
}
resize_widget_->SetBounds(bounds);
}
void MultiWindowResizeController::CompleteResize() {
window_resizer_->CompleteDrag();
WindowState::Get(window_resizer_->GetTarget())->DeleteDragDetails();
window_resizer_.reset();
// Mouse may still be over resizer, if not hide.
gfx::Point screen_loc = display::Screen::GetScreen()->GetCursorScreenPoint();
if (!resize_widget_->GetWindowBoundsInScreen().Contains(screen_loc)) {
Hide();
} else {
// If the mouse is over the resizer we need to remove observers on any of
// the `other_windows`. If we start another resize we'll recalculate the
// `other_windows` and invoke AddObserver() as necessary.
for (aura::Window* other_window : windows_.other_windows) {
StopObserving(other_window);
}
windows_.other_windows.clear();
CreateMouseWatcher();
}
}
void MultiWindowResizeController::CancelResize() {
// Happens if window was destroyed and we nuked the WindowResizer.
if (!window_resizer_) {
return;
}
window_resizer_->RevertDrag();
WindowState::Get(window_resizer_->GetTarget())->DeleteDragDetails();
ResetResizer();
}
gfx::Rect MultiWindowResizeController::CalculateResizeWidgetBounds(
const gfx::PointF& location_in_parent) const {
gfx::Size pref = resize_widget_->GetContentsView()->GetPreferredSize();
int x = 0, y = 0;
if (windows_.direction == Direction::kLeftRight) {
x = windows_.window1->bounds().right() - pref.width() / 2;
y = location_in_parent.y() + kResizeWidgetPadding;
if (y + pref.height() / 2 > windows_.window1->bounds().bottom() &&
y + pref.height() / 2 > windows_.window2->bounds().bottom()) {
y = location_in_parent.y() - kResizeWidgetPadding - pref.height();
}
} else {
x = location_in_parent.x() + kResizeWidgetPadding;
if (x + pref.height() / 2 > windows_.window1->bounds().right() &&
x + pref.height() / 2 > windows_.window2->bounds().right()) {
x = location_in_parent.x() - kResizeWidgetPadding - pref.width();
}
y = windows_.window1->bounds().bottom() - pref.height() / 2;
}
return gfx::Rect(x, y, pref.width(), pref.height());
}
bool MultiWindowResizeController::IsOverResizeWidget(
const gfx::Point& location_in_screen) const {
return resize_widget_->GetWindowBoundsInScreen().Contains(location_in_screen);
}
bool MultiWindowResizeController::IsOverWindows(
const gfx::Point& location_in_screen) const {
if (IsOverResizeWidget(location_in_screen)) {
return true;
}
if (windows_.direction == Direction::kTopBottom) {
if (!ContainsScreenX(windows_.window1, location_in_screen.x()) ||
!ContainsScreenX(windows_.window2, location_in_screen.x())) {
return false;
}
} else {
if (!ContainsScreenY(windows_.window1, location_in_screen.y()) ||
!ContainsScreenY(windows_.window2, location_in_screen.y())) {
return false;
}
}
// Check whether `location_in_screen` is in the event target's resize region.
// This is tricky because a window's resize region can extend outside a
// window's bounds.
aura::Window* target = RootWindowController::ForWindow(windows_.window1)
->FindEventTarget(location_in_screen);
if (target == windows_.window1) {
return IsOverComponent(
windows_.window1, location_in_screen,
windows_.direction == Direction::kTopBottom ? HTBOTTOM : HTRIGHT);
}
if (target == windows_.window2) {
return IsOverComponent(
windows_.window2, location_in_screen,
windows_.direction == Direction::kTopBottom ? HTTOP : HTLEFT);
}
return false;
}
bool MultiWindowResizeController::IsOverComponent(
aura::Window* window,
const gfx::Point& location_in_screen,
int component) const {
gfx::Point window_loc(location_in_screen);
::wm::ConvertPointFromScreen(window, &window_loc);
return window_util::GetNonClientComponent(window, window_loc) == component;
}
} // namespace ash