diff options
Diffstat (limited to 'chromium/ash/display/mouse_cursor_event_filter.cc')
-rw-r--r-- | chromium/ash/display/mouse_cursor_event_filter.cc | 255 |
1 files changed, 255 insertions, 0 deletions
diff --git a/chromium/ash/display/mouse_cursor_event_filter.cc b/chromium/ash/display/mouse_cursor_event_filter.cc new file mode 100644 index 00000000000..ebac900a891 --- /dev/null +++ b/chromium/ash/display/mouse_cursor_event_filter.cc @@ -0,0 +1,255 @@ +// 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/display/mouse_cursor_event_filter.h" + +#include "ash/display/display_controller.h" +#include "ash/display/display_manager.h" +#include "ash/display/mirror_window_controller.h" +#include "ash/display/shared_display_edge_indicator.h" +#include "ash/screen_ash.h" +#include "ash/shell.h" +#include "ash/wm/coordinate_conversion.h" +#include "ash/wm/window_util.h" +#include "ui/aura/env.h" +#include "ui/aura/root_window.h" +#include "ui/aura/window.h" +#include "ui/base/layout.h" +#include "ui/compositor/dip_util.h" +#include "ui/events/event.h" +#include "ui/gfx/screen.h" + +namespace ash { +namespace internal { +namespace { + +// Maximum size on the display edge that initiate snapping phantom window, +// from the corner of the display. +const int kMaximumSnapHeight = 16; + +// Minimum height of an indicator on the display edge that allows +// dragging a window. If two displays shares the edge smaller than +// this, entire edge will be used as a draggable space. +const int kMinimumIndicatorHeight = 200; + +const int kIndicatorThickness = 1; +} + +MouseCursorEventFilter::MouseCursorEventFilter() + : mouse_warp_mode_(WARP_ALWAYS), + was_mouse_warped_(false), + drag_source_root_(NULL), + scale_when_drag_started_(1.0f), + shared_display_edge_indicator_(new SharedDisplayEdgeIndicator) { +} + +MouseCursorEventFilter::~MouseCursorEventFilter() { + HideSharedEdgeIndicator(); +} + +void MouseCursorEventFilter::ShowSharedEdgeIndicator( + const aura::Window* from) { + HideSharedEdgeIndicator(); + if (Shell::GetScreen()->GetNumDisplays() <= 1 || from == NULL) { + src_indicator_bounds_.SetRect(0, 0, 0, 0); + dst_indicator_bounds_.SetRect(0, 0, 0, 0); + drag_source_root_ = NULL; + return; + } + drag_source_root_ = from; + + DisplayLayout::Position position = Shell::GetInstance()-> + display_manager()->GetCurrentDisplayLayout().position; + if (position == DisplayLayout::TOP || position == DisplayLayout::BOTTOM) + UpdateHorizontalIndicatorWindowBounds(); + else + UpdateVerticalIndicatorWindowBounds(); + + shared_display_edge_indicator_->Show(src_indicator_bounds_, + dst_indicator_bounds_); +} + +void MouseCursorEventFilter::HideSharedEdgeIndicator() { + shared_display_edge_indicator_->Hide(); +} + +void MouseCursorEventFilter::OnMouseEvent(ui::MouseEvent* event) { + if (event->type() == ui::ET_MOUSE_PRESSED) { + aura::Window* target = static_cast<aura::Window*>(event->target()); + scale_when_drag_started_ = ui::GetDeviceScaleFactor(target->layer()); + } else if (event->type() == ui::ET_MOUSE_RELEASED) { + scale_when_drag_started_ = 1.0f; + } + + // Handle both MOVED and DRAGGED events here because when the mouse pointer + // enters the other root window while dragging, the underlying window system + // (at least X11) stops generating a ui::ET_MOUSE_MOVED event. + if (event->type() != ui::ET_MOUSE_MOVED && + event->type() != ui::ET_MOUSE_DRAGGED) { + return; + } + Shell::GetInstance()->display_controller()-> + mirror_window_controller()->UpdateCursorLocation(); + + gfx::Point point_in_screen(event->location()); + aura::Window* target = static_cast<aura::Window*>(event->target()); + wm::ConvertPointToScreen(target, &point_in_screen); + if (WarpMouseCursorIfNecessary(target->GetRootWindow(), point_in_screen)) + event->StopPropagation(); +} + +bool MouseCursorEventFilter::WarpMouseCursorIfNecessary( + aura::Window* target_root, + const gfx::Point& point_in_screen) { + if (Shell::GetScreen()->GetNumDisplays() <= 1 || + mouse_warp_mode_ == WARP_NONE) + return false; + + // Do not warp again right after the cursor was warped. Sometimes the offset + // is not long enough and the cursor moves at the edge of the destination + // display. See crbug.com/278885 + // TODO(mukai): simplify the offset calculation below, it would not be + // necessary anymore with this flag. + if (was_mouse_warped_) { + was_mouse_warped_ = false; + return false; + } + + aura::Window* root_at_point = wm::GetRootWindowAt(point_in_screen); + gfx::Point point_in_root = point_in_screen; + wm::ConvertPointFromScreen(root_at_point, &point_in_root); + gfx::Rect root_bounds = root_at_point->bounds(); + int offset_x = 0; + int offset_y = 0; + + // If the window is dragged between 2x display and 1x display, + // staring from 2x display, pointer location is rounded by the + // source scale factor (2x) so it will never reach the edge (which + // is odd). Shrink by scale factor of the display where the dragging + // started instead. Only integral scale factor is supported for now. + int shrink = scale_when_drag_started_; + // Make the bounds inclusive to detect the edge. + root_bounds.Inset(0, 0, shrink, shrink); + gfx::Rect src_indicator_bounds = src_indicator_bounds_; + src_indicator_bounds.Inset(-shrink, -shrink, -shrink, -shrink); + + if (point_in_root.x() <= root_bounds.x()) { + // Use -2, not -1, to avoid infinite loop of pointer warp. + offset_x = -2 * scale_when_drag_started_; + } else if (point_in_root.x() >= root_bounds.right()) { + offset_x = 2 * scale_when_drag_started_; + } else if (point_in_root.y() <= root_bounds.y()) { + offset_y = -2 * scale_when_drag_started_; + } else if (point_in_root.y() >= root_bounds.bottom()) { + offset_y = 2 * scale_when_drag_started_; + } else { + return false; + } + + gfx::Point point_in_dst_screen(point_in_screen); + point_in_dst_screen.Offset(offset_x, offset_y); + aura::Window* dst_root = wm::GetRootWindowAt(point_in_dst_screen); + + // Warp the mouse cursor only if the location is in the indicator bounds + // or the mouse pointer is in the destination root. + if (mouse_warp_mode_ == WARP_DRAG && + dst_root != drag_source_root_ && + !src_indicator_bounds.Contains(point_in_screen)) { + return false; + } + + wm::ConvertPointFromScreen(dst_root, &point_in_dst_screen); + + if (dst_root->bounds().Contains(point_in_dst_screen)) { + DCHECK_NE(dst_root, root_at_point); + was_mouse_warped_ = true; + dst_root->MoveCursorTo(point_in_dst_screen); + return true; + } + return false; +} + +void MouseCursorEventFilter::UpdateHorizontalIndicatorWindowBounds() { + bool from_primary = Shell::GetPrimaryRootWindow() == drag_source_root_; + // GetPrimaryDisplay returns an object on stack, so copy the bounds + // instead of using reference. + const gfx::Rect primary_bounds = + Shell::GetScreen()->GetPrimaryDisplay().bounds(); + const gfx::Rect secondary_bounds = ScreenAsh::GetSecondaryDisplay().bounds(); + DisplayLayout::Position position = Shell::GetInstance()-> + display_manager()->GetCurrentDisplayLayout().position; + + src_indicator_bounds_.set_x( + std::max(primary_bounds.x(), secondary_bounds.x())); + src_indicator_bounds_.set_width( + std::min(primary_bounds.right(), secondary_bounds.right()) - + src_indicator_bounds_.x()); + src_indicator_bounds_.set_height(kIndicatorThickness); + src_indicator_bounds_.set_y( + position == DisplayLayout::TOP ? + primary_bounds.y() - (from_primary ? 0 : kIndicatorThickness) : + primary_bounds.bottom() - (from_primary ? kIndicatorThickness : 0)); + + dst_indicator_bounds_ = src_indicator_bounds_; + dst_indicator_bounds_.set_height(kIndicatorThickness); + dst_indicator_bounds_.set_y( + position == DisplayLayout::TOP ? + primary_bounds.y() - (from_primary ? kIndicatorThickness : 0) : + primary_bounds.bottom() - (from_primary ? 0 : kIndicatorThickness)); +} + +void MouseCursorEventFilter::UpdateVerticalIndicatorWindowBounds() { + bool in_primary = Shell::GetPrimaryRootWindow() == drag_source_root_; + // GetPrimaryDisplay returns an object on stack, so copy the bounds + // instead of using reference. + const gfx::Rect primary_bounds = + Shell::GetScreen()->GetPrimaryDisplay().bounds(); + const gfx::Rect secondary_bounds = ScreenAsh::GetSecondaryDisplay().bounds(); + DisplayLayout::Position position = Shell::GetInstance()-> + display_manager()->GetCurrentDisplayLayout().position; + + int upper_shared_y = std::max(primary_bounds.y(), secondary_bounds.y()); + int lower_shared_y = std::min(primary_bounds.bottom(), + secondary_bounds.bottom()); + int shared_height = lower_shared_y - upper_shared_y; + + int dst_x = position == DisplayLayout::LEFT ? + primary_bounds.x() - (in_primary ? kIndicatorThickness : 0) : + primary_bounds.right() - (in_primary ? 0 : kIndicatorThickness); + dst_indicator_bounds_.SetRect( + dst_x, upper_shared_y, kIndicatorThickness, shared_height); + + // The indicator on the source display. + src_indicator_bounds_.set_width(kIndicatorThickness); + src_indicator_bounds_.set_x( + position == DisplayLayout::LEFT ? + primary_bounds.x() - (in_primary ? 0 : kIndicatorThickness) : + primary_bounds.right() - (in_primary ? kIndicatorThickness : 0)); + + const gfx::Rect& source_bounds = + in_primary ? primary_bounds : secondary_bounds; + int upper_indicator_y = source_bounds.y() + kMaximumSnapHeight; + int lower_indicator_y = std::min(source_bounds.bottom(), lower_shared_y); + + // This gives a hight that can be used without sacrifying the snap space. + int available_space = lower_indicator_y - + std::max(upper_shared_y, upper_indicator_y); + + if (shared_height < kMinimumIndicatorHeight) { + // If the shared height is smaller than minimum height, use the + // entire height. + upper_indicator_y = upper_shared_y; + } else if (available_space < kMinimumIndicatorHeight) { + // Snap to the bottom. + upper_indicator_y = + std::max(upper_shared_y, lower_indicator_y + kMinimumIndicatorHeight); + } else { + upper_indicator_y = std::max(upper_indicator_y, upper_shared_y); + } + src_indicator_bounds_.set_y(upper_indicator_y); + src_indicator_bounds_.set_height(lower_indicator_y - upper_indicator_y); +} + +} // namespace internal +} // namespace ash |