Index: ash/wm/workspace/multi_window_resize_controller.cc |
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 0000000000000000000000000000000000000000..c60a81812baaf4858518b791f381cf7928f8a95f |
--- /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 |