blob: 597acbf88ec6ddc74e2700ba3648a43c817c30c6 [file] [log] [blame]
// Copyright (c) 2012 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/workspace/multi_window_resize_controller.h"
#include "ash/screen_ash.h"
#include "ash/shell.h"
#include "ash/shell_window_ids.h"
#include "ash/wm/coordinate_conversion.h"
#include "ash/wm/window_animations.h"
#include "ash/wm/workspace/workspace_event_handler.h"
#include "ash/wm/workspace/workspace_window_resizer.h"
#include "grit/ash_resources.h"
#include "ui/aura/client/screen_position_client.h"
#include "ui/aura/root_window.h"
#include "ui/aura/window.h"
#include "ui/aura/window_delegate.h"
#include "ui/base/hit_test.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/screen.h"
#include "ui/views/corewm/compound_event_filter.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
using aura::Window;
namespace ash {
namespace internal {
namespace {
// Delay before showing.
const int kShowDelayMS = 400;
// Delay before hiding.
const int kHideDelayMS = 500;
// Padding from the bottom/right edge the resize widget is shown at.
const int kResizeWidgetPadding = 15;
bool ContainsX(Window* window, int x) {
return window->bounds().x() <= x && window->bounds().right() >= x;
}
bool ContainsY(Window* window, int y) {
return window->bounds().y() <= y && window->bounds().bottom() >= y;
}
bool Intersects(int x1, int max_1, int x2, int max_2) {
return x2 <= max_1 && max_2 > x1;
}
} // namespace
// 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:
explicit ResizeView(MultiWindowResizeController* controller,
Direction direction)
: controller_(controller),
direction_(direction),
image_(NULL) {
ResourceBundle& rb = ResourceBundle::GetSharedInstance();
int image_id =
direction == TOP_BOTTOM ? IDR_AURA_MULTI_WINDOW_RESIZE_H :
IDR_AURA_MULTI_WINDOW_RESIZE_V;
image_ = rb.GetImageNamed(image_id).ToImageSkia();
}
// views::View overrides:
virtual gfx::Size GetPreferredSize() OVERRIDE {
return gfx::Size(image_->width(), image_->height());
}
virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
canvas->DrawImageInt(*image_, 0, 0);
}
virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE {
gfx::Point location(event.location());
views::View::ConvertPointToScreen(this, &location);
controller_->StartResize(location);
return true;
}
virtual bool OnMouseDragged(const ui::MouseEvent& event) OVERRIDE {
gfx::Point location(event.location());
views::View::ConvertPointToScreen(this, &location);
controller_->Resize(location, event.flags());
return true;
}
virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE {
controller_->CompleteResize(event.flags());
}
virtual void OnMouseCaptureLost() OVERRIDE {
controller_->CancelResize();
}
virtual gfx::NativeCursor GetCursor(
const ui::MouseEvent& event) OVERRIDE {
int component = (direction_ == LEFT_RIGHT) ? HTRIGHT : HTBOTTOM;
return views::corewm::CompoundEventFilter::CursorForWindowComponent(
component);
}
private:
MultiWindowResizeController* controller_;
const Direction direction_;
const gfx::ImageSkia* image_;
DISALLOW_COPY_AND_ASSIGN(ResizeView);
};
// MouseWatcherHost implementation for MultiWindowResizeController. Forwards
// Contains() to MultiWindowResizeController.
class MultiWindowResizeController::ResizeMouseWatcherHost :
public views::MouseWatcherHost {
public:
ResizeMouseWatcherHost(MultiWindowResizeController* host) : host_(host) {}
// MouseWatcherHost overrides:
virtual bool Contains(const gfx::Point& point_in_screen,
MouseEventType type) OVERRIDE {
return host_->IsOverWindows(point_in_screen);
}
private:
MultiWindowResizeController* host_;
DISALLOW_COPY_AND_ASSIGN(ResizeMouseWatcherHost);
};
MultiWindowResizeController::ResizeWindows::ResizeWindows()
: window1(NULL),
window2(NULL),
direction(TOP_BOTTOM){
}
MultiWindowResizeController::ResizeWindows::~ResizeWindows() {
}
bool MultiWindowResizeController::ResizeWindows::Equals(
const ResizeWindows& other) const {
return window1 == other.window1 &&
window2 == other.window2 &&
direction == other.direction;
}
MultiWindowResizeController::MultiWindowResizeController() {
}
MultiWindowResizeController::~MultiWindowResizeController() {
window_resizer_.reset();
Hide();
}
void MultiWindowResizeController::Show(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()) {
if (windows_.Equals(windows))
return; // Over the same windows.
DelayedHide();
}
if (!windows.is_valid())
return;
Hide();
windows_ = windows;
windows_.window1->AddObserver(this);
windows_.window2->AddObserver(this);
show_location_in_parent_ = point_in_window;
Window::ConvertPointToTarget(
window, window->parent(), &show_location_in_parent_);
if (show_timer_.IsRunning())
return;
show_timer_.Start(
FROM_HERE, base::TimeDelta::FromMilliseconds(kShowDelayMS),
this, &MultiWindowResizeController::ShowIfValidMouseLocation);
}
void MultiWindowResizeController::Hide() {
hide_timer_.Stop();
if (window_resizer_)
return; // Ignore hides while actively resizing.
if (windows_.window1) {
windows_.window1->RemoveObserver(this);
windows_.window1 = NULL;
}
if (windows_.window2) {
windows_.window2->RemoveObserver(this);
windows_.window2 = NULL;
}
show_timer_.Stop();
if (!resize_widget_)
return;
for (size_t i = 0; i < windows_.other_windows.size(); ++i)
windows_.other_windows[i]->RemoveObserver(this);
mouse_watcher_.reset();
resize_widget_.reset();
windows_ = ResizeWindows();
}
void MultiWindowResizeController::MouseMovedOutOfHost() {
Hide();
}
void MultiWindowResizeController::OnWindowDestroying(
aura::Window* window) {
// Have to explicitly reset the WindowResizer, otherwise Hide() does nothing.
window_resizer_.reset();
Hide();
}
MultiWindowResizeController::ResizeWindows
MultiWindowResizeController::DetermineWindowsFromScreenPoint(
aura::Window* window) const {
gfx::Point mouse_location(
gfx::Screen::GetScreenFor(window)->GetCursorScreenPoint());
wm::ConvertPointFromScreen(window, &mouse_location);
const int component =
window->delegate()->GetNonClientComponent(mouse_location);
return DetermineWindows(window, component, mouse_location);
}
MultiWindowResizeController::ResizeWindows
MultiWindowResizeController::DetermineWindows(
Window* window,
int window_component,
const gfx::Point& point) const {
ResizeWindows result;
gfx::Point point_in_parent(point);
Window::ConvertPointToTarget(window, window->parent(), &point_in_parent);
switch (window_component) {
case HTRIGHT:
result.direction = LEFT_RIGHT;
result.window1 = window;
result.window2 = FindWindowByEdge(
window, HTLEFT, window->bounds().right(), point_in_parent.y());
break;
case HTLEFT:
result.direction = LEFT_RIGHT;
result.window1 = FindWindowByEdge(
window, HTRIGHT, window->bounds().x(), point_in_parent.y());
result.window2 = window;
break;
case HTTOP:
result.direction = TOP_BOTTOM;
result.window1 = FindWindowByEdge(
window, HTBOTTOM, point_in_parent.x(), window->bounds().y());
result.window2 = window;
break;
case HTBOTTOM:
result.direction = TOP_BOTTOM;
result.window1 = window;
result.window2 = FindWindowByEdge(
window, HTTOP, point_in_parent.x(), window->bounds().bottom());
break;
default:
break;
}
return result;
}
Window* MultiWindowResizeController::FindWindowByEdge(
Window* window_to_ignore,
int edge_want,
int x,
int y) const {
Window* parent = window_to_ignore->parent();
const Window::Windows& windows(parent->children());
for (Window::Windows::const_reverse_iterator i = windows.rbegin();
i != windows.rend(); ++i) {
Window* window = *i;
if (window == window_to_ignore || !window->IsVisible())
continue;
switch (edge_want) {
case HTLEFT:
if (ContainsY(window, y) && window->bounds().x() == x)
return window;
break;
case HTRIGHT:
if (ContainsY(window, y) && window->bounds().right() == x)
return window;
break;
case HTTOP:
if (ContainsX(window, x) && window->bounds().y() == y)
return window;
break;
case HTBOTTOM:
if (ContainsX(window, x) && window->bounds().bottom() == y)
return window;
break;
default:
NOTREACHED();
}
// Window doesn't contain the edge, but if window contains |point|
// it's obscuring any other window that could be at the location.
if (window->bounds().Contains(x, y))
return NULL;
}
return NULL;
}
aura::Window* MultiWindowResizeController::FindWindowTouching(
aura::Window* window,
Direction direction) const {
int right = window->bounds().right();
int bottom = window->bounds().bottom();
Window* parent = window->parent();
const Window::Windows& windows(parent->children());
for (Window::Windows::const_reverse_iterator i = windows.rbegin();
i != windows.rend(); ++i) {
Window* other = *i;
if (other == window || !other->IsVisible())
continue;
switch (direction) {
case TOP_BOTTOM:
if (other->bounds().y() == bottom &&
Intersects(other->bounds().x(), other->bounds().right(),
window->bounds().x(), window->bounds().right())) {
return other;
}
break;
case LEFT_RIGHT:
if (other->bounds().x() == right &&
Intersects(other->bounds().y(), other->bounds().bottom(),
window->bounds().y(), window->bounds().bottom())) {
return other;
}
break;
default:
NOTREACHED();
}
}
return NULL;
}
void MultiWindowResizeController::FindWindowsTouching(
aura::Window* start,
Direction direction,
std::vector<aura::Window*>* others) const {
while (start) {
start = FindWindowTouching(start, direction);
if (start)
others->push_back(start);
}
}
void MultiWindowResizeController::DelayedHide() {
if (hide_timer_.IsRunning())
return;
hide_timer_.Start(FROM_HERE,
base::TimeDelta::FromMilliseconds(kHideDelayMS),
this, &MultiWindowResizeController::Hide);
}
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();
resize_widget_.reset(new views::Widget);
views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.parent = Shell::GetContainer(
Shell::GetTargetRootWindow(),
internal::kShellWindowId_AlwaysOnTopContainer);
params.can_activate = false;
ResizeView* view = new ResizeView(this, windows_.direction);
resize_widget_->set_focus_on_creation(false);
resize_widget_->Init(params);
views::corewm::SetWindowVisibilityAnimationType(
resize_widget_->GetNativeWindow(),
views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE);
resize_widget_->GetNativeWindow()->SetName("MultiWindowResizeController");
resize_widget_->SetContentsView(view);
show_bounds_in_screen_ = ScreenAsh::ConvertRectToScreen(
windows_.window1->parent(),
CalculateResizeWidgetBounds(show_location_in_parent_));
resize_widget_->SetBounds(show_bounds_in_screen_);
resize_widget_->Show();
mouse_watcher_.reset(new views::MouseWatcher(
new ResizeMouseWatcherHost(this),
this));
mouse_watcher_->set_notify_on_exit_time(
base::TimeDelta::FromMilliseconds(kHideDelayMS));
mouse_watcher_->Start();
}
bool MultiWindowResizeController::IsShowing() const {
return resize_widget_.get() || show_timer_.IsRunning();
}
void MultiWindowResizeController::StartResize(
const gfx::Point& location_in_screen) {
DCHECK(!window_resizer_.get());
DCHECK(windows_.is_valid());
hide_timer_.Stop();
gfx::Point location_in_parent(location_in_screen);
aura::client::GetScreenPositionClient(windows_.window2->GetRootWindow())->
ConvertPointFromScreen(windows_.window2->parent(), &location_in_parent);
std::vector<aura::Window*> windows;
windows.push_back(windows_.window2);
DCHECK(windows_.other_windows.empty());
FindWindowsTouching(windows_.window2, windows_.direction,
&windows_.other_windows);
for (size_t i = 0; i < windows_.other_windows.size(); ++i) {
windows_.other_windows[i]->AddObserver(this);
windows.push_back(windows_.other_windows[i]);
}
int component = windows_.direction == LEFT_RIGHT ? HTRIGHT : HTBOTTOM;
window_resizer_.reset(WorkspaceWindowResizer::Create(
windows_.window1,
location_in_parent,
component,
aura::client::WINDOW_MOVE_SOURCE_MOUSE,
windows));
}
void MultiWindowResizeController::Resize(const gfx::Point& location_in_screen,
int event_flags) {
gfx::Point location_in_parent(location_in_screen);
aura::client::GetScreenPositionClient(windows_.window1->GetRootWindow())->
ConvertPointFromScreen(windows_.window1->parent(), &location_in_parent);
window_resizer_->Drag(location_in_parent, event_flags);
gfx::Rect bounds = ScreenAsh::ConvertRectToScreen(
windows_.window1->parent(),
CalculateResizeWidgetBounds(location_in_parent));
if (windows_.direction == LEFT_RIGHT)
bounds.set_y(show_bounds_in_screen_.y());
else
bounds.set_x(show_bounds_in_screen_.x());
resize_widget_->SetBounds(bounds);
}
void MultiWindowResizeController::CompleteResize(int event_flags) {
window_resizer_->CompleteDrag(event_flags);
window_resizer_.reset();
// Mouse may still be over resizer, if not hide.
gfx::Point screen_loc = Shell::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 (size_t i = 0; i < windows_.other_windows.size(); ++i)
windows_.other_windows[i]->RemoveObserver(this);
windows_.other_windows.clear();
}
}
void MultiWindowResizeController::CancelResize() {
if (!window_resizer_)
return; // Happens if window was destroyed and we nuked the WindowResizer.
window_resizer_->RevertDrag();
window_resizer_.reset();
Hide();
}
gfx::Rect MultiWindowResizeController::CalculateResizeWidgetBounds(
const gfx::Point& location_in_parent) const {
gfx::Size pref = resize_widget_->GetContentsView()->GetPreferredSize();
int x = 0, y = 0;
if (windows_.direction == LEFT_RIGHT) {
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::IsOverWindows(
const gfx::Point& location_in_screen) const {
if (window_resizer_)
return true; // Ignore hides while actively resizing.
if (resize_widget_->GetWindowBoundsInScreen().Contains(location_in_screen))
return true;
int hit1, hit2;
if (windows_.direction == TOP_BOTTOM) {
hit1 = HTBOTTOM;
hit2 = HTTOP;
} else {
hit1 = HTRIGHT;
hit2 = HTLEFT;
}
return IsOverWindow(windows_.window1, location_in_screen, hit1) ||
IsOverWindow(windows_.window2, location_in_screen, hit2);
}
bool MultiWindowResizeController::IsOverWindow(
aura::Window* window,
const gfx::Point& location_in_screen,
int component) const {
if (!window->delegate())
return false;
gfx::Point window_loc(location_in_screen);
aura::Window::ConvertPointToTarget(
window->GetRootWindow(), window, &window_loc);
return window->HitTest(window_loc) &&
window->delegate()->GetNonClientComponent(window_loc) == component;
}
} // namespace internal
} // namespace ash