summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-03-06 18:45:17 +0000
committersky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-03-06 18:45:17 +0000
commitef4bb745462727517e924002b4b0cdf8e95d441f (patch)
tree280efcf07fdf5088ce3ef4783da6b98a8deb78c2
parentd06d97c961bdd759c3b77c106b1a42e9aa82756e (diff)
downloadchromium_src-ef4bb745462727517e924002b4b0cdf8e95d441f.zip
chromium_src-ef4bb745462727517e924002b4b0cdf8e95d441f.tar.gz
chromium_src-ef4bb745462727517e924002b4b0cdf8e95d441f.tar.bz2
Initial cut at multi-window resize code. There's still some TODOs, but
this is enough for folks to get a feel for it. BUG=116079 TEST=covered by tests. R=ben@chromium.org Review URL: https://chromiumcodereview.appspot.com/9609016 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@125187 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--ash/ash.gyp2
-rw-r--r--ash/wm/root_window_event_filter.cc49
-rw-r--r--ash/wm/root_window_event_filter.h6
-rw-r--r--ash/wm/toplevel_window_event_filter_unittest.cc8
-rw-r--r--ash/wm/workspace/multi_window_resize_controller.cc376
-rw-r--r--ash/wm/workspace/multi_window_resize_controller.h145
-rw-r--r--ash/wm/workspace/workspace_event_filter.cc12
-rw-r--r--ash/wm/workspace/workspace_event_filter.h3
-rw-r--r--ash/wm/workspace/workspace_window_resizer.cc229
-rw-r--r--ash/wm/workspace/workspace_window_resizer.h52
-rw-r--r--ash/wm/workspace/workspace_window_resizer_unittest.cc356
-rw-r--r--ui/views/mouse_watcher.h6
-rw-r--r--ui/views/mouse_watcher_view_host.h5
13 files changed, 1190 insertions, 59 deletions
diff --git a/ash/ash.gyp b/ash/ash.gyp
index 42310ec..cb458d1 100644
--- a/ash/ash.gyp
+++ b/ash/ash.gyp
@@ -220,6 +220,8 @@
'wm/workspace/managed_workspace.h',
'wm/workspace/maximized_workspace.cc',
'wm/workspace/maximized_workspace.h',
+ 'wm/workspace/multi_window_resize_controller.cc',
+ 'wm/workspace/multi_window_resize_controller.h',
'wm/workspace/workspace.cc',
'wm/workspace/workspace.h',
'wm/workspace/workspace_event_filter.cc',
diff --git a/ash/wm/root_window_event_filter.cc b/ash/wm/root_window_event_filter.cc
index 7fcaa74..5fd9024 100644
--- a/ash/wm/root_window_event_filter.cc
+++ b/ash/wm/root_window_event_filter.cc
@@ -19,30 +19,6 @@ namespace internal {
namespace {
-// Returns the default cursor for a window component.
-gfx::NativeCursor CursorForWindowComponent(int window_component) {
- switch (window_component) {
- case HTBOTTOM:
- return aura::kCursorSouthResize;
- case HTBOTTOMLEFT:
- return aura::kCursorSouthWestResize;
- case HTBOTTOMRIGHT:
- return aura::kCursorSouthEastResize;
- case HTLEFT:
- return aura::kCursorWestResize;
- case HTRIGHT:
- return aura::kCursorEastResize;
- case HTTOP:
- return aura::kCursorNorthResize;
- case HTTOPLEFT:
- return aura::kCursorNorthWestResize;
- case HTTOPRIGHT:
- return aura::kCursorNorthEastResize;
- default:
- return aura::kCursorNull;
- }
-}
-
aura::Window* FindFocusableWindowFor(aura::Window* window) {
while (window && !window->CanFocus())
window = window->parent();
@@ -67,6 +43,31 @@ RootWindowEventFilter::~RootWindowEventFilter() {
// check_empty == true and will DCHECK failure if it is not empty.
}
+// static
+gfx::NativeCursor RootWindowEventFilter::CursorForWindowComponent(
+ int window_component) {
+ switch (window_component) {
+ case HTBOTTOM:
+ return aura::kCursorSouthResize;
+ case HTBOTTOMLEFT:
+ return aura::kCursorSouthWestResize;
+ case HTBOTTOMRIGHT:
+ return aura::kCursorSouthEastResize;
+ case HTLEFT:
+ return aura::kCursorWestResize;
+ case HTRIGHT:
+ return aura::kCursorEastResize;
+ case HTTOP:
+ return aura::kCursorNorthResize;
+ case HTTOPLEFT:
+ return aura::kCursorNorthWestResize;
+ case HTTOPRIGHT:
+ return aura::kCursorNorthEastResize;
+ default:
+ return aura::kCursorNull;
+ }
+}
+
void RootWindowEventFilter::LockCursor() {
cursor_lock_count_++;
}
diff --git a/ash/wm/root_window_event_filter.h b/ash/wm/root_window_event_filter.h
index a6a1c76..1782534 100644
--- a/ash/wm/root_window_event_filter.h
+++ b/ash/wm/root_window_event_filter.h
@@ -28,6 +28,9 @@ class ASH_EXPORT RootWindowEventFilter : public aura::EventFilter {
RootWindowEventFilter();
virtual ~RootWindowEventFilter();
+ // Returns the cursor for the specified component.
+ static gfx::NativeCursor CursorForWindowComponent(int window_component);
+
// Freezes updates to the cursor until UnlockCursor() is invoked.
void LockCursor();
@@ -38,7 +41,8 @@ class ASH_EXPORT RootWindowEventFilter : public aura::EventFilter {
update_cursor_visibility_ = update;
}
- // Adds/removes additional event filters.
+ // Adds/removes additional event filters. This does not take ownership of
+ // the EventFilter.
void AddFilter(aura::EventFilter* filter);
void RemoveFilter(aura::EventFilter* filter);
size_t GetFilterCount() const;
diff --git a/ash/wm/toplevel_window_event_filter_unittest.cc b/ash/wm/toplevel_window_event_filter_unittest.cc
index 57cf390..1ef64e4 100644
--- a/ash/wm/toplevel_window_event_filter_unittest.cc
+++ b/ash/wm/toplevel_window_event_filter_unittest.cc
@@ -122,15 +122,15 @@ TEST_F(ToplevelWindowEventFilterTest, Caption) {
gfx::Size size = w1->bounds().size();
DragFromCenterBy(w1.get(), 100, 100);
// Position should have been offset by 100,100.
- EXPECT_EQ(gfx::Point(100, 100), w1->bounds().origin());
+ EXPECT_EQ("100,100", w1->bounds().origin().ToString());
// Size should not have.
- EXPECT_EQ(size, w1->bounds().size());
+ EXPECT_EQ(size.ToString(), w1->bounds().size().ToString());
TouchDragFromCenterBy(w1.get(), 100, 100);
// Position should have been offset by 100,100.
- EXPECT_EQ(gfx::Point(200, 200), w1->bounds().origin());
+ EXPECT_EQ("200,200", w1->bounds().origin().ToString());
// Size should not have.
- EXPECT_EQ(size, w1->bounds().size());
+ EXPECT_EQ(size.ToString(), w1->bounds().size().ToString());
}
TEST_F(ToplevelWindowEventFilterTest, BottomRight) {
diff --git a/ash/wm/workspace/multi_window_resize_controller.cc b/ash/wm/workspace/multi_window_resize_controller.cc
new file mode 100644
index 0000000..c60a818
--- /dev/null
+++ b/ash/wm/workspace/multi_window_resize_controller.cc
@@ -0,0 +1,376 @@
+// 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/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/root_window_event_filter.h"
+#include "ash/wm/workspace/workspace_event_filter.h"
+#include "ash/wm/workspace/workspace_window_resizer.h"
+#include "ui/aura/event_filter.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/gfx/canvas.h"
+#include "ui/gfx/canvas_skia.h"
+#include "ui/gfx/screen.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 {
+
+const int kShowDelayMS = 100;
+
+// Padding from the bottom/right edge the resize widget is shown at.
+const int kResizeWidgetPadding = 40;
+
+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;
+}
+
+} // 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) {
+ }
+
+ // views::View overrides:
+ virtual gfx::Size GetPreferredSize() OVERRIDE {
+ return gfx::Size(30, 30);
+ }
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
+ // TODO: replace with real assets.
+ SkPaint paint;
+ paint.setColor(SkColorSetARGB(128, 0, 0, 0));
+ paint.setStyle(SkPaint::kFill_Style);
+ paint.setAntiAlias(true);
+ canvas->AsCanvasSkia()->GetSkCanvas()->drawCircle(
+ SkIntToScalar(15), SkIntToScalar(15), SkIntToScalar(15), paint);
+ }
+ virtual bool OnMousePressed(const views::MouseEvent& event) OVERRIDE {
+ gfx::Point location(event.location());
+ views::View::ConvertPointToScreen(this, &location);
+ controller_->StartResize(location);
+ return true;
+ }
+ virtual bool OnMouseDragged(const views::MouseEvent& event) OVERRIDE {
+ gfx::Point location(event.location());
+ views::View::ConvertPointToScreen(this, &location);
+ controller_->Resize(location);
+ return true;
+ }
+ virtual void OnMouseReleased(const views::MouseEvent& event) OVERRIDE {
+ controller_->CompleteResize();
+ }
+ virtual void OnMouseCaptureLost() OVERRIDE {
+ controller_->CancelResize();
+ }
+ virtual gfx::NativeCursor GetCursor(
+ const views::MouseEvent& event) OVERRIDE {
+ int component = (direction_ == LEFT_RIGHT) ? HTRIGHT : HTBOTTOM;
+ return RootWindowEventFilter::CursorForWindowComponent(component);
+ }
+
+ private:
+ MultiWindowResizeController* controller_;
+ const Direction direction_;
+
+ 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& screen_point,
+ MouseEventType type) OVERRIDE {
+ return !host_->IsOverWindows(screen_point);
+ }
+
+ 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()
+ : resize_widget_(NULL),
+ grid_size_(0) {
+}
+
+MultiWindowResizeController::~MultiWindowResizeController() {
+ Hide();
+}
+
+void MultiWindowResizeController::Show(Window* window,
+ int component,
+ const gfx::Point& point) {
+ ResizeWindows windows(DetermineWindows(window, component, point));
+ if (IsShowing()) {
+ if (windows_.Equals(windows))
+ return;
+ Hide();
+ // Fall through to see if there is another hot spot we should show at.
+ }
+
+ windows_ = windows;
+ if (!windows_.is_valid())
+ return;
+ // TODO: need to listen for windows to be closed/destroyed, maybe window
+ // moved too.
+ show_timer_.Start(FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kShowDelayMS),
+ this, &MultiWindowResizeController::ShowNow);
+}
+
+void MultiWindowResizeController::Hide() {
+ if (window_resizer_.get())
+ return; // Ignore hides while actively resizing.
+
+ show_timer_.Stop();
+ if (!resize_widget_)
+ return;
+ resize_widget_->Close();
+ resize_widget_ = NULL;
+ windows_ = ResizeWindows();
+}
+
+void MultiWindowResizeController::MouseMovedOutOfHost() {
+ Hide();
+}
+
+MultiWindowResizeController::ResizeWindows
+MultiWindowResizeController::DetermineWindows(
+ Window* window,
+ int window_component,
+ const gfx::Point& point) const {
+ ResizeWindows result;
+ gfx::Point point_in_parent(point);
+ Window::ConvertPointToWindow(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;
+}
+
+void MultiWindowResizeController::ShowNow() {
+ DCHECK(!resize_widget_);
+ DCHECK(windows_.is_valid());
+ show_timer_.Stop();
+ resize_widget_ = new views::Widget;
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
+ params.transparent = true;
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.parent = Shell::GetInstance()->GetContainer(
+ ash::internal::kShellWindowId_AlwaysOnTopContainer);
+ params.can_activate = false;
+ ResizeView* view = new ResizeView(this, windows_.direction);
+ params.delegate = new views::WidgetDelegateView;
+ resize_widget_->set_focus_on_creation(false);
+ resize_widget_->Init(params);
+ resize_widget_->GetNativeWindow()->SetName("MultiWindowResizeController");
+ resize_widget_->SetContentsView(view);
+ show_bounds_ = CalculateResizeWidgetBounds();
+ resize_widget_->SetBounds(show_bounds_);
+ resize_widget_->Show();
+}
+
+bool MultiWindowResizeController::IsShowing() const {
+ return resize_widget_ || show_timer_.IsRunning();
+}
+
+void MultiWindowResizeController::StartResize(
+ const gfx::Point& screen_location) {
+ DCHECK(!window_resizer_.get());
+ DCHECK(windows_.is_valid());
+ gfx::Point parent_location(screen_location);
+ aura::Window::ConvertPointToWindow(
+ windows_.window1->GetRootWindow(), windows_.window1->parent(),
+ &parent_location);
+ std::vector<aura::Window*> windows;
+ windows.push_back(windows_.window2);
+ // TODO: search for other windows.
+ int component = windows_.direction == LEFT_RIGHT ? HTRIGHT : HTBOTTOM;
+ window_resizer_.reset(WorkspaceWindowResizer::Create(
+ windows_.window1, parent_location, component, grid_size_, windows));
+}
+
+void MultiWindowResizeController::Resize(const gfx::Point& screen_location) {
+ gfx::Point parent_location(screen_location);
+ aura::Window::ConvertPointToWindow(windows_.window1->GetRootWindow(),
+ windows_.window1->parent(),
+ &parent_location);
+ window_resizer_->Drag(parent_location);
+ gfx::Rect bounds = CalculateResizeWidgetBounds();
+ if (windows_.direction == LEFT_RIGHT)
+ bounds.set_y(show_bounds_.y());
+ else
+ bounds.set_x(show_bounds_.x());
+ resize_widget_->SetBounds(bounds);
+}
+
+void MultiWindowResizeController::CompleteResize() {
+ window_resizer_->CompleteDrag();
+ window_resizer_.reset();
+
+ // Mouse may still be over resizer, if not hide.
+ gfx::Point screen_loc = gfx::Screen::GetCursorScreenPoint();
+ if (!resize_widget_->GetWindowScreenBounds().Contains(screen_loc))
+ Hide();
+}
+
+void MultiWindowResizeController::CancelResize() {
+ window_resizer_->RevertDrag();
+ window_resizer_.reset();
+ Hide();
+}
+
+gfx::Rect MultiWindowResizeController::CalculateResizeWidgetBounds() 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 = std::min(windows_.window1->bounds().bottom(),
+ windows_.window2->bounds().bottom()) - kResizeWidgetPadding;
+ y -= pref.height() / 2;
+ } else {
+ x = std::min(windows_.window1->bounds().right(),
+ windows_.window2->bounds().right()) - kResizeWidgetPadding;
+ x -= pref.width() / 2;
+ y = windows_.window1->bounds().bottom() - pref.height() / 2;
+ }
+ return gfx::Rect(x, y, pref.width(), pref.height());
+}
+
+bool MultiWindowResizeController::IsOverWindows(
+ const gfx::Point& screen_location) const {
+ if (window_resizer_.get())
+ return true; // Ignore hides while actively resizing.
+
+ if (resize_widget_->GetWindowScreenBounds().Contains(screen_location))
+ return true;
+
+ return IsOverWindow(windows_.window1, screen_location) ||
+ IsOverWindow(windows_.window2, screen_location);
+}
+
+bool MultiWindowResizeController::IsOverWindow(
+ aura::Window* window,
+ const gfx::Point& screen_location) const {
+ if (!window->GetScreenBounds().Contains(screen_location))
+ return false;
+
+ gfx::Point window_loc(screen_location);
+ aura::Window::ConvertPointToWindow(
+ window->GetRootWindow(), window->parent(), &window_loc);
+ int component = window->delegate()->GetNonClientComponent(window_loc);
+ // TODO: this needs to make sure no other window is obscuring window.
+ return windows_.Equals(DetermineWindows(window, component, window_loc));
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/ash/wm/workspace/multi_window_resize_controller.h b/ash/wm/workspace/multi_window_resize_controller.h
new file mode 100644
index 0000000..efb3882
--- /dev/null
+++ b/ash/wm/workspace/multi_window_resize_controller.h
@@ -0,0 +1,145 @@
+// 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.
+
+#ifndef ASH_WM_WORKSPACE_MULTI_WINDOW_RESIZE_CONTROLLER_H_
+#define ASH_WM_WORKSPACE_MULTI_WINDOW_RESIZE_CONTROLLER_H_
+#pragma once
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/timer.h"
+#include "ui/gfx/rect.h"
+#include "ui/views/mouse_watcher.h"
+
+namespace aura {
+class Window;
+}
+
+namespace views {
+class Widget;
+}
+
+namespace ash {
+namespace internal {
+
+class WorkspaceWindowResizer;
+
+// Two directions resizes happen in.
+enum Direction {
+ TOP_BOTTOM,
+ LEFT_RIGHT,
+};
+
+// MultiWindowResizeController is responsible for determining and showing a
+// widget that allows resizing multiple windows at the same time.
+// MultiWindowResizeController is driven by WorkspaceEventFilter.
+class ASH_EXPORT MultiWindowResizeController :
+ public views::MouseWatcherListener {
+ public:
+ MultiWindowResizeController();
+ virtual ~MultiWindowResizeController();
+
+ // If necessary, shows the resize widget. |window| is the window the mouse
+ // is over, |component| the edge and |point| the location of the mouse.
+ void Show(aura::Window* window, int component, const gfx::Point& point);
+
+ // Hides the resize widget.
+ void Hide();
+
+ void set_grid_size(int grid_size) { grid_size_ = grid_size; }
+
+ // MouseWatcherListenre overrides:
+ virtual void MouseMovedOutOfHost() OVERRIDE;
+
+ private:
+ // Used to track the two resizable windows and direction.
+ struct ResizeWindows {
+ ResizeWindows();
+ ~ResizeWindows();
+
+ // Returns true if |other| equals this ResizeWindows.
+ bool Equals(const ResizeWindows& other) const;
+
+ // Returns true if this ResizeWindows is valid.
+ bool is_valid() const { return window1 && window2; }
+
+ // The left/top window to resize.
+ aura::Window* window1;
+
+ // Other window to resize.
+ aura::Window* window2;
+
+ // Direction
+ Direction direction;
+ };
+
+ class ResizeMouseWatcherHost;
+ class ResizeView;
+
+ // Returns a ResizeWindows based on the specified arguments. Use is_valid()
+ // to test if the return value is a valid multi window resize location.
+ ResizeWindows DetermineWindows(aura::Window* window,
+ int window_component,
+ const gfx::Point& point) const;
+
+ // Finds a window by edge (one of the constants HitTestCompat.
+ aura::Window* FindWindowByEdge(aura::Window* window_to_ignore,
+ int edge_want,
+ int x,
+ int y) const;
+
+ // Shows the widget immediately.
+ void ShowNow();
+
+ // Returns true if the widget is showing.
+ bool IsShowing() const;
+
+ // Initiates a resize.
+ void StartResize(const gfx::Point& screen_location);
+
+ // Resizes to the new location.
+ void Resize(const gfx::Point& screen_location);
+
+ // Completes the resize.
+ void CompleteResize();
+
+ // Cancels the resize.
+ void CancelResize();
+
+ // Returns the bounds for the resize widget.
+ gfx::Rect CalculateResizeWidgetBounds() const;
+
+ // Returns true if |screen_location| is over the resize windows (or the resize
+ // widget itself).
+ bool IsOverWindows(const gfx::Point& screen_location) const;
+
+ // Returns true if |screen_location| is over |window|.
+ bool IsOverWindow(aura::Window* window,
+ const gfx::Point& screen_location) const;
+
+ // Windows and direction to resize.
+ ResizeWindows windows_;
+
+ // Timer used before showing.
+ base::OneShotTimer<MultiWindowResizeController> show_timer_;
+
+ views::Widget* resize_widget_;
+
+ // If non-null we're in a resize loop.
+ scoped_ptr<WorkspaceWindowResizer> window_resizer_;
+
+ // Bounds the widget was last shown at.
+ gfx::Rect show_bounds_;
+
+ // Size of the grid.
+ int grid_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(MultiWindowResizeController);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_WORKSPACE_MULTI_WINDOW_RESIZE_CONTROLLER_H_
diff --git a/ash/wm/workspace/workspace_event_filter.cc b/ash/wm/workspace/workspace_event_filter.cc
index ae4ae98..a137b15 100644
--- a/ash/wm/workspace/workspace_event_filter.cc
+++ b/ash/wm/workspace/workspace_event_filter.cc
@@ -61,6 +61,14 @@ WorkspaceEventFilter::~WorkspaceEventFilter() {
bool WorkspaceEventFilter::PreHandleMouseEvent(aura::Window* target,
aura::MouseEvent* event) {
switch (event->type()) {
+ case ui::ET_MOUSE_MOVED: {
+ int component =
+ target->delegate()->GetNonClientComponent(event->location());
+ multi_window_resize_controller_.set_grid_size(grid_size());
+ multi_window_resize_controller_.Show(target, component,
+ event->location());
+ break;
+ }
case ui::ET_MOUSE_ENTERED:
UpdateHoveredWindow(wm::GetActivatableWindow(target));
break;
@@ -69,6 +77,7 @@ bool WorkspaceEventFilter::PreHandleMouseEvent(aura::Window* target,
UpdateHoveredWindow(NULL);
break;
case ui::ET_MOUSE_PRESSED:
+ multi_window_resize_controller_.Hide();
HandleVerticalResizeDoubleClick(target, event);
break;
default:
@@ -94,7 +103,8 @@ WindowResizer* WorkspaceEventFilter::CreateWindowResizer(
return NULL;
}
return WorkspaceWindowResizer::Create(
- window, point, window_component, grid_size());
+ window, point, window_component, grid_size(),
+ std::vector<aura::Window*>());
}
void WorkspaceEventFilter::UpdateHoveredWindow(
diff --git a/ash/wm/workspace/workspace_event_filter.h b/ash/wm/workspace/workspace_event_filter.h
index a068c4e..e52d4ca 100644
--- a/ash/wm/workspace/workspace_event_filter.h
+++ b/ash/wm/workspace/workspace_event_filter.h
@@ -7,6 +7,7 @@
#pragma once
#include "ash/wm/toplevel_window_event_filter.h"
+#include "ash/wm/workspace/multi_window_resize_controller.h"
#include "ui/aura/window_observer.h"
namespace aura {
@@ -51,6 +52,8 @@ class WorkspaceEventFilter : public ToplevelWindowEventFilter,
// Top-level window under the mouse cursor.
aura::Window* hovered_window_;
+ MultiWindowResizeController multi_window_resize_controller_;
+
DISALLOW_COPY_AND_ASSIGN(WorkspaceEventFilter);
};
diff --git a/ash/wm/workspace/workspace_window_resizer.cc b/ash/wm/workspace/workspace_window_resizer.cc
index a68c9fd..e7ace29 100644
--- a/ash/wm/workspace/workspace_window_resizer.cc
+++ b/ash/wm/workspace/workspace_window_resizer.cc
@@ -27,6 +27,10 @@ const aura::WindowProperty<int> kHeightBeforeObscuredProp = {0};
const aura::WindowProperty<int>* const kHeightBeforeObscuredKey =
&kHeightBeforeObscuredProp;
+const aura::WindowProperty<int> kWidthBeforeObscuredProp = {0};
+const aura::WindowProperty<int>* const kWidthBeforeObscuredKey =
+ &kWidthBeforeObscuredProp;
+
void SetHeightBeforeObscured(aura::Window* window, int height) {
window->SetProperty(kHeightBeforeObscuredKey, height);
}
@@ -39,8 +43,23 @@ void ClearHeightBeforeObscured(aura::Window* window) {
window->SetProperty(kHeightBeforeObscuredKey, 0);
}
+void SetWidthBeforeObscured(aura::Window* window, int width) {
+ window->SetProperty(kWidthBeforeObscuredKey, width);
+}
+
+int GetWidthBeforeObscured(aura::Window* window) {
+ return window->GetProperty(kWidthBeforeObscuredKey);
+}
+
+void ClearWidthBeforeObscured(aura::Window* window) {
+ window->SetProperty(kWidthBeforeObscuredKey, 0);
+}
+
} // namespace
+// static
+const int WorkspaceWindowResizer::kMinOnscreenSize = 20;
+
WorkspaceWindowResizer::~WorkspaceWindowResizer() {
if (root_filter_)
root_filter_->UnlockCursor();
@@ -51,10 +70,11 @@ WorkspaceWindowResizer* WorkspaceWindowResizer::Create(
aura::Window* window,
const gfx::Point& location,
int window_component,
- int grid_size) {
+ int grid_size,
+ const std::vector<aura::Window*>& attached_windows) {
Details details(window, location, window_component, grid_size);
return details.is_resizable ?
- new WorkspaceWindowResizer(details) : NULL;
+ new WorkspaceWindowResizer(details, attached_windows) : NULL;
}
void WorkspaceWindowResizer::Drag(const gfx::Point& location) {
@@ -65,9 +85,17 @@ void WorkspaceWindowResizer::Drag(const gfx::Point& location) {
did_move_or_resize_ = true;
details_.window->SetBounds(bounds);
}
+ if (!attached_windows_.empty()) {
+ if (details_.window_component == HTRIGHT)
+ LayoutAttachedWindowsHorizontally(bounds);
+ else
+ LayoutAttachedWindowsVertically(bounds);
+ }
}
void WorkspaceWindowResizer::CompleteDrag() {
+ // This code only matters when dragging the caption and there's a grid, so
+ // it doesn't need to worry about attached windows.
if (details_.grid_size <= 1 || !did_move_or_resize_ ||
details_.window_component != HTCAPTION)
return;
@@ -112,30 +140,185 @@ void WorkspaceWindowResizer::RevertDrag() {
return;
details_.window->SetBounds(details_.initial_bounds);
+ if (details_.window_component == HTRIGHT) {
+ int last_x = details_.initial_bounds.right();
+ for (size_t i = 0; i < attached_windows_.size(); ++i) {
+ gfx::Rect bounds(attached_windows_[i]->bounds());
+ bounds.set_x(last_x);
+ bounds.set_width(initial_size_[i]);
+ attached_windows_[i]->SetBounds(bounds);
+ last_x = attached_windows_[i]->bounds().right();
+ }
+ } else {
+ int last_y = details_.initial_bounds.bottom();
+ for (size_t i = 0; i < attached_windows_.size(); ++i) {
+ gfx::Rect bounds(attached_windows_[i]->bounds());
+ bounds.set_y(last_y);
+ bounds.set_height(initial_size_[i]);
+ attached_windows_[i]->SetBounds(bounds);
+ last_y = attached_windows_[i]->bounds().bottom();
+ }
+ }
}
WorkspaceWindowResizer::WorkspaceWindowResizer(
- const Details& details)
+ const Details& details,
+ const std::vector<aura::Window*>& attached_windows)
: details_(details),
constrain_size_(wm::IsWindowNormal(details.window)),
+ attached_windows_(attached_windows),
did_move_or_resize_(false),
- root_filter_(NULL) {
+ root_filter_(NULL),
+ total_min_(0),
+ total_initial_size_(0) {
DCHECK(details_.is_resizable);
root_filter_ = Shell::GetInstance()->root_filter();
if (root_filter_)
root_filter_->LockCursor();
+ // We should never be in a situation where we have an attached window and not
+ // constrain the size. The only case we don't constrain size is for dragging
+ // tabs, which should never have an attached window.
+ DCHECK(attached_windows_.empty() || constrain_size_);
+ // Only support attaching to the right/bottom.
+ DCHECK(attached_windows_.empty() ||
+ (details.window_component == HTRIGHT ||
+ details.window_component == HTBOTTOM));
+
+ // TODO: figure out how to deal with window going off the edge.
+
+ // Calculate sizes so that we can maintain the ratios if we need to resize.
+ int total_available = 0;
+ for (size_t i = 0; i < attached_windows_.size(); ++i) {
+ gfx::Size min(attached_windows_[i]->delegate()->GetMinimumSize());
+ int initial_size = PrimaryAxisSize(attached_windows_[i]->bounds().size());
+ int cached_size = PrimaryAxisCoordinate(
+ GetWidthBeforeObscured(attached_windows_[i]),
+ GetHeightBeforeObscured(attached_windows_[i]));
+ if (initial_size > cached_size) {
+ if (details.window_component == HTRIGHT)
+ ClearWidthBeforeObscured(attached_windows_[i]);
+ else
+ ClearHeightBeforeObscured(attached_windows_[i]);
+ } else if (cached_size) {
+ initial_size = cached_size;
+ }
+ initial_size_.push_back(initial_size);
+ // If current size is smaller than the min, use the current size as the min.
+ // This way we don't snap on resize.
+ int min_size = std::min(initial_size,
+ std::max(PrimaryAxisSize(min), kMinOnscreenSize));
+ // Make sure the min size falls on the grid.
+ if (details_.grid_size > 1 && min_size % details_.grid_size != 0)
+ min_size = (min_size / details_.grid_size + 1) * details_.grid_size;
+ min_size_.push_back(min_size);
+ total_min_ += min_size;
+ total_initial_size_ += initial_size;
+ total_available += std::max(min_size, initial_size) - min_size;
+ }
+
+ for (size_t i = 0; i < attached_windows_.size(); ++i) {
+ if (total_initial_size_ != total_min_) {
+ compress_fraction_.push_back(
+ static_cast<float>(initial_size_[i] - min_size_[i]) /
+ static_cast<float>(total_available));
+ } else {
+ compress_fraction_.push_back(0.0f);
+ }
+ }
+
if (is_resizable() && constrain_size_ &&
(!TouchesBottomOfScreen() ||
details_.bounds_change != kBoundsChange_Repositions)) {
ClearCachedHeights();
}
+
+ if (is_resizable() && constrain_size_ &&
+ (!TouchesRightSideOfScreen() ||
+ details_.bounds_change != kBoundsChange_Repositions)) {
+ ClearCachedWidths();
+ }
+}
+
+void WorkspaceWindowResizer::LayoutAttachedWindowsHorizontally(
+ const gfx::Rect& bounds) {
+ gfx::Rect work_area(gfx::Screen::GetMonitorWorkAreaNearestWindow(window()));
+ int last_x = bounds.right();
+ if (bounds.right() <= work_area.right() - total_initial_size_) {
+ ClearCachedWidths();
+ // All the windows fit at their initial size; tile them horizontally.
+ for (size_t i = 0; i < attached_windows_.size(); ++i) {
+ gfx::Rect attached_bounds(attached_windows_[i]->bounds());
+ attached_bounds.set_x(last_x);
+ attached_bounds.set_width(initial_size_[i]);
+ attached_windows_[i]->SetBounds(attached_bounds);
+ last_x = attached_bounds.right();
+ }
+ } else {
+ DCHECK_NE(total_initial_size_, total_min_);
+ int delta = total_initial_size_ - (work_area.right() - bounds.right());
+ for (size_t i = 0; i < attached_windows_.size(); ++i) {
+ gfx::Rect attached_bounds(attached_windows_[i]->bounds());
+ int size = initial_size_[i] -
+ static_cast<int>(compress_fraction_[i] * delta);
+ size = AlignToGrid(size, details_.grid_size);
+ if (!GetWidthBeforeObscured(attached_windows_[i]))
+ SetWidthBeforeObscured(attached_windows_[i], attached_bounds.width());
+ attached_bounds.set_x(last_x);
+ if (i == attached_windows_.size())
+ size = work_area.right() - last_x;
+ attached_bounds.set_width(size);
+ attached_windows_[i]->SetBounds(attached_bounds);
+ last_x = attached_bounds.right();
+ }
+ }
+}
+
+void WorkspaceWindowResizer::LayoutAttachedWindowsVertically(
+ const gfx::Rect& bounds) {
+ gfx::Rect work_area(gfx::Screen::GetMonitorWorkAreaNearestWindow(window()));
+ int last_y = bounds.bottom();
+ if (bounds.bottom() <= work_area.bottom() - total_initial_size_) {
+ ClearCachedHeights();
+ // All the windows fit at their initial size; tile them vertically.
+ for (size_t i = 0; i < attached_windows_.size(); ++i) {
+ gfx::Rect attached_bounds(attached_windows_[i]->bounds());
+ attached_bounds.set_y(last_y);
+ attached_bounds.set_height(initial_size_[i]);
+ attached_windows_[i]->SetBounds(attached_bounds);
+ last_y = attached_bounds.bottom();
+ }
+ } else {
+ DCHECK_NE(total_initial_size_, total_min_);
+ int delta = total_initial_size_ - (work_area.bottom() - bounds.bottom());
+ for (size_t i = 0; i < attached_windows_.size(); ++i) {
+ gfx::Rect attached_bounds(attached_windows_[i]->bounds());
+ int size = initial_size_[i] -
+ static_cast<int>(compress_fraction_[i] * delta);
+ size = AlignToGrid(size, details_.grid_size);
+ if (i == attached_windows_.size())
+ size = work_area.bottom() - last_y;
+ if (!GetHeightBeforeObscured(attached_windows_[i]))
+ SetHeightBeforeObscured(attached_windows_[i], attached_bounds.height());
+ attached_bounds.set_height(size);
+ attached_bounds.set_y(last_y);
+ attached_windows_[i]->SetBounds(attached_bounds);
+ last_y = attached_bounds.bottom();
+ }
+ }
}
void WorkspaceWindowResizer::AdjustBoundsForMainWindow(
gfx::Rect* bounds) const {
gfx::Rect work_area(gfx::Screen::GetMonitorWorkAreaNearestWindow(window()));
+ if (!attached_windows_.empty() && details_.window_component == HTBOTTOM)
+ work_area.set_height(work_area.height() - total_min_);
AdjustBoundsForWindow(work_area, window(), bounds);
+
+ if (!attached_windows_.empty() && details_.window_component == HTRIGHT) {
+ bounds->set_width(std::min(bounds->width(),
+ work_area.right() - total_min_ - bounds->x()));
+ }
}
void WorkspaceWindowResizer::AdjustBoundsForWindow(
@@ -168,12 +351,48 @@ void WorkspaceWindowResizer::AdjustBoundsForWindow(
void WorkspaceWindowResizer::ClearCachedHeights() {
ClearHeightBeforeObscured(details_.window);
+ for (size_t i = 0; i < attached_windows_.size(); ++i)
+ ClearHeightBeforeObscured(attached_windows_[i]);
+}
+
+void WorkspaceWindowResizer::ClearCachedWidths() {
+ ClearWidthBeforeObscured(details_.window);
+ for (size_t i = 0; i < attached_windows_.size(); ++i)
+ ClearWidthBeforeObscured(attached_windows_[i]);
}
bool WorkspaceWindowResizer::TouchesBottomOfScreen() const {
gfx::Rect work_area(
gfx::Screen::GetMonitorWorkAreaNearestWindow(details_.window));
- return details_.window->bounds().bottom() == work_area.bottom();
+ return (attached_windows_.empty() &&
+ details_.window->bounds().bottom() == work_area.bottom()) ||
+ (!attached_windows_.empty() &&
+ attached_windows_.back()->bounds().bottom() == work_area.bottom());
+}
+
+bool WorkspaceWindowResizer::TouchesRightSideOfScreen() const {
+ gfx::Rect work_area(
+ gfx::Screen::GetMonitorWorkAreaNearestWindow(details_.window));
+ return (attached_windows_.empty() &&
+ details_.window->bounds().right() == work_area.right()) ||
+ (!attached_windows_.empty() &&
+ attached_windows_.back()->bounds().right() == work_area.right());
+}
+
+int WorkspaceWindowResizer::PrimaryAxisSize(const gfx::Size& size) const {
+ return PrimaryAxisCoordinate(size.width(), size.height());
+}
+
+int WorkspaceWindowResizer::PrimaryAxisCoordinate(int x, int y) const {
+ switch (details_.window_component) {
+ case HTRIGHT:
+ return x;
+ case HTBOTTOM:
+ return y;
+ default:
+ NOTREACHED();
+ }
+ return 0;
}
} // namespace internal
diff --git a/ash/wm/workspace/workspace_window_resizer.h b/ash/wm/workspace/workspace_window_resizer.h
index e881202..7410846 100644
--- a/ash/wm/workspace/workspace_window_resizer.h
+++ b/ash/wm/workspace/workspace_window_resizer.h
@@ -6,6 +6,8 @@
#define ASH_WM_WORKSPACE_WINDOW_RESIZER_H_
#pragma once
+#include <vector>
+
#include "ash/wm/window_resizer.h"
#include "base/compiler_specific.h"
@@ -21,15 +23,19 @@ class RootWindowEventFilter;
// attempt to restore the old height.
class ASH_EXPORT WorkspaceWindowResizer : public WindowResizer {
public:
+ // When dragging an attached window this is the min size we'll make sure is
+ // visibile. In the vertical direction we take the max of this and that from
+ // the delegate.
+ static const int kMinOnscreenSize;
+
virtual ~WorkspaceWindowResizer();
- // Creates a new WorkspaceWindowResizer. The caller takes ownership of the
- // returned object. Returns NULL if not resizable.
static WorkspaceWindowResizer* Create(
aura::Window* window,
const gfx::Point& location,
int window_component,
- int grid_size);
+ int grid_size,
+ const std::vector<aura::Window*>& attached_windows);
// Returns true if the drag will result in changing the window in anyway.
bool is_resizable() const { return details_.is_resizable; }
@@ -44,8 +50,16 @@ class ASH_EXPORT WorkspaceWindowResizer : public WindowResizer {
virtual void RevertDrag() OVERRIDE;
private:
+ WorkspaceWindowResizer(const Details& details,
+ const std::vector<aura::Window*>& attached_windows);
+
+ private:
explicit WorkspaceWindowResizer(const Details& details);
+ // Lays out the attached windows. |bounds| is the bounds of the main window.
+ void LayoutAttachedWindowsHorizontally(const gfx::Rect& bounds);
+ void LayoutAttachedWindowsVertically(const gfx::Rect& bounds);
+
// Adjusts the bounds to enforce that windows are vertically contained in the
// work area.
void AdjustBoundsForMainWindow(gfx::Rect* bounds) const;
@@ -53,11 +67,18 @@ class ASH_EXPORT WorkspaceWindowResizer : public WindowResizer {
aura::Window* window,
gfx::Rect* bounds) const;
- // Clears the cached height of the window being dragged.
+ // Clears the cached width/height of the window being dragged.
void ClearCachedHeights();
+ void ClearCachedWidths();
- // Returns true if the window touches the bottom of the work area.
+ // Returns true if the window touches the bottom/right edge of the work area.
bool TouchesBottomOfScreen() const;
+ bool TouchesRightSideOfScreen() const;
+
+ // Returns a coordinate along the primary axis. Used to share code for
+ // left/right multi window resize and top/bottom resize.
+ int PrimaryAxisSize(const gfx::Size& size) const;
+ int PrimaryAxisCoordinate(int x, int y) const;
aura::Window* window() const { return details_.window; }
@@ -66,11 +87,32 @@ class ASH_EXPORT WorkspaceWindowResizer : public WindowResizer {
// True if the window size (height) should be constrained.
const bool constrain_size_;
+ const std::vector<aura::Window*> attached_windows_;
+
// Set to true once Drag() is invoked and the bounds of the window change.
bool did_move_or_resize_;
internal::RootWindowEventFilter* root_filter_;
+ // The initial size of each of the windows in |attached_windows_| along the
+ // primary axis.
+ std::vector<int> initial_size_;
+
+ // The min size of each of the windows in |attached_windows_| along the
+ // primary axis.
+ std::vector<int> min_size_;
+
+ // The amount each of the windows in |attached_windows_| can be compressed.
+ // This is a fraction of the amount a window can be compressed over the total
+ // space the windows can be compressed.
+ std::vector<float> compress_fraction_;
+
+ // Sum of sizes in |min_size_|.
+ int total_min_;
+
+ // Sum of the sizes in |initial_size_|.
+ int total_initial_size_;
+
DISALLOW_COPY_AND_ASSIGN(WorkspaceWindowResizer);
};
diff --git a/ash/wm/workspace/workspace_window_resizer_unittest.cc b/ash/wm/workspace/workspace_window_resizer_unittest.cc
index a3a3542..d129d1e 100644
--- a/ash/wm/workspace/workspace_window_resizer_unittest.cc
+++ b/ash/wm/workspace/workspace_window_resizer_unittest.cc
@@ -82,6 +82,10 @@ class WorkspaceWindowResizerTest : public test::AshTestBase {
return location;
}
+ std::vector<aura::Window*> empty_windows() const {
+ return std::vector<aura::Window*>();
+ }
+
TestWindowDelegate delegate_;
TestWindowDelegate delegate2_;
TestWindowDelegate delegate3_;
@@ -102,7 +106,7 @@ TEST_F(WorkspaceWindowResizerTest, ShrinkOnDrag) {
// the bottom.
{
scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
- window_.get(), gfx::Point(), HTBOTTOM, 0));
+ window_.get(), gfx::Point(), HTBOTTOM, 0, empty_windows()));
ASSERT_TRUE(resizer.get());
resizer->Drag(CalculateDragPoint(*resizer, 0, 600));
EXPECT_EQ(kRootHeight - initial_y, window_->bounds().height());
@@ -116,7 +120,7 @@ TEST_F(WorkspaceWindowResizerTest, ShrinkOnDrag) {
// Move the window down 10 pixels, the height should change.
int initial_height = window_->bounds().height();
scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
- window_.get(), gfx::Point(), HTCAPTION, 0));
+ window_.get(), gfx::Point(), HTCAPTION, 0, empty_windows()));
ASSERT_TRUE(resizer.get());
resizer->Drag(CalculateDragPoint(*resizer, 0, 10));
EXPECT_EQ(initial_height - 10, window_->bounds().height());
@@ -139,7 +143,7 @@ TEST_F(WorkspaceWindowResizerTest, ShrinkOnDrag2) {
// the bottom.
{
scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
- window_.get(), gfx::Point(), HTCAPTION, 0));
+ window_.get(), gfx::Point(), HTCAPTION, 0, empty_windows()));
ASSERT_TRUE(resizer.get());
resizer->Drag(CalculateDragPoint(*resizer, 0, 200));
EXPECT_EQ(500, window_->bounds().y());
@@ -150,7 +154,7 @@ TEST_F(WorkspaceWindowResizerTest, ShrinkOnDrag2) {
{
// Drag up 400.
scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
- window_.get(), gfx::Point(), HTCAPTION, 0));
+ window_.get(), gfx::Point(), HTCAPTION, 0, empty_windows()));
ASSERT_TRUE(resizer.get());
resizer->Drag(CalculateDragPoint(*resizer, 0, -400));
EXPECT_EQ(100, window_->bounds().y());
@@ -167,7 +171,7 @@ TEST_F(WorkspaceWindowResizerTest, ShrinkMoveThanMoveUp) {
// the bottom.
{
scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
- window_.get(), gfx::Point(), HTCAPTION, 0));
+ window_.get(), gfx::Point(), HTCAPTION, 0, empty_windows()));
ASSERT_TRUE(resizer.get());
resizer->Drag(CalculateDragPoint(*resizer, 0, 200));
EXPECT_EQ(500, window_->bounds().y());
@@ -177,7 +181,7 @@ TEST_F(WorkspaceWindowResizerTest, ShrinkMoveThanMoveUp) {
{
scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
- window_.get(), gfx::Point(), HTCAPTION, 0));
+ window_.get(), gfx::Point(), HTCAPTION, 0, empty_windows()));
ASSERT_TRUE(resizer.get());
resizer->Drag(CalculateDragPoint(*resizer, 0, -400));
resizer->Drag(CalculateDragPoint(*resizer, 0, -450));
@@ -191,7 +195,7 @@ TEST_F(WorkspaceWindowResizerTest, ShrinkWithGrid) {
window_->SetBounds(gfx::Rect(0, 300, 400, 296));
scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
- window_.get(), gfx::Point(), HTCAPTION, 5));
+ window_.get(), gfx::Point(), HTCAPTION, 5, empty_windows()));
ASSERT_TRUE(resizer.get());
// Drag down 8 pixels.
resizer->Drag(CalculateDragPoint(*resizer, 0, 8));
@@ -211,7 +215,7 @@ TEST_F(WorkspaceWindowResizerTest, ShrinkThanGrow) {
// bottom.
{
scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
- window_.get(), gfx::Point(), HTCAPTION, 0));
+ window_.get(), gfx::Point(), HTCAPTION, 0, empty_windows()));
ASSERT_TRUE(resizer.get());
resizer->Drag(CalculateDragPoint(*resizer, 0, 150));
EXPECT_EQ(550, window_->bounds().y());
@@ -221,7 +225,7 @@ TEST_F(WorkspaceWindowResizerTest, ShrinkThanGrow) {
// Resize the window 500 pixels up.
{
scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
- window_.get(), gfx::Point(), HTTOP, 0));
+ window_.get(), gfx::Point(), HTTOP, 0, empty_windows()));
ASSERT_TRUE(resizer.get());
resizer->Drag(CalculateDragPoint(*resizer, 0, -500));
EXPECT_EQ(50, window_->bounds().y());
@@ -238,7 +242,7 @@ TEST_F(WorkspaceWindowResizerTest, DontRememberAfterMove) {
// bottom.
{
scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
- window_.get(), gfx::Point(), HTCAPTION, 0));
+ window_.get(), gfx::Point(), HTCAPTION, 0, empty_windows()));
ASSERT_TRUE(resizer.get());
resizer->Drag(CalculateDragPoint(*resizer, 0, 150));
EXPECT_EQ(450, window_->bounds().y());
@@ -251,7 +255,7 @@ TEST_F(WorkspaceWindowResizerTest, DontRememberAfterMove) {
// Resize it slightly.
{
scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
- window_.get(), gfx::Point(), HTBOTTOM, 0));
+ window_.get(), gfx::Point(), HTBOTTOM, 0, empty_windows()));
ASSERT_TRUE(resizer.get());
resizer->Drag(CalculateDragPoint(*resizer, 0, -100));
EXPECT_EQ(150, window_->bounds().y());
@@ -261,7 +265,7 @@ TEST_F(WorkspaceWindowResizerTest, DontRememberAfterMove) {
{
// Move it down then back up.
scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
- window_.get(), gfx::Point(), HTCAPTION, 0));
+ window_.get(), gfx::Point(), HTCAPTION, 0, empty_windows()));
ASSERT_TRUE(resizer.get());
resizer->Drag(CalculateDragPoint(*resizer, 0, 400));
EXPECT_EQ(550, window_->bounds().y());
@@ -282,7 +286,7 @@ TEST_F(WorkspaceWindowResizerTest, HonorMin) {
// bottom.
{
scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
- window_.get(), gfx::Point(), HTCAPTION, 0));
+ window_.get(), gfx::Point(), HTCAPTION, 0, empty_windows()));
ASSERT_TRUE(resizer.get());
resizer->Drag(CalculateDragPoint(*resizer, 0, 350));
EXPECT_EQ(500, window_->bounds().y());
@@ -306,6 +310,332 @@ TEST_F(WorkspaceWindowResizerTest, HonorMin) {
}
}
+// Assertions around attached window resize dragging from the right with 2
+// windows.
+TEST_F(WorkspaceWindowResizerTest, AttachedResize_RIGHT_2) {
+ window_->SetBounds(gfx::Rect(0, 300, 400, 300));
+ window2_->SetBounds(gfx::Rect(400, 200, 100, 200));
+
+ std::vector<aura::Window*> windows;
+ windows.push_back(window2_.get());
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTRIGHT, 0, windows));
+ ASSERT_TRUE(resizer.get());
+ // Move it 100 to the right, which should expand w1 and push w2.
+ resizer->Drag(CalculateDragPoint(*resizer, 100, 10));
+ EXPECT_EQ("0,300 500x300", window_->bounds().ToString());
+ EXPECT_EQ("500,200 100x200", window2_->bounds().ToString());
+
+ // Push off the screen, w2 should be resized to its min.
+ delegate2_.set_min_size(gfx::Size(20, 20));
+ resizer->Drag(CalculateDragPoint(*resizer, 800, 20));
+ EXPECT_EQ("0,300 780x300", window_->bounds().ToString());
+ EXPECT_EQ("780,200 20x200", window2_->bounds().ToString());
+
+ // Move back to 100 and verify w2 gets its original size.
+ resizer->Drag(CalculateDragPoint(*resizer, 100, 10));
+ EXPECT_EQ("0,300 500x300", window_->bounds().ToString());
+ EXPECT_EQ("500,200 100x200", window2_->bounds().ToString());
+
+ // Revert and make sure everything moves back.
+ resizer->Drag(CalculateDragPoint(*resizer, 800, 20));
+ resizer->RevertDrag();
+ EXPECT_EQ("0,300 400x300", window_->bounds().ToString());
+ EXPECT_EQ("400,200 100x200", window2_->bounds().ToString());
+}
+
+// Makes sure we remember the size of an attached window across drags when
+// compressing.
+TEST_F(WorkspaceWindowResizerTest, AttachedResize_RIGHT_2_REMEMBER) {
+ window_->SetBounds(gfx::Rect(0, 300, 400, 300));
+ window2_->SetBounds(gfx::Rect(400, 200, 100, 200));
+
+ std::vector<aura::Window*> windows;
+ windows.push_back(window2_.get());
+
+ {
+ delegate2_.set_min_size(gfx::Size(20, 20));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTRIGHT, 0, windows));
+ ASSERT_TRUE(resizer.get());
+ // Resize enough to slightly compress w2.
+ resizer->Drag(CalculateDragPoint(*resizer, 350, 10));
+ EXPECT_EQ("0,300 750x300", window_->bounds().ToString());
+ EXPECT_EQ("750,200 50x200", window2_->bounds().ToString());
+
+ // Compress w2 a bit more.
+ resizer->Drag(CalculateDragPoint(*resizer, 400, 10));
+ EXPECT_EQ("0,300 780x300", window_->bounds().ToString());
+ EXPECT_EQ("780,200 20x200", window2_->bounds().ToString());
+
+ resizer->CompleteDrag();
+ }
+
+ // Restart drag and drag back 200, making sure window2 goes back to 200.
+ {
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTRIGHT, 0, windows));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, -200, 10));
+ EXPECT_EQ("0,300 580x300", window_->bounds().ToString());
+ EXPECT_EQ("580,200 100x200", window2_->bounds().ToString());
+ }
+}
+
+// Assertions around attached window resize dragging from the right with 3
+// windows.
+TEST_F(WorkspaceWindowResizerTest, AttachedResize_RIGHT_3) {
+ window_->SetBounds(gfx::Rect( 100, 300, 200, 300));
+ window2_->SetBounds(gfx::Rect(300, 300, 150, 200));
+ window3_->SetBounds(gfx::Rect(450, 300, 100, 200));
+ delegate2_.set_min_size(gfx::Size(52, 50));
+ delegate3_.set_min_size(gfx::Size(38, 50));
+
+ std::vector<aura::Window*> windows;
+ windows.push_back(window2_.get());
+ windows.push_back(window3_.get());
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTRIGHT, 10, windows));
+ ASSERT_TRUE(resizer.get());
+ // Move it 100 to the right, which should expand w1 and push w2 and w3.
+ resizer->Drag(CalculateDragPoint(*resizer, 100, -10));
+ EXPECT_EQ("100,300 300x300", window_->bounds().ToString());
+ EXPECT_EQ("400,300 150x200", window2_->bounds().ToString());
+ EXPECT_EQ("550,300 100x200", window3_->bounds().ToString());
+
+ // Move it 296, which should now snap to grid and things should compress.
+ resizer->Drag(CalculateDragPoint(*resizer, 296, -10));
+ EXPECT_EQ("100,300 500x300", window_->bounds().ToString());
+ EXPECT_EQ("600,300 120x200", window2_->bounds().ToString());
+ EXPECT_EQ("720,300 80x200", window3_->bounds().ToString());
+
+ // Move it so much everything ends up at it's min.
+ resizer->Drag(CalculateDragPoint(*resizer, 798, 50));
+ EXPECT_EQ("100,300 600x300", window_->bounds().ToString());
+ EXPECT_EQ("700,300 60x200", window2_->bounds().ToString());
+ EXPECT_EQ("760,300 40x200", window3_->bounds().ToString());
+
+ // Revert and make sure everything moves back.
+ resizer->RevertDrag();
+ EXPECT_EQ("100,300 200x300", window_->bounds().ToString());
+ EXPECT_EQ("300,300 150x200", window2_->bounds().ToString());
+ EXPECT_EQ("450,300 100x200", window3_->bounds().ToString());
+}
+
+TEST_F(WorkspaceWindowResizerTest, AttachedResize_RIGHT_RememberWidth) {
+ window_->SetBounds(gfx::Rect( 100, 300, 200, 300));
+ window2_->SetBounds(gfx::Rect(300, 300, 150, 200));
+ window3_->SetBounds(gfx::Rect(450, 300, 100, 200));
+ delegate2_.set_min_size(gfx::Size(52, 50));
+ delegate3_.set_min_size(gfx::Size(38, 50));
+
+ std::vector<aura::Window*> windows;
+ windows.push_back(window2_.get());
+ windows.push_back(window3_.get());
+
+ {
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTRIGHT, 10, windows));
+ ASSERT_TRUE(resizer.get());
+ // Move it 100 to the right, which should expand w1 and push w2 and w3.
+ resizer->Drag(CalculateDragPoint(*resizer, 100, -10));
+ EXPECT_EQ("100,300 300x300", window_->bounds().ToString());
+ EXPECT_EQ("400,300 150x200", window2_->bounds().ToString());
+ EXPECT_EQ("550,300 100x200", window3_->bounds().ToString());
+
+ // Move it so much everything ends up at it's min.
+ resizer->Drag(CalculateDragPoint(*resizer, 798, 50));
+ EXPECT_EQ("100,300 600x300", window_->bounds().ToString());
+ EXPECT_EQ("700,300 60x200", window2_->bounds().ToString());
+ EXPECT_EQ("760,300 40x200", window3_->bounds().ToString());
+ }
+
+ {
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTRIGHT, 10, windows));
+ ASSERT_TRUE(resizer.get());
+
+ resizer->Drag(CalculateDragPoint(*resizer, -100, 50));
+ EXPECT_EQ("100,300 500x300", window_->bounds().ToString());
+ EXPECT_EQ("600,300 120x200", window2_->bounds().ToString());
+ EXPECT_EQ("720,300 80x200", window3_->bounds().ToString());
+
+ // Move it 300 to the left.
+ resizer->Drag(CalculateDragPoint(*resizer, -300, 50));
+ EXPECT_EQ("100,300 300x300", window_->bounds().ToString());
+ EXPECT_EQ("400,300 150x200", window2_->bounds().ToString());
+ EXPECT_EQ("550,300 100x200", window3_->bounds().ToString());
+ }
+
+}
+
+// Assertions around attached window resize dragging from the bottom with 2
+// windows.
+TEST_F(WorkspaceWindowResizerTest, AttachedResize_BOTTOM_2) {
+ window_->SetBounds(gfx::Rect( 0, 50, 400, 200));
+ window2_->SetBounds(gfx::Rect(0, 250, 200, 100));
+
+ std::vector<aura::Window*> windows;
+ windows.push_back(window2_.get());
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTBOTTOM, 0, windows));
+ ASSERT_TRUE(resizer.get());
+ // Move it 100 to the bottom, which should expand w1 and push w2.
+ resizer->Drag(CalculateDragPoint(*resizer, 10, 100));
+ EXPECT_EQ("0,50 400x300", window_->bounds().ToString());
+ EXPECT_EQ("0,350 200x100", window2_->bounds().ToString());
+
+ // Push off the screen, w2 should be resized to its min.
+ delegate2_.set_min_size(gfx::Size(20, 20));
+ resizer->Drag(CalculateDragPoint(*resizer, 50, 820));
+ EXPECT_EQ("0,50 400x530", window_->bounds().ToString());
+ EXPECT_EQ("0,580 200x20", window2_->bounds().ToString());
+
+ // Move back to 100 and verify w2 gets its original size.
+ resizer->Drag(CalculateDragPoint(*resizer, 10, 100));
+ EXPECT_EQ("0,50 400x300", window_->bounds().ToString());
+ EXPECT_EQ("0,350 200x100", window2_->bounds().ToString());
+
+ // Revert and make sure everything moves back.
+ resizer->Drag(CalculateDragPoint(*resizer, 800, 20));
+ resizer->RevertDrag();
+ EXPECT_EQ("0,50 400x200", window_->bounds().ToString());
+ EXPECT_EQ("0,250 200x100", window2_->bounds().ToString());
+}
+
+// Makes sure we remember the size of an attached window across drags when
+// compressing.
+TEST_F(WorkspaceWindowResizerTest, AttachedResize_BOTTOM_2_REMEMBER) {
+ window_->SetBounds(gfx::Rect( 0, 0, 400, 300));
+ window2_->SetBounds(gfx::Rect(40, 300, 100, 200));
+
+ std::vector<aura::Window*> windows;
+ windows.push_back(window2_.get());
+
+ {
+ delegate2_.set_min_size(gfx::Size(20, 20));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTBOTTOM, 0, windows));
+ ASSERT_TRUE(resizer.get());
+ // Resize enough to slightly compress w2.
+ resizer->Drag(CalculateDragPoint(*resizer, 10, 150));
+ EXPECT_EQ("0,0 400x450", window_->bounds().ToString());
+ EXPECT_EQ("40,450 100x150", window2_->bounds().ToString());
+
+ // Compress w2 a bit more.
+ resizer->Drag(CalculateDragPoint(*resizer, 5, 400));
+ EXPECT_EQ("0,0 400x580", window_->bounds().ToString());
+ EXPECT_EQ("40,580 100x20", window2_->bounds().ToString());
+
+ resizer->CompleteDrag();
+ }
+
+ // Restart drag and drag back 200, making sure window2 goes back to 200.
+ {
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTBOTTOM, 0, windows));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, -10, -200));
+ EXPECT_EQ("0,0 400x380", window_->bounds().ToString());
+ EXPECT_EQ("40,380 100x200", window2_->bounds().ToString());
+ }
+}
+
+// Assertions around attached window resize dragging from the bottom with 3
+// windows.
+TEST_F(WorkspaceWindowResizerTest, AttachedResize_BOTTOM_3) {
+ aura::RootWindow* root = Shell::GetInstance()->GetRootWindow();
+ root->SetBounds(gfx::Rect(0, 0, 600, 800));
+ root->SetScreenWorkAreaInsets(gfx::Insets());
+
+ window_->SetBounds(gfx::Rect( 300, 100, 300, 200));
+ window2_->SetBounds(gfx::Rect(300, 300, 200, 150));
+ window3_->SetBounds(gfx::Rect(300, 450, 200, 100));
+ delegate2_.set_min_size(gfx::Size(50, 52));
+ delegate3_.set_min_size(gfx::Size(50, 38));
+
+ std::vector<aura::Window*> windows;
+ windows.push_back(window2_.get());
+ windows.push_back(window3_.get());
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTBOTTOM, 10, windows));
+ ASSERT_TRUE(resizer.get());
+ // Move it 100 to the right, which should expand w1 and push w2 and w3.
+ resizer->Drag(CalculateDragPoint(*resizer, -10, 100));
+ EXPECT_EQ("300,100 300x300", window_->bounds().ToString());
+ EXPECT_EQ("300,400 200x150", window2_->bounds().ToString());
+ EXPECT_EQ("300,550 200x100", window3_->bounds().ToString());
+
+ // Move it 296, which should now snap to grid and things should compress.
+ resizer->Drag(CalculateDragPoint(*resizer, -10, 296));
+ EXPECT_EQ("300,100 300x500", window_->bounds().ToString());
+ EXPECT_EQ("300,600 200x120", window2_->bounds().ToString());
+ EXPECT_EQ("300,720 200x80", window3_->bounds().ToString());
+
+ // Move it so much everything ends up at it's min.
+ resizer->Drag(CalculateDragPoint(*resizer, 50, 798));
+ EXPECT_EQ("300,100 300x600", window_->bounds().ToString());
+ EXPECT_EQ("300,700 200x60", window2_->bounds().ToString());
+ EXPECT_EQ("300,760 200x40", window3_->bounds().ToString());
+
+ // Revert and make sure everything moves back.
+ resizer->RevertDrag();
+ EXPECT_EQ("300,100 300x200", window_->bounds().ToString());
+ EXPECT_EQ("300,300 200x150", window2_->bounds().ToString());
+ EXPECT_EQ("300,450 200x100", window3_->bounds().ToString());
+}
+
+TEST_F(WorkspaceWindowResizerTest, AttachedResize_BOTTOM_RememberHeight) {
+ aura::RootWindow* root = Shell::GetInstance()->GetRootWindow();
+ root->SetBounds(gfx::Rect(0, 0, 600, 800));
+ root->SetScreenWorkAreaInsets(gfx::Insets());
+
+ window_->SetBounds(gfx::Rect( 300, 100, 300, 200));
+ window2_->SetBounds(gfx::Rect(300, 300, 200, 150));
+ window3_->SetBounds(gfx::Rect(300, 450, 200, 100));
+ delegate2_.set_min_size(gfx::Size(50, 52));
+ delegate3_.set_min_size(gfx::Size(50, 38));
+
+ std::vector<aura::Window*> windows;
+ windows.push_back(window2_.get());
+ windows.push_back(window3_.get());
+
+ {
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTBOTTOM, 10, windows));
+ ASSERT_TRUE(resizer.get());
+ // Move it 100 to the bottom, which should expand w1 and push w2 and w3.
+ resizer->Drag(CalculateDragPoint(*resizer, -10, 100));
+ EXPECT_EQ("300,100 300x300", window_->bounds().ToString());
+ EXPECT_EQ("300,400 200x150", window2_->bounds().ToString());
+ EXPECT_EQ("300,550 200x100", window3_->bounds().ToString());
+
+ // Move it so much everything ends up at it's min.
+ resizer->Drag(CalculateDragPoint(*resizer, 50, 798));
+ EXPECT_EQ("300,100 300x600", window_->bounds().ToString());
+ EXPECT_EQ("300,700 200x60", window2_->bounds().ToString());
+ EXPECT_EQ("300,760 200x40", window3_->bounds().ToString());
+ }
+
+ {
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTBOTTOM, 10, windows));
+ ASSERT_TRUE(resizer.get());
+
+ resizer->Drag(CalculateDragPoint(*resizer, 50, -100));
+ EXPECT_EQ("300,100 300x500", window_->bounds().ToString());
+ EXPECT_EQ("300,600 200x120", window2_->bounds().ToString());
+ EXPECT_EQ("300,720 200x80", window3_->bounds().ToString());
+
+ // Move it 300 up.
+ resizer->Drag(CalculateDragPoint(*resizer, 50, -300));
+ EXPECT_EQ("300,100 300x300", window_->bounds().ToString());
+ EXPECT_EQ("300,400 200x150", window2_->bounds().ToString());
+ EXPECT_EQ("300,550 200x100", window3_->bounds().ToString());
+ }
+
+}
+
} // namespace
} // namespace test
} // namespace aura
diff --git a/ui/views/mouse_watcher.h b/ui/views/mouse_watcher.h
index eb82e79..d3cb62a 100644
--- a/ui/views/mouse_watcher.h
+++ b/ui/views/mouse_watcher.h
@@ -40,10 +40,10 @@ class VIEWS_EXPORT MouseWatcherHost {
};
virtual ~MouseWatcherHost();
+
// Return false when the mouse has moved outside the monitored region.
- virtual bool Contains(
- const gfx::Point& screen_point,
- MouseEventType type) = 0;
+ virtual bool Contains(const gfx::Point& screen_point,
+ MouseEventType type) = 0;
};
// MouseWatcher is used to watch mouse movement and notify its listener when the
diff --git a/ui/views/mouse_watcher_view_host.h b/ui/views/mouse_watcher_view_host.h
index d6ab3ac..fcba9a6 100644
--- a/ui/views/mouse_watcher_view_host.h
+++ b/ui/views/mouse_watcher_view_host.h
@@ -23,9 +23,8 @@ class VIEWS_EXPORT MouseWatcherViewHost : public MouseWatcherHost {
virtual ~MouseWatcherViewHost();
// MouseWatcherHost.
- virtual bool Contains(
- const gfx::Point& screen_point,
- MouseEventType type) OVERRIDE;
+ virtual bool Contains(const gfx::Point& screen_point,
+ MouseEventType type) OVERRIDE;
private:
bool IsCursorInViewZone(const gfx::Point& screen_point);