blob: 92b875567d0b7487bf65e66a854176044686478a [file] [log] [blame]
// 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/drag_window_resizer.h"
#include "ash/display/mouse_cursor_event_filter.h"
#include "ash/root_window_controller.h"
#include "ash/screen_ash.h"
#include "ash/shell.h"
#include "ash/system/tray/system_tray.h"
#include "ash/system/user/tray_user.h"
#include "ash/wm/coordinate_conversion.h"
#include "ash/wm/drag_window_controller.h"
#include "base/memory/weak_ptr.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/env.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/ui_base_types.h"
#include "ui/gfx/screen.h"
namespace ash {
namespace internal {
namespace {
// The maximum opacity of the drag phantom window.
const float kMaxOpacity = 0.8f;
// Returns true if Ash has more than one root window.
bool HasSecondaryRootWindow() {
return Shell::GetAllRootWindows().size() > 1;
}
// When there are two root windows, returns one of the root windows which is not
// |root_window|. Returns NULL if only one root window exists.
aura::RootWindow* GetAnotherRootWindow(aura::RootWindow* root_window) {
Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
if (root_windows.size() < 2)
return NULL;
DCHECK_EQ(2U, root_windows.size());
if (root_windows[0] == root_window)
return root_windows[1];
return root_windows[0];
}
} // namespace
// static
DragWindowResizer* DragWindowResizer::instance_ = NULL;
DragWindowResizer::~DragWindowResizer() {
Shell* shell = Shell::GetInstance();
shell->mouse_cursor_filter()->set_mouse_warp_mode(
MouseCursorEventFilter::WARP_ALWAYS);
shell->mouse_cursor_filter()->HideSharedEdgeIndicator();
if (instance_ == this)
instance_ = NULL;
}
// static
DragWindowResizer* DragWindowResizer::Create(
WindowResizer* next_window_resizer,
aura::Window* window,
const gfx::Point& location,
int window_component,
aura::client::WindowMoveSource source) {
Details details(window, location, window_component, source);
return details.is_resizable ?
new DragWindowResizer(next_window_resizer, details) : NULL;
}
void DragWindowResizer::Drag(const gfx::Point& location, int event_flags) {
base::WeakPtr<DragWindowResizer> resizer(weak_ptr_factory_.GetWeakPtr());
// If we are on top of a window to desktop transfer button, we move the window
// temporarily back to where it was initially (showing that we do something).
gfx::Point filtered_location = GetTrayUserItemAtPoint(location) ?
details_.initial_location_in_parent : location;
next_window_resizer_->Drag(filtered_location, event_flags);
if (!resizer)
return;
last_mouse_location_ = location;
// Show a phantom window for dragging in another root window.
if (HasSecondaryRootWindow()) {
gfx::Point location_in_screen = location;
wm::ConvertPointToScreen(GetTarget()->parent(), &location_in_screen);
const bool in_original_root =
wm::GetRootWindowAt(location_in_screen) == GetTarget()->GetRootWindow();
UpdateDragWindow(GetTarget()->bounds(), in_original_root);
} else {
drag_window_controller_.reset();
}
}
void DragWindowResizer::CompleteDrag(int event_flags) {
if (TryDraggingToNewUser())
return;
next_window_resizer_->CompleteDrag(event_flags);
GetTarget()->layer()->SetOpacity(details_.initial_opacity);
drag_window_controller_.reset();
// Check if the destination is another display.
gfx::Point last_mouse_location_in_screen = last_mouse_location_;
wm::ConvertPointToScreen(GetTarget()->parent(),
&last_mouse_location_in_screen);
gfx::Screen* screen = Shell::GetScreen();
const gfx::Display dst_display =
screen->GetDisplayNearestPoint(last_mouse_location_in_screen);
if (dst_display.id() !=
screen->GetDisplayNearestWindow(GetTarget()->GetRootWindow()).id()) {
const gfx::Rect dst_bounds =
ScreenAsh::ConvertRectToScreen(GetTarget()->parent(),
GetTarget()->bounds());
GetTarget()->SetBoundsInScreen(dst_bounds, dst_display);
}
}
void DragWindowResizer::RevertDrag() {
next_window_resizer_->RevertDrag();
drag_window_controller_.reset();
GetTarget()->layer()->SetOpacity(details_.initial_opacity);
}
aura::Window* DragWindowResizer::GetTarget() {
return next_window_resizer_->GetTarget();
}
const gfx::Point& DragWindowResizer::GetInitialLocation() const {
return details_.initial_location_in_parent;
}
DragWindowResizer::DragWindowResizer(WindowResizer* next_window_resizer,
const Details& details)
: next_window_resizer_(next_window_resizer),
details_(details),
weak_ptr_factory_(this) {
// The pointer should be confined in one display during resizing a window
// because the window cannot span two displays at the same time anyway. The
// exception is window/tab dragging operation. During that operation,
// |mouse_warp_mode_| should be set to WARP_DRAG so that the user could move a
// window/tab to another display.
MouseCursorEventFilter* mouse_cursor_filter =
Shell::GetInstance()->mouse_cursor_filter();
mouse_cursor_filter->set_mouse_warp_mode(
ShouldAllowMouseWarp() ?
MouseCursorEventFilter::WARP_DRAG : MouseCursorEventFilter::WARP_NONE);
if (ShouldAllowMouseWarp()) {
mouse_cursor_filter->ShowSharedEdgeIndicator(
details.window->GetRootWindow());
}
instance_ = this;
}
void DragWindowResizer::UpdateDragWindow(const gfx::Rect& bounds,
bool in_original_root) {
if (details_.window_component != HTCAPTION || !ShouldAllowMouseWarp())
return;
// It's available. Show a phantom window on the display if needed.
aura::RootWindow* another_root =
GetAnotherRootWindow(GetTarget()->GetRootWindow());
const gfx::Rect root_bounds_in_screen(another_root->GetBoundsInScreen());
const gfx::Rect bounds_in_screen =
ScreenAsh::ConvertRectToScreen(GetTarget()->parent(), bounds);
gfx::Rect bounds_in_another_root =
gfx::IntersectRects(root_bounds_in_screen, bounds_in_screen);
const float fraction_in_another_window =
(bounds_in_another_root.width() * bounds_in_another_root.height()) /
static_cast<float>(bounds.width() * bounds.height());
if (fraction_in_another_window > 0) {
if (!drag_window_controller_) {
drag_window_controller_.reset(
new DragWindowController(GetTarget()));
// Always show the drag phantom on the |another_root| window.
drag_window_controller_->SetDestinationDisplay(
Shell::GetScreen()->GetDisplayNearestWindow(another_root));
drag_window_controller_->Show();
} else {
// No animation.
drag_window_controller_->SetBounds(bounds_in_screen);
}
const float phantom_opacity =
!in_original_root ? 1 : (kMaxOpacity * fraction_in_another_window);
const float window_opacity =
in_original_root ? 1 : (kMaxOpacity * (1 - fraction_in_another_window));
drag_window_controller_->SetOpacity(phantom_opacity);
GetTarget()->layer()->SetOpacity(window_opacity);
} else {
drag_window_controller_.reset();
GetTarget()->layer()->SetOpacity(1.0f);
}
}
bool DragWindowResizer::ShouldAllowMouseWarp() {
return (details_.window_component == HTCAPTION) &&
!GetTarget()->transient_parent() &&
(GetTarget()->type() == aura::client::WINDOW_TYPE_NORMAL ||
GetTarget()->type() == aura::client::WINDOW_TYPE_PANEL);
}
TrayUser* DragWindowResizer::GetTrayUserItemAtPoint(
const gfx::Point& point_in_screen) {
// Unit tests might not have an ash shell.
if (!ash::Shell::GetInstance())
return NULL;
// Check that this is a drag move operation from a suitable window.
if (details_.window_component != HTCAPTION ||
GetTarget()->transient_parent() ||
(GetTarget()->type() != aura::client::WINDOW_TYPE_NORMAL &&
GetTarget()->type() != aura::client::WINDOW_TYPE_PANEL &&
GetTarget()->type() != aura::client::WINDOW_TYPE_POPUP))
return NULL;
// We only allow to drag the window onto a tray of it's own RootWindow.
SystemTray* tray = internal::GetRootWindowController(
details_.window->GetRootWindow())->GetSystemTray();
// Again - unit tests might not have a tray.
if (!tray)
return NULL;
const std::vector<internal::TrayUser*> tray_users = tray->GetTrayUserItems();
if (tray_users.size() <= 1)
return NULL;
std::vector<internal::TrayUser*>::const_iterator it = tray_users.begin();
for (; it != tray_users.end(); ++it) {
if ((*it)->CanDropWindowHereToTransferToUser(point_in_screen))
return *it;
}
return NULL;
}
bool DragWindowResizer::TryDraggingToNewUser() {
TrayUser* tray_user = GetTrayUserItemAtPoint(last_mouse_location_);
// No need to try dragging if there is no user.
if (!tray_user)
return false;
// We have to avoid a brief flash caused by the RevertDrag operation.
// To do this, we first set the opacity of our target window to 0, so that no
// matter what the RevertDrag does the window will stay hidden. Then transfer
// the window to the new owner (which will hide it). RevertDrag will then do
// it's thing and return the transparency to its original value.
int old_opacity = GetTarget()->layer()->opacity();
GetTarget()->layer()->SetOpacity(0);
if (!tray_user->TransferWindowToUser(details_.window)) {
GetTarget()->layer()->SetOpacity(old_opacity);
return false;
}
RevertDrag();
return true;
}
} // namespace internal
} // namespace ash