diff options
author | varunjain@chromium.org <varunjain@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-11-28 12:11:49 +0000 |
---|---|---|
committer | varunjain@chromium.org <varunjain@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-11-28 12:11:49 +0000 |
commit | ffa4e448cf15245002c286ea781717426aa3eff4 (patch) | |
tree | ca251556ea039f07dfc5a4bb96817aaaac8c656a /ash/drag_drop | |
parent | 186b661f7383039cecfb392643349eb0c5fce2a8 (diff) | |
download | chromium_src-ffa4e448cf15245002c286ea781717426aa3eff4.zip chromium_src-ffa4e448cf15245002c286ea781717426aa3eff4.tar.gz chromium_src-ffa4e448cf15245002c286ea781717426aa3eff4.tar.bz2 |
aura: Enable touch initiated drag and drop.
Touch drag/drop in aura follows the following rules:
1. Initiate drag on long press gesture. Give the user a visual cue by showing
a scaled up drag image.
2. Do not initiate dnd if there is no drag image.
3. If, after initiating a drag, the user lifts up their finger without moving,
cancel the drag and show a context menu (this is implemented using the long tap
gesture).
BUG=114755
Review URL: https://chromiumcodereview.appspot.com/11368131
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@169901 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ash/drag_drop')
-rw-r--r-- | ash/drag_drop/drag_drop_controller.cc | 274 | ||||
-rw-r--r-- | ash/drag_drop/drag_drop_controller.h | 32 | ||||
-rw-r--r-- | ash/drag_drop/drag_drop_controller_unittest.cc | 154 | ||||
-rw-r--r-- | ash/drag_drop/drag_drop_tracker.cc | 9 | ||||
-rw-r--r-- | ash/drag_drop/drag_drop_tracker.h | 11 | ||||
-rw-r--r-- | ash/drag_drop/drag_drop_tracker_unittest.cc | 24 | ||||
-rw-r--r-- | ash/drag_drop/drag_image_view.cc | 17 | ||||
-rw-r--r-- | ash/drag_drop/drag_image_view.h | 4 |
8 files changed, 450 insertions, 75 deletions
diff --git a/ash/drag_drop/drag_drop_controller.cc b/ash/drag_drop/drag_drop_controller.cc index bde1b36..4e947ba 100644 --- a/ash/drag_drop/drag_drop_controller.cc +++ b/ash/drag_drop/drag_drop_controller.cc @@ -9,6 +9,7 @@ #include "ash/shell.h" #include "ash/wm/coordinate_conversion.h" #include "ash/wm/cursor_manager.h" +#include "base/bind.h" #include "base/message_loop.h" #include "base/run_loop.h" #include "ui/aura/client/capture_client.h" @@ -22,6 +23,7 @@ #include "ui/base/events/event.h" #include "ui/gfx/point.h" #include "ui/gfx/rect.h" +#include "ui/gfx/rect_conversions.h" #include "ui/views/views_delegate.h" #include "ui/views/widget/native_widget_aura.h" @@ -33,8 +35,41 @@ using aura::RootWindow; namespace { // The duration of the drag cancel animation in millisecond. const int kCancelAnimationDuration = 250; +const int kTouchCancelAnimationDuration = 20; // The frame rate of the drag cancel animation in hertz. const int kCancelAnimationFrameRate = 60; + +// For touch initiated dragging, we scale and shift drag image by the following: +static const float kTouchDragImageScale = 1.2; +static const int kTouchDragImageVerticalOffset = -25; + +// Adjusts the drag image bounds such that the new bounds are scaled by |scale| +// and translated by the |drag_image_offset| and and additional +// |vertical_offset|. +gfx::Rect AdjustDragImageBoundsForScaleAndOffset( + const gfx::Rect& drag_image_bounds, + int vertical_offset, + float scale, + gfx::Vector2d* drag_image_offset) { + gfx::PointF final_origin = drag_image_bounds.origin(); + gfx::SizeF final_size = drag_image_bounds.size(); + final_size.Scale(scale); + drag_image_offset->set_x(drag_image_offset->x() * scale); + drag_image_offset->set_y(drag_image_offset->y() * scale); + float total_x_offset = drag_image_offset->x(); + float total_y_offset = drag_image_offset->y() - vertical_offset; + final_origin.Offset(-total_x_offset, -total_y_offset); + return gfx::ToEnclosingRect(gfx::RectF(final_origin, final_size)); +} + +void DispatchGestureEndToWindow(aura::Window* window) { + if (window && window->delegate()) { + ui::GestureEvent gesture_end(ui::ET_GESTURE_END, 0, 0, 0, + base::Time::Now() - base::Time::FromDoubleT(0), + ui::GestureEventDetails(ui::ET_GESTURE_END, 0, 0), 0); + window->delegate()->OnGestureEvent(&gesture_end); + } +} } // namespace //////////////////////////////////////////////////////////////////////////////// @@ -45,7 +80,10 @@ DragDropController::DragDropController() drag_data_(NULL), drag_operation_(0), drag_window_(NULL), - should_block_during_drag_drop_(true) { + drag_source_window_(NULL), + should_block_during_drag_drop_(true), + current_drag_event_source_(ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE), + weak_factory_(this) { Shell::GetInstance()->AddPreTargetHandler(this); } @@ -65,26 +103,60 @@ int DragDropController::StartDragAndDrop( const gfx::Point& root_location, int operation, ui::DragDropTypes::DragEventSource source) { - DCHECK(!IsDragDropInProgress()); + if (IsDragDropInProgress()) + return 0; - drag_drop_tracker_.reset(new DragDropTracker); + const ui::OSExchangeDataProviderAura& provider = + static_cast<const ui::OSExchangeDataProviderAura&>(data.provider()); + // We do not support touch drag/drop without a drag image. + if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH && + provider.drag_image().size().IsEmpty()) + return 0; + + current_drag_event_source_ = source; + DragDropTracker* tracker = new DragDropTracker; + if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH) { + // We need to transfer the current gesture sequence and the GR's touch event + // queue to the |drag_drop_tracker_|'s capture window so that when it takes + // capture, it still gets a valid gesture state. + root_window->gesture_recognizer()->TransferEventsTo(source_window, + tracker->capture_window()); + // We also send a gesture end to the source window so it can clear state. + // TODO(varunjain): Remove this whole block when gesture sequence + // transferring is properly done in the GR (http://crbug.com/160558) + DispatchGestureEndToWindow(source_window); + } + tracker->TakeCapture(); + drag_drop_tracker_.reset(tracker); + drag_source_window_ = source_window; + if (drag_source_window_) + drag_source_window_->AddObserver(this); + pending_long_tap_.reset(); drag_data_ = &data; drag_operation_ = operation; - const ui::OSExchangeDataProviderAura& provider = - static_cast<const ui::OSExchangeDataProviderAura&>(data.provider()); + float drag_image_scale = 1; + int drag_image_vertical_offset = 0; + if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH) { + drag_image_scale = kTouchDragImageScale; + drag_image_vertical_offset = kTouchDragImageVerticalOffset; + } + gfx::Point start_location = root_location; + ash::wm::ConvertPointToScreen(root_window, &start_location); + drag_image_final_bounds_for_cancel_animation_ = gfx::Rect( + start_location - provider.drag_image_offset(), + provider.drag_image().size()); drag_image_.reset(new DragImageView); drag_image_->SetImage(provider.drag_image()); drag_image_offset_ = provider.drag_image_offset(); - drag_image_->SetBoundsInScreen(gfx::Rect( - root_location - drag_image_offset_, - drag_image_->GetPreferredSize())); + gfx::Rect drag_image_bounds(start_location, drag_image_->GetPreferredSize()); + drag_image_bounds = AdjustDragImageBoundsForScaleAndOffset(drag_image_bounds, + drag_image_vertical_offset, drag_image_scale, &drag_image_offset_); + drag_image_->SetBoundsInScreen(drag_image_bounds); drag_image_->SetWidgetVisible(true); drag_window_ = NULL; - drag_start_location_ = root_location - drag_image_offset_; - ash::wm::ConvertPointToScreen(root_window, &drag_start_location_); // Ends cancel animation if it's in progress. if (cancel_animation_.get()) @@ -100,6 +172,15 @@ int DragDropController::StartDragAndDrop( } #endif // !defined(OS_MACOSX) + if (!cancel_animation_.get() || !cancel_animation_->is_animating() || + !pending_long_tap_.get()) { + // If drag cancel animation is running, this cleanup is done when the + // animation completes. + if (drag_source_window_) + drag_source_window_->RemoveObserver(this); + drag_source_window_ = NULL; + } + return drag_operation_; } @@ -110,10 +191,13 @@ void DragDropController::DragUpdate(aura::Window* target, if (drag_window_) { if ((delegate = aura::client::GetDragDropDelegate(drag_window_))) delegate->OnDragExited(); - drag_window_->RemoveObserver(this); + if (drag_window_ != drag_source_window_) + drag_window_->RemoveObserver(this); } drag_window_ = target; - drag_window_->AddObserver(this); + // We are already an observer of |drag_source_window_| so no need to add. + if (drag_window_ != drag_source_window_) + drag_window_->AddObserver(this); if ((delegate = aura::client::GetDragDropDelegate(drag_window_))) { ui::DropTargetEvent e(*drag_data_, event.location(), @@ -169,7 +253,7 @@ void DragDropController::Drop(aura::Window* target, e.set_flags(event.flags()); drag_operation_ = delegate->OnPerformDrop(e); if (drag_operation_ == 0) - StartCanceledAnimation(); + StartCanceledAnimation(kCancelAnimationDuration); else drag_image_.reset(); } else { @@ -182,21 +266,7 @@ void DragDropController::Drop(aura::Window* target, } void DragDropController::DragCancel() { - ash::Shell::GetInstance()->cursor_manager()->SetCursor(ui::kCursorPointer); - - // |drag_window_| can be NULL if we have just started the drag and have not - // received any DragUpdates, or, if the |drag_window_| gets destroyed during - // a drag/drop. - aura::client::DragDropDelegate* delegate = drag_window_? - aura::client::GetDragDropDelegate(drag_window_) : NULL; - if (delegate) - delegate->OnDragExited(); - - Cleanup(); - drag_operation_ = 0; - StartCanceledAnimation(); - if (should_block_during_drag_drop_) - quit_closure_.Run(); + DoDragCancel(kCancelAnimationDuration); } bool DragDropController::IsDragDropInProgress() { @@ -214,13 +284,19 @@ ui::EventResult DragDropController::OnKeyEvent(ui::KeyEvent* event) { ui::EventResult DragDropController::OnMouseEvent(ui::MouseEvent* event) { if (!IsDragDropInProgress()) return ui::ER_UNHANDLED; + + // If current drag session was not started by mouse, dont process this mouse + // event, but consume it so it does not interfere with current drag session. + if (current_drag_event_source_ != ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE) + return ui::ER_CONSUMED; + aura::Window* translated_target = drag_drop_tracker_->GetTarget(*event); if (!translated_target) { DragCancel(); return ui::ER_CONSUMED; } - scoped_ptr<ui::MouseEvent> translated_event( - drag_drop_tracker_->ConvertMouseEvent(translated_target, *event)); + scoped_ptr<ui::LocatedEvent> translated_event( + drag_drop_tracker_->ConvertEvent(translated_target, *event)); switch (translated_event->type()) { case ui::ET_MOUSE_DRAGGED: DragUpdate(translated_target, *translated_event.get()); @@ -238,26 +314,77 @@ ui::EventResult DragDropController::OnMouseEvent(ui::MouseEvent* event) { } ui::EventResult DragDropController::OnTouchEvent(ui::TouchEvent* event) { - // TODO(sad): Also check for the touch-id. - // TODO(varunjain): Add code for supporting drag-and-drop across displays - // (http://crbug.com/114755). - aura::Window* target = static_cast<aura::Window*>(event->target()); if (!IsDragDropInProgress()) return ui::ER_UNHANDLED; + + // If current drag session was not started by touch, dont process this touch + // event, but consume it so it does not interfere with current drag session. + if (current_drag_event_source_ != ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH) + return ui::ER_CONSUMED; + switch (event->type()) { - case ui::ET_TOUCH_MOVED: - DragUpdate(target, *event); - break; - case ui::ET_TOUCH_RELEASED: - Drop(target, *event); - break; case ui::ET_TOUCH_CANCELLED: DragCancel(); break; default: - return ui::ER_UNHANDLED; + break; } - return ui::ER_CONSUMED; + return ui::ER_UNHANDLED; +} + +ui::EventResult DragDropController::OnGestureEvent(ui::GestureEvent* event) { + if (!IsDragDropInProgress()) + return ui::ER_UNHANDLED; + + // If current drag session was not started by touch, dont process this touch + // event, but consume it so it does not interfere with current drag session. + if (current_drag_event_source_ != ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH) + return ui::ER_CONSUMED; + + // Apply kTouchDragImageVerticalOffset to the location. + ui::GestureEvent touch_offset_event(*event, + static_cast<aura::Window*>(NULL), + static_cast<aura::Window*>(NULL)); + gfx::Point touch_offset_location = touch_offset_event.location(); + gfx::Point touch_offset_root_location = touch_offset_event.root_location(); + touch_offset_location.Offset(0, kTouchDragImageVerticalOffset); + touch_offset_root_location.Offset(0, kTouchDragImageVerticalOffset); + touch_offset_event.set_location(touch_offset_location); + touch_offset_event.set_root_location(touch_offset_root_location); + + aura::Window* translated_target = + drag_drop_tracker_->GetTarget(touch_offset_event); + if (!translated_target) { + DragCancel(); + return ui::ER_HANDLED; + } + scoped_ptr<ui::LocatedEvent> translated_event( + drag_drop_tracker_->ConvertEvent(translated_target, touch_offset_event)); + + switch (event->type()) { + case ui::ET_GESTURE_SCROLL_UPDATE: + DragUpdate(translated_target, *translated_event.get()); + break; + case ui::ET_GESTURE_SCROLL_END: + Drop(translated_target, *translated_event.get()); + break; + case ui::ET_SCROLL_FLING_START: + case ui::ET_GESTURE_LONG_TAP: + // Ideally we would want to just forward this long tap event to the + // |drag_source_window_|. However, webkit does not accept events while a + // drag drop is still in progress. The drag drop ends only when the nested + // message loop ends. Due to this stupidity, we have to defer forwarding + // the long tap. + pending_long_tap_.reset( + new ui::GestureEvent(*event, + static_cast<aura::Window*>(drag_drop_tracker_->capture_window()), + static_cast<aura::Window*>(drag_source_window_))); + DoDragCancel(kTouchCancelAnimationDuration); + break; + default: + break; + } + return ui::ER_HANDLED; } void DragDropController::OnWindowDestroyed(aura::Window* window) { @@ -265,42 +392,87 @@ void DragDropController::OnWindowDestroyed(aura::Window* window) { drag_window_->RemoveObserver(this); drag_window_ = NULL; } + if (drag_source_window_ == window) { + drag_source_window_->RemoveObserver(this); + drag_source_window_ = NULL; + } } //////////////////////////////////////////////////////////////////////////////// // DragDropController, private: void DragDropController::AnimationEnded(const ui::Animation* animation) { - drag_image_->SetScreenPosition(drag_start_location_); + drag_image_->SetScreenPosition( + drag_image_final_bounds_for_cancel_animation_.origin()); cancel_animation_.reset(); // By the time we finish animation, another drag/drop session may have // started. We do not want to destroy the drag image in that case. if (!IsDragDropInProgress()) drag_image_.reset(); + if (pending_long_tap_.get()) { + // If not in a nested message loop, we can forward the long tap right now. + if (!should_block_during_drag_drop_) + ForwardPendingLongTap(); + else { + // See comment about this in OnGestureEvent(). + MessageLoopForUI::current()->PostTask( + FROM_HERE, base::Bind(&DragDropController::ForwardPendingLongTap, + weak_factory_.GetWeakPtr())); + } + } +} + +void DragDropController::DoDragCancel(int drag_cancel_animation_duration_ms) { + ash::Shell::GetInstance()->cursor_manager()->SetCursor(ui::kCursorPointer); + + // |drag_window_| can be NULL if we have just started the drag and have not + // received any DragUpdates, or, if the |drag_window_| gets destroyed during + // a drag/drop. + aura::client::DragDropDelegate* delegate = drag_window_? + aura::client::GetDragDropDelegate(drag_window_) : NULL; + if (delegate) + delegate->OnDragExited(); + + Cleanup(); + drag_operation_ = 0; + StartCanceledAnimation(drag_cancel_animation_duration_ms); + if (should_block_during_drag_drop_) + quit_closure_.Run(); } void DragDropController::AnimationProgressed(const ui::Animation* animation) { - drag_image_->SetScreenPosition(gfx::Point( - animation->CurrentValueBetween(drag_cancel_location_.x(), - drag_start_location_.x()), - animation->CurrentValueBetween(drag_cancel_location_.y(), - drag_start_location_.y()))); + gfx::Rect current_bounds = animation->CurrentValueBetween( + drag_image_initial_bounds_for_cancel_animation_, + drag_image_final_bounds_for_cancel_animation_); + drag_image_->SetBoundsInScreen(current_bounds); } void DragDropController::AnimationCanceled(const ui::Animation* animation) { AnimationEnded(animation); } -void DragDropController::StartCanceledAnimation() { +void DragDropController::StartCanceledAnimation(int animation_duration_ms) { DCHECK(drag_image_.get()); - drag_cancel_location_ = drag_image_->GetBoundsInScreen().origin(); - cancel_animation_.reset(new ui::LinearAnimation(kCancelAnimationDuration, + drag_image_initial_bounds_for_cancel_animation_ = + drag_image_->GetBoundsInScreen(); + cancel_animation_.reset(new ui::LinearAnimation(animation_duration_ms, kCancelAnimationFrameRate, this)); cancel_animation_->Start(); } +void DragDropController::ForwardPendingLongTap() { + if (drag_source_window_ && drag_source_window_->delegate()) { + drag_source_window_->delegate()->OnGestureEvent(pending_long_tap_.get()); + DispatchGestureEndToWindow(drag_source_window_); + } + pending_long_tap_.reset(); + if (drag_source_window_) + drag_source_window_->RemoveObserver(this); + drag_source_window_ = NULL; +} + void DragDropController::Cleanup() { if (drag_window_) drag_window_->RemoveObserver(this); diff --git a/ash/drag_drop/drag_drop_controller.h b/ash/drag_drop/drag_drop_controller.h index bdeb06f..9c6375f 100644 --- a/ash/drag_drop/drag_drop_controller.h +++ b/ash/drag_drop/drag_drop_controller.h @@ -7,13 +7,14 @@ #include "ash/ash_export.h" #include "base/callback.h" +#include "base/memory/weak_ptr.h" #include "ui/aura/client/drag_drop_client.h" #include "ui/aura/window_observer.h" #include "ui/base/animation/animation_delegate.h" #include "ui/base/dragdrop/os_exchange_data.h" #include "ui/base/events/event_constants.h" #include "ui/base/events/event_handler.h" -#include "ui/gfx/point.h" +#include "ui/gfx/rect.h" namespace aura { class RootWindow; @@ -67,10 +68,15 @@ class ASH_EXPORT DragDropController virtual ui::EventResult OnKeyEvent(ui::KeyEvent* event) OVERRIDE; virtual ui::EventResult OnMouseEvent(ui::MouseEvent* event) OVERRIDE; virtual ui::EventResult OnTouchEvent(ui::TouchEvent* event) OVERRIDE; + virtual ui::EventResult OnGestureEvent(ui::GestureEvent* event) OVERRIDE; // Overridden from aura::WindowObserver. virtual void OnWindowDestroyed(aura::Window* window) OVERRIDE; + protected: + // Actual implementation of |DragCancel()|. protected for testing. + virtual void DoDragCancel(int drag_cancel_animation_duration_ms); + private: friend class ash::test::DragDropControllerTest; @@ -80,7 +86,10 @@ class ASH_EXPORT DragDropController virtual void AnimationCanceled(const ui::Animation* animation) OVERRIDE; // Helper method to start drag widget flying back animation. - void StartCanceledAnimation(); + void StartCanceledAnimation(int animation_duration_ms); + + // Helper method to forward |pending_log_tap_| event to |drag_source_window_|. + void ForwardPendingLongTap(); // Helper method to reset everything. void Cleanup(); @@ -92,13 +101,16 @@ class ASH_EXPORT DragDropController // Window that is currently under the drag cursor. aura::Window* drag_window_; - // The location where drag is started in screen coordinate. - gfx::Point drag_start_location_; - // The location where drag is canceled in screen coordinate. - gfx::Point drag_cancel_location_; + + // Starting and final bounds for the drag image for the drag cancel animation. + gfx::Rect drag_image_initial_bounds_for_cancel_animation_; + gfx::Rect drag_image_final_bounds_for_cancel_animation_; scoped_ptr<ui::LinearAnimation> cancel_animation_; + // Window that started the drag. + aura::Window* drag_source_window_; + // Indicates whether the caller should be blocked on a drag/drop session. // Only be used for tests. bool should_block_during_drag_drop_; @@ -108,6 +120,14 @@ class ASH_EXPORT DragDropController scoped_ptr<ash::internal::DragDropTracker> drag_drop_tracker_; + ui::DragDropTypes::DragEventSource current_drag_event_source_; + + // Holds a synthetic long tap event to be sent to the |drag_source_window_|. + // See comment in OnGestureEvent() on why we need this. + scoped_ptr<ui::GestureEvent> pending_long_tap_; + + base::WeakPtrFactory<DragDropController> weak_factory_; + DISALLOW_COPY_AND_ASSIGN(DragDropController); }; diff --git a/ash/drag_drop/drag_drop_controller_unittest.cc b/ash/drag_drop/drag_drop_controller_unittest.cc index 4474207..45c06c0 100644 --- a/ash/drag_drop/drag_drop_controller_unittest.cc +++ b/ash/drag_drop/drag_drop_controller_unittest.cc @@ -7,6 +7,7 @@ #include "ash/drag_drop/drag_image_view.h" #include "ash/shell.h" #include "ash/test/ash_test_base.h" +#include "base/command_line.h" #include "base/location.h" #include "base/utf_string_conversions.h" #include "ui/aura/root_window.h" @@ -16,7 +17,11 @@ #include "ui/base/clipboard/scoped_clipboard_writer.h" #include "ui/base/dragdrop/drag_drop_types.h" #include "ui/base/dragdrop/os_exchange_data.h" +#include "ui/base/dragdrop/os_exchange_data_provider_aura.h" #include "ui/base/events/event.h" +#include "ui/base/gestures/gesture_types.h" +#include "ui/base/ui_base_switches.h" +#include "ui/gfx/image/image_skia_rep.h" #include "ui/views/test/test_views_delegate.h" #include "ui/views/view.h" #include "ui/views/views_delegate.h" @@ -42,6 +47,7 @@ class DragTestView : public views::View { num_drag_updates_ = 0; num_drops_ = 0; drag_done_received_ = false; + long_tap_received_ = false; } int VerticalDragThreshold() { @@ -57,6 +63,7 @@ class DragTestView : public views::View { int num_drag_updates_; int num_drops_; bool drag_done_received_; + bool long_tap_received_; private: // View overrides: @@ -66,12 +73,24 @@ class DragTestView : public views::View { void WriteDragData(const gfx::Point& p, OSExchangeData* data) OVERRIDE { data->SetString(UTF8ToUTF16("I am being dragged")); + ui::OSExchangeDataProviderAura& provider = + static_cast<ui::OSExchangeDataProviderAura&>(data->provider()); + gfx::ImageSkiaRep* image = new gfx::ImageSkiaRep( + gfx::Size(10, 20), ui::SCALE_FACTOR_100P); + gfx::ImageSkia* image_skia = new gfx::ImageSkia(*image); + provider.set_drag_image(*image_skia); } bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE { return true; } + ui::EventResult OnGestureEvent(ui::GestureEvent* event) OVERRIDE { + if (event->type() == ui::ET_GESTURE_LONG_TAP) + long_tap_received_ = true; + return ui::ER_UNHANDLED; + } + bool GetDropFormats(int* formats, std::set<OSExchangeData::CustomFormat>* custom_formats) { *formats = ui::OSExchangeData::STRING; @@ -149,6 +168,11 @@ class TestDragDropController : public internal::DragDropController { drag_canceled_ = true; } + void DoDragCancel(int animation_duration_ms) OVERRIDE { + DragDropController::DoDragCancel(animation_duration_ms); + drag_canceled_ = true; + } + bool drag_start_received_; int num_drag_updates_; bool drop_received_; @@ -216,6 +240,13 @@ void AddViewToWidgetAndResize(views::Widget* widget, views::View* view) { widget->SetBounds(contents_view_bounds); } +void DispatchGesture(ui::EventType gesture_type, gfx::Point location) { + ui::GestureEvent gesture_event(gesture_type, location.x(), location.y(), 0, + base::Time::NowFromSystemTime() - base::Time(), + ui::GestureEventDetails(gesture_type, 0, 0), 1); + Shell::GetPrimaryRootWindow()->DispatchGestureEvent(&gesture_event); +} + } // namespace class DragDropControllerTest : public AshTestBase { @@ -246,6 +277,15 @@ class DragDropControllerTest : public AshTestBase { return drag_drop_controller_->drag_window_; } + aura::Window* GetDragSourceWindow() { + return drag_drop_controller_->drag_source_window_; + } + + void SetDragSourceWindow(aura::Window* drag_source_window) { + drag_drop_controller_->drag_source_window_ = drag_source_window; + drag_source_window->AddObserver(drag_drop_controller_.get()); + } + aura::Window* GetDragImageWindow() { return drag_drop_controller_->drag_image_.get() ? drag_drop_controller_->drag_image_->GetWidget()->GetNativeWindow() : @@ -689,6 +729,116 @@ TEST_F(DragDropControllerTest, PressingEscapeCancelsDragDrop) { EXPECT_TRUE(drag_view->drag_done_received_); } +TEST_F(DragDropControllerTest, TouchDragDropInMultipleWindows) { + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kEnableTouchDragDrop); + scoped_ptr<views::Widget> widget1(CreateNewWidget()); + DragTestView* drag_view1 = new DragTestView; + AddViewToWidgetAndResize(widget1.get(), drag_view1); + scoped_ptr<views::Widget> widget2(CreateNewWidget()); + DragTestView* drag_view2 = new DragTestView; + AddViewToWidgetAndResize(widget2.get(), drag_view2); + gfx::Rect widget1_bounds = widget1->GetClientAreaBoundsInScreen(); + gfx::Rect widget2_bounds = widget2->GetClientAreaBoundsInScreen(); + widget2->SetBounds(gfx::Rect(widget1_bounds.width(), 0, + widget2_bounds.width(), widget2_bounds.height())); + + ui::OSExchangeData data; + data.SetString(UTF8ToUTF16("I am being dragged")); + + aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(), + widget1->GetNativeView()); + generator.PressTouch(); + gfx::Point point = gfx::Rect(drag_view1->bounds()).CenterPoint(); + DispatchGesture(ui::ET_GESTURE_LONG_PRESS, point); + // Because we are not doing a blocking drag and drop, the original + // OSDragExchangeData object is lost as soon as we return from the drag + // initiation in DragDropController::StartDragAndDrop(). Hence we set the + // drag_data_ to a fake drag data object that we created. + UpdateDragData(&data); + gfx::Point gesture_location = point; + int num_drags = drag_view1->width(); + for (int i = 0; i < num_drags; ++i) { + gesture_location.Offset(1, 0); + DispatchGesture(ui::ET_GESTURE_SCROLL_UPDATE, gesture_location); + + // Execute any scheduled draws to process deferred mouse events. + RunAllPendingInMessageLoop(); + } + + DispatchGesture(ui::ET_GESTURE_SCROLL_END, gesture_location); + + EXPECT_TRUE(drag_drop_controller_->drag_start_received_); + EXPECT_EQ(num_drags, drag_drop_controller_->num_drag_updates_); + EXPECT_TRUE(drag_drop_controller_->drop_received_); + EXPECT_EQ(UTF8ToUTF16("I am being dragged"), + drag_drop_controller_->drag_string_); + + EXPECT_EQ(1, drag_view1->num_drag_enters_); + int num_expected_updates = drag_view1->bounds().width() - + drag_view1->bounds().CenterPoint().x() - 1; + EXPECT_EQ(num_expected_updates, drag_view1->num_drag_updates_); + EXPECT_EQ(0, drag_view1->num_drops_); + EXPECT_EQ(1, drag_view1->num_drag_exits_); + EXPECT_TRUE(drag_view1->drag_done_received_); + + EXPECT_EQ(1, drag_view2->num_drag_enters_); + num_expected_updates = num_drags - num_expected_updates; + EXPECT_EQ(num_expected_updates, drag_view2->num_drag_updates_); + EXPECT_EQ(1, drag_view2->num_drops_); + EXPECT_EQ(0, drag_view2->num_drag_exits_); + EXPECT_FALSE(drag_view2->drag_done_received_); +} + +TEST_F(DragDropControllerTest, TouchDragDropCancelsOnLongTap) { + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kEnableTouchDragDrop); + scoped_ptr<views::Widget> widget(CreateNewWidget()); + DragTestView* drag_view = new DragTestView; + AddViewToWidgetAndResize(widget.get(), drag_view); + aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(), + widget->GetNativeView()); + + generator.PressTouch(); + gfx::Point point = gfx::Rect(drag_view->bounds()).CenterPoint(); + DispatchGesture(ui::ET_GESTURE_LONG_PRESS, point); + DispatchGesture(ui::ET_GESTURE_LONG_TAP, point); + + EXPECT_TRUE(drag_drop_controller_->drag_start_received_); + EXPECT_TRUE(drag_drop_controller_->drag_canceled_); + EXPECT_EQ(0, drag_drop_controller_->num_drag_updates_); + EXPECT_FALSE(drag_drop_controller_->drop_received_); + EXPECT_EQ(UTF8ToUTF16("I am being dragged"), + drag_drop_controller_->drag_string_); + EXPECT_EQ(0, drag_view->num_drag_enters_); + EXPECT_EQ(0, drag_view->num_drops_); + EXPECT_EQ(0, drag_view->num_drag_exits_); + EXPECT_TRUE(drag_view->drag_done_received_); +} + +TEST_F(DragDropControllerTest, TouchDragDropLongTapGestureIsForwarded) { + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kEnableTouchDragDrop); + scoped_ptr<views::Widget> widget(CreateNewWidget()); + DragTestView* drag_view = new DragTestView; + AddViewToWidgetAndResize(widget.get(), drag_view); + aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(), + widget->GetNativeView()); + + generator.PressTouch(); + gfx::Point point = gfx::Rect(drag_view->bounds()).CenterPoint(); + DispatchGesture(ui::ET_GESTURE_LONG_PRESS, point); + + // Since we are not running inside a nested loop, the |drag_source_window_| + // will get destroyed immediately. Hence we reassign it. + EXPECT_EQ(NULL, GetDragSourceWindow()); + SetDragSourceWindow(widget->GetNativeView()); + EXPECT_FALSE(drag_view->long_tap_received_); + DispatchGesture(ui::ET_GESTURE_LONG_TAP, point); + EndCancelAnimation(); + EXPECT_TRUE(drag_view->long_tap_received_); +} + namespace { class DragImageWindowObserver : public aura::WindowObserver { @@ -790,6 +940,10 @@ TEST_F(DragDropControllerTest, DragCancelAcrossDisplays) { EXPECT_EQ("405,405", observer.window_location_on_destroying().ToString()); } + for (Shell::RootWindowList::iterator iter = root_windows.begin(); + iter != root_windows.end(); ++iter) { + aura::client::SetDragDropClient(*iter, NULL); + } } } // namespace test diff --git a/ash/drag_drop/drag_drop_tracker.cc b/ash/drag_drop/drag_drop_tracker.cc index f0bb206..2258755 100644 --- a/ash/drag_drop/drag_drop_tracker.cc +++ b/ash/drag_drop/drag_drop_tracker.cc @@ -30,13 +30,16 @@ namespace internal { DragDropTracker::DragDropTracker() : capture_window_(CreateCaptureWindow()) { - capture_window_->SetCapture(); } DragDropTracker::~DragDropTracker() { capture_window_->ReleaseCapture(); } +void DragDropTracker::TakeCapture() { + capture_window_->SetCapture(); +} + aura::Window* DragDropTracker::GetTarget(const ui::LocatedEvent& event) { DCHECK(capture_window_.get()); gfx::Point location_in_screen = event.location(); @@ -49,9 +52,9 @@ aura::Window* DragDropTracker::GetTarget(const ui::LocatedEvent& event) { return root_window_at_point->GetEventHandlerForPoint(location_in_root); } -ui::MouseEvent* DragDropTracker::ConvertMouseEvent( +ui::LocatedEvent* DragDropTracker::ConvertEvent( aura::Window* target, - const ui::MouseEvent& event) { + const ui::LocatedEvent& event) { DCHECK(capture_window_.get()); gfx::Point target_location = event.location(); aura::Window::ConvertPointToTarget(capture_window_.get(), target, diff --git a/ash/drag_drop/drag_drop_tracker.h b/ash/drag_drop/drag_drop_tracker.h index 56b6309..6493b06 100644 --- a/ash/drag_drop/drag_drop_tracker.h +++ b/ash/drag_drop/drag_drop_tracker.h @@ -27,6 +27,13 @@ class ASH_EXPORT DragDropTracker { DragDropTracker(); ~DragDropTracker(); + aura::Window* capture_window() { return capture_window_.get(); } + + // Tells our |capture_window_| to take capture. This is not done right at + // creation to give the caller a chance to perform any operations needed + // before the capture is transfered. + void TakeCapture(); + // Gets the target located at |event| in the coordinates of the active root // window. aura::Window* GetTarget(const ui::LocatedEvent& event); @@ -34,8 +41,8 @@ class ASH_EXPORT DragDropTracker { // Converts the locations of |event| in the coordinates of the active root // window to the ones in |target|'s coordinates. // Caller takes ownership of the returned object. - ui::MouseEvent* ConvertMouseEvent(aura::Window* target, - const ui::MouseEvent& event); + ui::LocatedEvent* ConvertEvent(aura::Window* target, + const ui::LocatedEvent& event); private: // A window for capturing drag events while dragging. diff --git a/ash/drag_drop/drag_drop_tracker_unittest.cc b/ash/drag_drop/drag_drop_tracker_unittest.cc index dfefe82..279e17e 100644 --- a/ash/drag_drop/drag_drop_tracker_unittest.cc +++ b/ash/drag_drop/drag_drop_tracker_unittest.cc @@ -43,11 +43,11 @@ class DragDropTrackerTest : public test::AshTestBase { return target; } - static ui::MouseEvent* ConvertMouseEvent(aura::Window* target, + static ui::LocatedEvent* ConvertEvent(aura::Window* target, const ui::MouseEvent& event) { scoped_ptr<internal::DragDropTracker> tracker( new internal::DragDropTracker); - ui::MouseEvent* converted = tracker->ConvertMouseEvent(target, event); + ui::LocatedEvent* converted = tracker->ConvertEvent(target, event); return converted; } }; @@ -122,12 +122,12 @@ TEST_F(DragDropTrackerTest, MAYBE_GetTarget) { // TODO(mazda): Remove this once ash/wm/coordinate_conversion.h supports // non-X11 platforms. #if defined(USE_X11) -#define MAYBE_ConvertMouseEvent ConvertMouseEvent +#define MAYBE_ConvertEvent ConvertEvent #else -#define MAYBE_ConvertMouseEvent DISABLED_ConvertMouseEvent +#define MAYBE_ConvertEvent DISABLED_ConvertEvent #endif -TEST_F(DragDropTrackerTest, MAYBE_ConvertMouseEvent) { +TEST_F(DragDropTrackerTest, MAYBE_ConvertEvent) { Shell::RootWindowList root_windows = Shell::GetAllRootWindows(); EXPECT_EQ(2U, root_windows.size()); @@ -150,8 +150,8 @@ TEST_F(DragDropTrackerTest, MAYBE_ConvertMouseEvent) { gfx::Point(50, 50), gfx::Point(50, 50), ui::EF_NONE); - scoped_ptr<ui::MouseEvent> converted00(ConvertMouseEvent(window0.get(), - original00)); + scoped_ptr<ui::LocatedEvent> converted00(ConvertEvent(window0.get(), + original00)); EXPECT_EQ(original00.type(), converted00->type()); EXPECT_EQ("50,50", converted00->location().ToString()); EXPECT_EQ("50,50", converted00->root_location().ToString()); @@ -163,8 +163,8 @@ TEST_F(DragDropTrackerTest, MAYBE_ConvertMouseEvent) { gfx::Point(350, 150), gfx::Point(350, 150), ui::EF_NONE); - scoped_ptr<ui::MouseEvent> converted01(ConvertMouseEvent(window1.get(), - original01)); + scoped_ptr<ui::LocatedEvent> converted01(ConvertEvent(window1.get(), + original01)); EXPECT_EQ(original01.type(), converted01->type()); EXPECT_EQ("50,50", converted01->location().ToString()); EXPECT_EQ("150,150", converted01->root_location().ToString()); @@ -179,8 +179,8 @@ TEST_F(DragDropTrackerTest, MAYBE_ConvertMouseEvent) { gfx::Point(-150, 50), gfx::Point(-150, 50), ui::EF_NONE); - scoped_ptr<ui::MouseEvent> converted10(ConvertMouseEvent(window0.get(), - original10)); + scoped_ptr<ui::LocatedEvent> converted10(ConvertEvent(window0.get(), + original10)); EXPECT_EQ(original10.type(), converted10->type()); EXPECT_EQ("50,50", converted10->location().ToString()); EXPECT_EQ("50,50", converted10->root_location().ToString()); @@ -192,7 +192,7 @@ TEST_F(DragDropTrackerTest, MAYBE_ConvertMouseEvent) { gfx::Point(150, 150), gfx::Point(150, 150), ui::EF_NONE); - scoped_ptr<ui::MouseEvent> converted11(ConvertMouseEvent(window1.get(), + scoped_ptr<ui::LocatedEvent> converted11(ConvertEvent(window1.get(), original11)); EXPECT_EQ(original11.type(), converted11->type()); EXPECT_EQ("50,50", converted11->location().ToString()); diff --git a/ash/drag_drop/drag_image_view.cc b/ash/drag_drop/drag_image_view.cc index 6490d6f..fb28b9d 100644 --- a/ash/drag_drop/drag_image_view.cc +++ b/ash/drag_drop/drag_image_view.cc @@ -4,7 +4,9 @@ #include "ash/drag_drop/drag_image_view.h" +#include "skia/ext/image_operations.h" #include "ui/aura/window.h" +#include "ui/gfx/canvas.h" #include "ui/views/corewm/shadow_types.h" #include "ui/views/widget/widget.h" @@ -45,10 +47,11 @@ DragImageView::~DragImageView() { void DragImageView::SetBoundsInScreen(const gfx::Rect& bounds) { widget_->SetBounds(bounds); + widget_size_ = bounds.size(); } void DragImageView::SetScreenPosition(const gfx::Point& position) { - widget_->SetBounds(gfx::Rect(position, GetPreferredSize())); + widget_->SetBounds(gfx::Rect(position, widget_size_)); } void DragImageView::SetWidgetVisible(bool visible) { @@ -60,5 +63,17 @@ void DragImageView::SetWidgetVisible(bool visible) { } } +void DragImageView::OnPaint(gfx::Canvas* canvas) { + if (GetImage().size() == widget_size_) { + canvas->DrawImageInt(GetImage(), 0, 0); + } else { + SkBitmap scaled = skia::ImageOperations::Resize( + *GetImage().bitmap(), skia::ImageOperations::RESIZE_LANCZOS3, + widget_size_.width(), widget_size_.height()); + SkPaint paint; + canvas->sk_canvas()->drawBitmap(scaled, 0, 0, &paint); + } +} + } // namespace internal } // namespace ash diff --git a/ash/drag_drop/drag_image_view.h b/ash/drag_drop/drag_image_view.h index 63f718c..d8db83c 100644 --- a/ash/drag_drop/drag_image_view.h +++ b/ash/drag_drop/drag_image_view.h @@ -32,7 +32,11 @@ class DragImageView : public views::ImageView { void SetWidgetVisible(bool visible); private: + // Overridden from views::ImageView. + virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE; + scoped_ptr<views::Widget> widget_; + gfx::Size widget_size_; DISALLOW_COPY_AND_ASSIGN(DragImageView); }; |