diff options
-rw-r--r-- | ash/ash.gyp | 2 | ||||
-rw-r--r-- | ash/wm/root_window_event_filter.cc | 49 | ||||
-rw-r--r-- | ash/wm/root_window_event_filter.h | 6 | ||||
-rw-r--r-- | ash/wm/toplevel_window_event_filter_unittest.cc | 8 | ||||
-rw-r--r-- | ash/wm/workspace/multi_window_resize_controller.cc | 376 | ||||
-rw-r--r-- | ash/wm/workspace/multi_window_resize_controller.h | 145 | ||||
-rw-r--r-- | ash/wm/workspace/workspace_event_filter.cc | 12 | ||||
-rw-r--r-- | ash/wm/workspace/workspace_event_filter.h | 3 | ||||
-rw-r--r-- | ash/wm/workspace/workspace_window_resizer.cc | 229 | ||||
-rw-r--r-- | ash/wm/workspace/workspace_window_resizer.h | 52 | ||||
-rw-r--r-- | ash/wm/workspace/workspace_window_resizer_unittest.cc | 356 | ||||
-rw-r--r-- | ui/views/mouse_watcher.h | 6 | ||||
-rw-r--r-- | ui/views/mouse_watcher_view_host.h | 5 |
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); |