summaryrefslogtreecommitdiff
path: root/chromium/ash/wm/workspace/multi_window_resize_controller.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/ash/wm/workspace/multi_window_resize_controller.cc')
-rw-r--r--chromium/ash/wm/workspace/multi_window_resize_controller.cc545
1 files changed, 545 insertions, 0 deletions
diff --git a/chromium/ash/wm/workspace/multi_window_resize_controller.cc b/chromium/ash/wm/workspace/multi_window_resize_controller.cc
new file mode 100644
index 00000000000..597acbf88ec
--- /dev/null
+++ b/chromium/ash/wm/workspace/multi_window_resize_controller.cc
@@ -0,0 +1,545 @@
+// 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