diff options
Diffstat (limited to 'chromium/ash/wm/workspace/multi_window_resize_controller.cc')
-rw-r--r-- | chromium/ash/wm/workspace/multi_window_resize_controller.cc | 545 |
1 files changed, 545 insertions, 0 deletions
diff --git a/chromium/ash/wm/workspace/multi_window_resize_controller.cc b/chromium/ash/wm/workspace/multi_window_resize_controller.cc new file mode 100644 index 00000000000..597acbf88ec --- /dev/null +++ b/chromium/ash/wm/workspace/multi_window_resize_controller.cc @@ -0,0 +1,545 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ash/wm/workspace/multi_window_resize_controller.h" + +#include "ash/screen_ash.h" +#include "ash/shell.h" +#include "ash/shell_window_ids.h" +#include "ash/wm/coordinate_conversion.h" +#include "ash/wm/window_animations.h" +#include "ash/wm/workspace/workspace_event_handler.h" +#include "ash/wm/workspace/workspace_window_resizer.h" +#include "grit/ash_resources.h" +#include "ui/aura/client/screen_position_client.h" +#include "ui/aura/root_window.h" +#include "ui/aura/window.h" +#include "ui/aura/window_delegate.h" +#include "ui/base/hit_test.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/image/image.h" +#include "ui/gfx/screen.h" +#include "ui/views/corewm/compound_event_filter.h" +#include "ui/views/view.h" +#include "ui/views/widget/widget.h" +#include "ui/views/widget/widget_delegate.h" + +using aura::Window; + +namespace ash { +namespace internal { + +namespace { + +// Delay before showing. +const int kShowDelayMS = 400; + +// Delay before hiding. +const int kHideDelayMS = 500; + +// Padding from the bottom/right edge the resize widget is shown at. +const int kResizeWidgetPadding = 15; + +bool ContainsX(Window* window, int x) { + return window->bounds().x() <= x && window->bounds().right() >= x; +} + +bool ContainsY(Window* window, int y) { + return window->bounds().y() <= y && window->bounds().bottom() >= y; +} + +bool Intersects(int x1, int max_1, int x2, int max_2) { + return x2 <= max_1 && max_2 > x1; +} + +} // namespace + +// View contained in the widget. Passes along mouse events to the +// MultiWindowResizeController so that it can start/stop the resize loop. +class MultiWindowResizeController::ResizeView : public views::View { + public: + explicit ResizeView(MultiWindowResizeController* controller, + Direction direction) + : controller_(controller), + direction_(direction), + image_(NULL) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + int image_id = + direction == TOP_BOTTOM ? IDR_AURA_MULTI_WINDOW_RESIZE_H : + IDR_AURA_MULTI_WINDOW_RESIZE_V; + image_ = rb.GetImageNamed(image_id).ToImageSkia(); + } + + // views::View overrides: + virtual gfx::Size GetPreferredSize() OVERRIDE { + return gfx::Size(image_->width(), image_->height()); + } + virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE { + canvas->DrawImageInt(*image_, 0, 0); + } + virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE { + gfx::Point location(event.location()); + views::View::ConvertPointToScreen(this, &location); + controller_->StartResize(location); + return true; + } + virtual bool OnMouseDragged(const ui::MouseEvent& event) OVERRIDE { + gfx::Point location(event.location()); + views::View::ConvertPointToScreen(this, &location); + controller_->Resize(location, event.flags()); + return true; + } + virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE { + controller_->CompleteResize(event.flags()); + } + virtual void OnMouseCaptureLost() OVERRIDE { + controller_->CancelResize(); + } + virtual gfx::NativeCursor GetCursor( + const ui::MouseEvent& event) OVERRIDE { + int component = (direction_ == LEFT_RIGHT) ? HTRIGHT : HTBOTTOM; + return views::corewm::CompoundEventFilter::CursorForWindowComponent( + component); + } + + private: + MultiWindowResizeController* controller_; + const Direction direction_; + const gfx::ImageSkia* image_; + + DISALLOW_COPY_AND_ASSIGN(ResizeView); +}; + +// MouseWatcherHost implementation for MultiWindowResizeController. Forwards +// Contains() to MultiWindowResizeController. +class MultiWindowResizeController::ResizeMouseWatcherHost : + public views::MouseWatcherHost { + public: + ResizeMouseWatcherHost(MultiWindowResizeController* host) : host_(host) {} + + // MouseWatcherHost overrides: + virtual bool Contains(const gfx::Point& point_in_screen, + MouseEventType type) OVERRIDE { + return host_->IsOverWindows(point_in_screen); + } + + private: + MultiWindowResizeController* host_; + + DISALLOW_COPY_AND_ASSIGN(ResizeMouseWatcherHost); +}; + +MultiWindowResizeController::ResizeWindows::ResizeWindows() + : window1(NULL), + window2(NULL), + direction(TOP_BOTTOM){ +} + +MultiWindowResizeController::ResizeWindows::~ResizeWindows() { +} + +bool MultiWindowResizeController::ResizeWindows::Equals( + const ResizeWindows& other) const { + return window1 == other.window1 && + window2 == other.window2 && + direction == other.direction; +} + +MultiWindowResizeController::MultiWindowResizeController() { +} + +MultiWindowResizeController::~MultiWindowResizeController() { + window_resizer_.reset(); + Hide(); +} + +void MultiWindowResizeController::Show(Window* window, + int component, + const gfx::Point& point_in_window) { + // When the resize widget is showing we ignore Show() requests. Instead we + // only care about mouse movements from MouseWatcher. This is necessary as + // WorkspaceEventHandler only sees mouse movements over the windows, not all + // windows or over the desktop. + if (resize_widget_) + return; + + ResizeWindows windows(DetermineWindows(window, component, point_in_window)); + if (IsShowing()) { + if (windows_.Equals(windows)) + return; // Over the same windows. + DelayedHide(); + } + + if (!windows.is_valid()) + return; + Hide(); + windows_ = windows; + windows_.window1->AddObserver(this); + windows_.window2->AddObserver(this); + show_location_in_parent_ = point_in_window; + Window::ConvertPointToTarget( + window, window->parent(), &show_location_in_parent_); + if (show_timer_.IsRunning()) + return; + show_timer_.Start( + FROM_HERE, base::TimeDelta::FromMilliseconds(kShowDelayMS), + this, &MultiWindowResizeController::ShowIfValidMouseLocation); +} + +void MultiWindowResizeController::Hide() { + hide_timer_.Stop(); + if (window_resizer_) + return; // Ignore hides while actively resizing. + + if (windows_.window1) { + windows_.window1->RemoveObserver(this); + windows_.window1 = NULL; + } + if (windows_.window2) { + windows_.window2->RemoveObserver(this); + windows_.window2 = NULL; + } + + show_timer_.Stop(); + + if (!resize_widget_) + return; + + for (size_t i = 0; i < windows_.other_windows.size(); ++i) + windows_.other_windows[i]->RemoveObserver(this); + mouse_watcher_.reset(); + resize_widget_.reset(); + windows_ = ResizeWindows(); +} + +void MultiWindowResizeController::MouseMovedOutOfHost() { + Hide(); +} + +void MultiWindowResizeController::OnWindowDestroying( + aura::Window* window) { + // Have to explicitly reset the WindowResizer, otherwise Hide() does nothing. + window_resizer_.reset(); + Hide(); +} + +MultiWindowResizeController::ResizeWindows +MultiWindowResizeController::DetermineWindowsFromScreenPoint( + aura::Window* window) const { + gfx::Point mouse_location( + gfx::Screen::GetScreenFor(window)->GetCursorScreenPoint()); + wm::ConvertPointFromScreen(window, &mouse_location); + const int component = + window->delegate()->GetNonClientComponent(mouse_location); + return DetermineWindows(window, component, mouse_location); +} + +MultiWindowResizeController::ResizeWindows +MultiWindowResizeController::DetermineWindows( + Window* window, + int window_component, + const gfx::Point& point) const { + ResizeWindows result; + gfx::Point point_in_parent(point); + Window::ConvertPointToTarget(window, window->parent(), &point_in_parent); + switch (window_component) { + case HTRIGHT: + result.direction = LEFT_RIGHT; + result.window1 = window; + result.window2 = FindWindowByEdge( + window, HTLEFT, window->bounds().right(), point_in_parent.y()); + break; + case HTLEFT: + result.direction = LEFT_RIGHT; + result.window1 = FindWindowByEdge( + window, HTRIGHT, window->bounds().x(), point_in_parent.y()); + result.window2 = window; + break; + case HTTOP: + result.direction = TOP_BOTTOM; + result.window1 = FindWindowByEdge( + window, HTBOTTOM, point_in_parent.x(), window->bounds().y()); + result.window2 = window; + break; + case HTBOTTOM: + result.direction = TOP_BOTTOM; + result.window1 = window; + result.window2 = FindWindowByEdge( + window, HTTOP, point_in_parent.x(), window->bounds().bottom()); + break; + default: + break; + } + return result; +} + +Window* MultiWindowResizeController::FindWindowByEdge( + Window* window_to_ignore, + int edge_want, + int x, + int y) const { + Window* parent = window_to_ignore->parent(); + const Window::Windows& windows(parent->children()); + for (Window::Windows::const_reverse_iterator i = windows.rbegin(); + i != windows.rend(); ++i) { + Window* window = *i; + if (window == window_to_ignore || !window->IsVisible()) + continue; + switch (edge_want) { + case HTLEFT: + if (ContainsY(window, y) && window->bounds().x() == x) + return window; + break; + case HTRIGHT: + if (ContainsY(window, y) && window->bounds().right() == x) + return window; + break; + case HTTOP: + if (ContainsX(window, x) && window->bounds().y() == y) + return window; + break; + case HTBOTTOM: + if (ContainsX(window, x) && window->bounds().bottom() == y) + return window; + break; + default: + NOTREACHED(); + } + // Window doesn't contain the edge, but if window contains |point| + // it's obscuring any other window that could be at the location. + if (window->bounds().Contains(x, y)) + return NULL; + } + return NULL; +} + +aura::Window* MultiWindowResizeController::FindWindowTouching( + aura::Window* window, + Direction direction) const { + int right = window->bounds().right(); + int bottom = window->bounds().bottom(); + Window* parent = window->parent(); + const Window::Windows& windows(parent->children()); + for (Window::Windows::const_reverse_iterator i = windows.rbegin(); + i != windows.rend(); ++i) { + Window* other = *i; + if (other == window || !other->IsVisible()) + continue; + switch (direction) { + case TOP_BOTTOM: + if (other->bounds().y() == bottom && + Intersects(other->bounds().x(), other->bounds().right(), + window->bounds().x(), window->bounds().right())) { + return other; + } + break; + case LEFT_RIGHT: + if (other->bounds().x() == right && + Intersects(other->bounds().y(), other->bounds().bottom(), + window->bounds().y(), window->bounds().bottom())) { + return other; + } + break; + default: + NOTREACHED(); + } + } + return NULL; +} + +void MultiWindowResizeController::FindWindowsTouching( + aura::Window* start, + Direction direction, + std::vector<aura::Window*>* others) const { + while (start) { + start = FindWindowTouching(start, direction); + if (start) + others->push_back(start); + } +} + +void MultiWindowResizeController::DelayedHide() { + if (hide_timer_.IsRunning()) + return; + + hide_timer_.Start(FROM_HERE, + base::TimeDelta::FromMilliseconds(kHideDelayMS), + this, &MultiWindowResizeController::Hide); +} + +void MultiWindowResizeController::ShowIfValidMouseLocation() { + if (DetermineWindowsFromScreenPoint(windows_.window1).Equals(windows_) || + DetermineWindowsFromScreenPoint(windows_.window2).Equals(windows_)) { + ShowNow(); + } else { + Hide(); + } +} + +void MultiWindowResizeController::ShowNow() { + DCHECK(!resize_widget_.get()); + DCHECK(windows_.is_valid()); + show_timer_.Stop(); + resize_widget_.reset(new views::Widget); + views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP); + params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; + params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + params.parent = Shell::GetContainer( + Shell::GetTargetRootWindow(), + internal::kShellWindowId_AlwaysOnTopContainer); + params.can_activate = false; + ResizeView* view = new ResizeView(this, windows_.direction); + resize_widget_->set_focus_on_creation(false); + resize_widget_->Init(params); + views::corewm::SetWindowVisibilityAnimationType( + resize_widget_->GetNativeWindow(), + views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE); + resize_widget_->GetNativeWindow()->SetName("MultiWindowResizeController"); + resize_widget_->SetContentsView(view); + show_bounds_in_screen_ = ScreenAsh::ConvertRectToScreen( + windows_.window1->parent(), + CalculateResizeWidgetBounds(show_location_in_parent_)); + resize_widget_->SetBounds(show_bounds_in_screen_); + resize_widget_->Show(); + mouse_watcher_.reset(new views::MouseWatcher( + new ResizeMouseWatcherHost(this), + this)); + mouse_watcher_->set_notify_on_exit_time( + base::TimeDelta::FromMilliseconds(kHideDelayMS)); + mouse_watcher_->Start(); +} + +bool MultiWindowResizeController::IsShowing() const { + return resize_widget_.get() || show_timer_.IsRunning(); +} + +void MultiWindowResizeController::StartResize( + const gfx::Point& location_in_screen) { + DCHECK(!window_resizer_.get()); + DCHECK(windows_.is_valid()); + hide_timer_.Stop(); + gfx::Point location_in_parent(location_in_screen); + aura::client::GetScreenPositionClient(windows_.window2->GetRootWindow())-> + ConvertPointFromScreen(windows_.window2->parent(), &location_in_parent); + std::vector<aura::Window*> windows; + windows.push_back(windows_.window2); + DCHECK(windows_.other_windows.empty()); + FindWindowsTouching(windows_.window2, windows_.direction, + &windows_.other_windows); + for (size_t i = 0; i < windows_.other_windows.size(); ++i) { + windows_.other_windows[i]->AddObserver(this); + windows.push_back(windows_.other_windows[i]); + } + int component = windows_.direction == LEFT_RIGHT ? HTRIGHT : HTBOTTOM; + window_resizer_.reset(WorkspaceWindowResizer::Create( + windows_.window1, + location_in_parent, + component, + aura::client::WINDOW_MOVE_SOURCE_MOUSE, + windows)); +} + +void MultiWindowResizeController::Resize(const gfx::Point& location_in_screen, + int event_flags) { + gfx::Point location_in_parent(location_in_screen); + aura::client::GetScreenPositionClient(windows_.window1->GetRootWindow())-> + ConvertPointFromScreen(windows_.window1->parent(), &location_in_parent); + window_resizer_->Drag(location_in_parent, event_flags); + gfx::Rect bounds = ScreenAsh::ConvertRectToScreen( + windows_.window1->parent(), + CalculateResizeWidgetBounds(location_in_parent)); + + if (windows_.direction == LEFT_RIGHT) + bounds.set_y(show_bounds_in_screen_.y()); + else + bounds.set_x(show_bounds_in_screen_.x()); + resize_widget_->SetBounds(bounds); +} + +void MultiWindowResizeController::CompleteResize(int event_flags) { + window_resizer_->CompleteDrag(event_flags); + window_resizer_.reset(); + + // Mouse may still be over resizer, if not hide. + gfx::Point screen_loc = Shell::GetScreen()->GetCursorScreenPoint(); + if (!resize_widget_->GetWindowBoundsInScreen().Contains(screen_loc)) { + Hide(); + } else { + // If the mouse is over the resizer we need to remove observers on any of + // the |other_windows|. If we start another resize we'll recalculate the + // |other_windows| and invoke AddObserver() as necessary. + for (size_t i = 0; i < windows_.other_windows.size(); ++i) + windows_.other_windows[i]->RemoveObserver(this); + windows_.other_windows.clear(); + } +} + +void MultiWindowResizeController::CancelResize() { + if (!window_resizer_) + return; // Happens if window was destroyed and we nuked the WindowResizer. + window_resizer_->RevertDrag(); + window_resizer_.reset(); + Hide(); +} + +gfx::Rect MultiWindowResizeController::CalculateResizeWidgetBounds( + const gfx::Point& location_in_parent) const { + gfx::Size pref = resize_widget_->GetContentsView()->GetPreferredSize(); + int x = 0, y = 0; + if (windows_.direction == LEFT_RIGHT) { + x = windows_.window1->bounds().right() - pref.width() / 2; + y = location_in_parent.y() + kResizeWidgetPadding; + if (y + pref.height() / 2 > windows_.window1->bounds().bottom() && + y + pref.height() / 2 > windows_.window2->bounds().bottom()) { + y = location_in_parent.y() - kResizeWidgetPadding - pref.height(); + } + } else { + x = location_in_parent.x() + kResizeWidgetPadding; + if (x + pref.height() / 2 > windows_.window1->bounds().right() && + x + pref.height() / 2 > windows_.window2->bounds().right()) { + x = location_in_parent.x() - kResizeWidgetPadding - pref.width(); + } + y = windows_.window1->bounds().bottom() - pref.height() / 2; + } + return gfx::Rect(x, y, pref.width(), pref.height()); +} + +bool MultiWindowResizeController::IsOverWindows( + const gfx::Point& location_in_screen) const { + if (window_resizer_) + return true; // Ignore hides while actively resizing. + + if (resize_widget_->GetWindowBoundsInScreen().Contains(location_in_screen)) + return true; + + int hit1, hit2; + if (windows_.direction == TOP_BOTTOM) { + hit1 = HTBOTTOM; + hit2 = HTTOP; + } else { + hit1 = HTRIGHT; + hit2 = HTLEFT; + } + + return IsOverWindow(windows_.window1, location_in_screen, hit1) || + IsOverWindow(windows_.window2, location_in_screen, hit2); +} + +bool MultiWindowResizeController::IsOverWindow( + aura::Window* window, + const gfx::Point& location_in_screen, + int component) const { + if (!window->delegate()) + return false; + + gfx::Point window_loc(location_in_screen); + aura::Window::ConvertPointToTarget( + window->GetRootWindow(), window, &window_loc); + return window->HitTest(window_loc) && + window->delegate()->GetNonClientComponent(window_loc) == component; +} + +} // namespace internal +} // namespace ash |