// 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/screen_util.h" #include "ash/shell.h" #include "ash/wm/coordinate_conversion.h" #include "ash/wm/drag_window_controller.h" #include "ash/wm/window_state.h" #include "ash/wm/window_util.h" #include "base/memory/weak_ptr.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/env.h" #include "ui/aura/window.h" #include "ui/aura/window_delegate.h" #include "ui/aura/window_event_dispatcher.h" #include "ui/base/hit_test.h" #include "ui/base/ui_base_types.h" #include "ui/gfx/screen.h" #include "ui/wm/core/coordinate_conversion.h" #include "ui/wm/core/window_util.h" namespace ash { 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 HasSecondaryRootWindows() { return Shell::GetAllRootWindows().size() > 1; } // When there are more than one root windows, returns all root windows which are // not |root_window|. Returns an empty vector if only one root window exists. aura::Window::Windows GetOtherRootWindows(aura::Window* root_window) { aura::Window::Windows root_windows = Shell::GetAllRootWindows(); aura::Window::Windows other_root_windows; if (root_windows.size() < 2) return other_root_windows; for (size_t i = 0; i < root_windows.size(); ++i) { if (root_windows[i] != root_window) other_root_windows.push_back(root_windows[i]); } return other_root_windows; } } // namespace // static DragWindowResizer* DragWindowResizer::instance_ = NULL; DragWindowResizer::~DragWindowResizer() { if (window_state_) window_state_->DeleteDragDetails(); 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, wm::WindowState* window_state) { return new DragWindowResizer(next_window_resizer, window_state); } void DragWindowResizer::Drag(const gfx::Point& location, int event_flags) { base::WeakPtr resizer(weak_ptr_factory_.GetWeakPtr()); 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 (HasSecondaryRootWindows()) { 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_controllers_.clear(); } } void DragWindowResizer::CompleteDrag() { next_window_resizer_->CompleteDrag(); GetTarget()->layer()->SetOpacity(details().initial_opacity); drag_window_controllers_.clear(); // 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()) { // Adjust the size and position so that it doesn't exceed the size of // work area. const gfx::Size& size = dst_display.work_area().size(); gfx::Rect bounds = GetTarget()->bounds(); if (bounds.width() > size.width()) { int diff = bounds.width() - size.width(); bounds.set_x(bounds.x() + diff / 2); bounds.set_width(size.width()); } if (bounds.height() > size.height()) bounds.set_height(size.height()); gfx::Rect dst_bounds = ScreenUtil::ConvertRectToScreen(GetTarget()->parent(), bounds); // Adjust the position so that the cursor is on the window. if (!dst_bounds.Contains(last_mouse_location_in_screen)) { if (last_mouse_location_in_screen.x() < dst_bounds.x()) dst_bounds.set_x(last_mouse_location_in_screen.x()); else if (last_mouse_location_in_screen.x() > dst_bounds.right()) dst_bounds.set_x( last_mouse_location_in_screen.x() - dst_bounds.width()); } ash::wm::AdjustBoundsToEnsureMinimumWindowVisibility( dst_display.bounds(), &dst_bounds); GetTarget()->SetBoundsInScreen(dst_bounds, dst_display); } } void DragWindowResizer::RevertDrag() { next_window_resizer_->RevertDrag(); drag_window_controllers_.clear(); GetTarget()->layer()->SetOpacity(details().initial_opacity); } DragWindowResizer::DragWindowResizer(WindowResizer* next_window_resizer, wm::WindowState* window_state) : WindowResizer(window_state), next_window_resizer_(next_window_resizer), 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(GetTarget()->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::Windows other_roots = GetOtherRootWindows(GetTarget()->GetRootWindow()); size_t drag_window_controller_count = 0; for (size_t i = 0; i < other_roots.size(); ++i) { aura::Window* another_root = other_roots[i]; const gfx::Rect root_bounds_in_screen(another_root->GetBoundsInScreen()); const gfx::Rect bounds_in_screen = ScreenUtil::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_controllers_.size() < ++drag_window_controller_count) drag_window_controllers_.resize(drag_window_controller_count); ScopedVector::reference drag_window_controller = drag_window_controllers_.back(); if (!drag_window_controller) { drag_window_controller = 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 { GetTarget()->layer()->SetOpacity(1.0f); } } // If we have more drag window controllers allocated than needed, release the // excess controllers by shrinking the vector |drag_window_controller_|. DCHECK_GE(drag_window_controllers_.size(), drag_window_controller_count); if (drag_window_controllers_.size() > drag_window_controller_count) drag_window_controllers_.resize(drag_window_controller_count); } bool DragWindowResizer::ShouldAllowMouseWarp() { return (details().window_component == HTCAPTION) && !::wm::GetTransientParent(GetTarget()) && (GetTarget()->type() == ui::wm::WINDOW_TYPE_NORMAL || GetTarget()->type() == ui::wm::WINDOW_TYPE_PANEL); } } // namespace ash