// 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 "ash/wm/window_state.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; // The opacity of the window when dragging it over a user item in the tray. const float kOpacityWhenDraggedOverUserIcon = 0.4f; // 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::Window* GetAnotherRootWindow(aura::Window* root_window) { aura::Window::Windows 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() { if (GetTarget()) wm::GetWindowState(GetTarget())->set_window_resizer_(NULL); 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 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 and make it semi-transparent. GetTarget()->layer()->SetOpacity( GetTrayUserItemAtPoint(location) ? kOpacityWhenDraggedOverUserIcon : details_.initial_opacity); next_window_resizer_->Drag(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::Window* 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(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 tray_users = tray->GetTrayUserItems(); if (tray_users.size() <= 1) return NULL; std::vector::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); GetTarget()->SetBounds(details_.initial_bounds_in_parent); if (!tray_user->TransferWindowToUser(details_.window)) { GetTarget()->layer()->SetOpacity(old_opacity); return false; } RevertDrag(); return true; } } // namespace internal } // namespace ash